ぴーさんログ

だいたいXamarin.Formsのブログ

【Xamarin.Forms】RelativeLayoutとConstraintのちょっと深い話

最近こんな質問に回答してました。

teratail.com

という訳で今回は、RelativeLayoutの子要素にConstraintを再セットすることについて少し掘り下げます。

C#でRelaytiveLayoutにレアイアウト制約付きで子要素追加する際のコードはこんな感じです。

var label = new Label
{
    Text = "Child Label"
};

var layout = RelativeLayout();
layout.Children.Add(label,
    Constraint.RelativeToParent(parent => (parent.Width / 2) - (label.Width / 2)), // xConstraint
    Constraint.RelativeToParent(parent => (parent.Height / 2) - (label.Height / 2)), // yConstraint
    Constraint.RelativeToParent(parent => parent.Width), // widthConstraint
    Constraint.Constant(50) // heightConstraint
);

teratailで回答したとおり RelativeLayout.Children.Add(View, Constraint, Constraint, Constraint, Constraint) を重ねてコールするとレイアウト制約を上書きできます。

「同じViewを何度もAddしてマズい事にならないの?」という疑問が浮かぶでしょうが、既にChildrenに存在するViewをAddすると追加処理が無視されます。(興味のある人は RelativeLayout.RelativeElementCollection -> ElementCollection<T> -> ObservableWrapper<TTrack, TRestrict> とソースを追いかけてみよう!)

そのため、結果としてレイアウト制約だけが上書きされます。

......そうはいっても何度もAddを繰り返すのは気持ちが悪いですよね。

実は他に RelativeLayout.SetBoundsConstraint(View, BoundsConstraint) を使う方法もあります。

先ほどのlayout.Children.Addと同等のレイアウト制約をセットすると次のようなコードになります。

RelativeLayout.SetBoundsConstraint(label,
    BoundsConstraint.FromExpression(
        () => new Rectangle(
            (layout.Width / 2) - (label.Width / 2),
            (layout.Height / 2) - (label.Height / 2),
            layout.Width,
            50),
        null));

BoundsConstraintBoundsConstraint.FromExpression() で作ります。第1引数には対象Viewの位置とサイズを表すRectangleを返す式、第2引数には式の中で参照されるViewのコレクションをセットします。第2引数はおそらく式で参照するViewが解放されないように参照を持っておくための物ではないかと思われます。基本的に内部で使用するものと想定しているのか妙なデザインですね。

RelativeLayout.Children.Add()で渡したConstraintは最終的にこのBoundsConstraintに変換されます。

以下のような流れです。

RelativeLayout.Children.Add()で子要素とConstraintをセット。
↓
子ViewがRelativeLayout.Childrenに追加される。
X,Y,Width,HeightのConstraintがBoundsConstraintに変換され子Viewにセットされる。
↓
RelativeLayoutのサイズ変更などのタイミングでBoundsConstraintを元に子Viewの位置とサイズが解決される。

ここで注目したいのは次の2点。

  • 必ず1度はRelativeLayout.Children.Add()する必要がある
  • Addする前にRelativeLayout.SetBoundsConstraint()してはならない(上書きされるから)

そんな風に気を使ってとっつきにくいBoundsConstraintよりは、RelativeLayout.Children.Add()を繰り返す方が分かりやすいのではないかなー、という訳で例の回答でしたとさ。