16
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

[初心者さん・学生さん大歓迎!] Xamarin その1Advent Calendar 2017

Day 5

Xamarin(Native/Forms) から使える iOS(UIStackView) / Android(LinearLayout) で同じようにLayoutするためのサンプル集

Last updated at Posted at 2017-12-04

CustomRenderer等でNativeのLayoutを使ってiOS/Android両方で同じようなLayoutにするコーディングのサンプルを紹介します。サンプルの環境ではFormsを使っていますが、Nativeでも同じようにできると思います。

使用するLayoutはiOSはUIStackView + (必要に応じて)Constraint、AndroidはLinearLayout + (必要に応じて)RelativeLayoutを使います。

なお、UIStackViewよりもLinearLayoutの方が自由度が高いのでこのサンプルでは、UIStackViewをベースとして比較します。

諸注意

  • 水平方向のみのレイアウトがメインです。垂直方向はほぼ同じなのでここでは扱いません。
  • レイアウトの背景はわかりやすくするために薄いグレーにしています。

iOS

  • _label1〜3はUILabelです。
  • UIStackView自体の細かい説明はしません。詳しい説明以下の記事ががすごくわかりやすくて良いです。

StackViewを賢く使ってらくちんAutoLayout

Android

  • サンプル中に出てくるContextはクラスのメンバにあるものを使っています。
  • Context.ToPixels(6)のようなToPixelsはFormsの拡張メソッドでdpをpxに変換するものです。Native単体の場合は変換処理を行うか、xmlでdp指定してください。
  • _label1〜3はTextViewです。
  • コードの方が対比しやすいのでコードでのサンプルですが、実際はxmlで記述する方が良いと思います。

ソース一式

最後の要素に余白を割り当てる 各要素は垂直中央揃え

iOS

6aa47bcd5504953568418691cb8647f0.png

void Sample1()
{
    _container = new UIStackView {
        Axis = UILayoutConstraintAxis.Horizontal, //並べる方向
        Alignment = UIStackViewAlignment.Center, //垂直位置属性
        Distribution = UIStackViewDistribution.Fill, //余白分配設定
    };

    _container.AddArrangedSubview(_label1);
    _container.AddArrangedSubview(_label2);
    _container.AddArrangedSubview(_label3);

    //余った領域を広げる優先度の設定(低いものが優先して拡大する)
    _label1.SetContentHuggingPriority(999f, UILayoutConstraintAxis.Horizontal);
    _label2.SetContentHuggingPriority(999f, UILayoutConstraintAxis.Horizontal);
    _label3.SetContentHuggingPriority(1f, UILayoutConstraintAxis.Horizontal); //最優先
}

UIStackViewのDistributionをFillに設定し、
余白の割り当てられやすさを各要素のSetContentHuggingPriorityで設定します。

優先度が高い(数値が低い)ものから優先的に余白が割り当てられます。この場合は最後の要素を広げたいので一番優先度を高く(数値を小さく)して他を低く(数値を高く)しています。

Android

feee538b577d394fda9ffc8c3c31a948.png

void Sample1()
{
    _container = new LinearLayout(Context);
    _container.Orientation = Orientation.Horizontal; //並べる方向

    var param = new LinearLayout.LayoutParams(LayoutParams.WrapContent, LayoutParams.WrapContent) {
        Gravity = GravityFlags.CenterVertical //垂直方向位置
    };
    _container.AddView(_label1, param);
    _container.AddView(_label2, param);

    var param2 = new LinearLayout.LayoutParams(LayoutParams.WrapContent, LayoutParams.WrapContent) {
        Width = 0,
        Weight = 1, //余白を優先して割り当てる
        Gravity = GravityFlags.CenterVertical //垂直方向位置
    };
    _container.AddView(_label3, param2);
}

余白の割り当てには割り当てたい要素のLayoutParamsのWeightに1を指定します。Weightを指定する時はセットでWidthを0にする必要があります。

サイズが溢れた時に要素が消えるのを防ぐ

iOS

ff2fd3f982226d600f7944cc538bfe6e.png

//余った領域を広げる優先度の設定(低いものが優先して拡大する)
_label1.SetContentHuggingPriority(999f, UILayoutConstraintAxis.Horizontal);
_label2.SetContentHuggingPriority(999f, UILayoutConstraintAxis.Horizontal);
_label3.SetContentHuggingPriority(1f, UILayoutConstraintAxis.Horizontal); //最優先

//縮まりやすさの設定(低いものが優先して縮まる)
_label1.SetContentCompressionResistancePriority(999f, UILayoutConstraintAxis.Horizontal);
_label2.SetContentCompressionResistancePriority(999f, UILayoutConstraintAxis.Horizontal);
_label3.SetContentCompressionResistancePriority(1f, UILayoutConstraintAxis.Horizontal); //最優先

iOSの場合は何も指定しない場合に、要素のサイズが溢れた時に他の要素が消えてしまうことがあります。

これを防ぐにはサイズが溢れる要素の縮まりやすさをSetContentCompressionResistancePriorityで優先度を高く(低い数値)します。

この場合は最後の要素のContentCompressionResistancePriorityを最も優先度を高く(数値を低く)し、他を低く(数値を高く)することで、文字が溢れた時に他の要素を巻き込まずに自身を縮めるような動きになります。

ContentHuggingPriorityを同時に設定することで文字が少なくサイズに余裕がある場合は余白を割り当て、溢れたらそれ以上は伸ばさないということが可能です。

Android

10fe275507c2c96bb552394c02c1bb6f.png

要素が縮められることはないので何もする必要はありません。

余白を振り分けない

iOS

52a630d9d81f7799b20e219732ed6e98.png

void Sample3()
{
    _container = new UIStackView {
        Axis = UILayoutConstraintAxis.Horizontal, //並べる方向
        Alignment = UIStackViewAlignment.Center, //垂直位置属性
        Distribution = UIStackViewDistribution.Fill, //余白分配設定
    };

    _container.AddArrangedSubview(_label1);
    _container.AddArrangedSubview(_label2);
    _container.AddArrangedSubview(_label3);

    var dummy = new UIView();
    _container.AddArrangedSubview(dummy);

    //余った領域を広げる優先度の設定(低いものが優先して拡大する)
    _label1.SetContentHuggingPriority(999f, UILayoutConstraintAxis.Horizontal);
    _label2.SetContentHuggingPriority(999f, UILayoutConstraintAxis.Horizontal);
    _label3.SetContentHuggingPriority(999f, UILayoutConstraintAxis.Horizontal);
    dummy.SetContentHuggingPriority(1f, UILayoutConstraintAxis.Horizontal); //最優先
}

そういう機能はないので、ダミーのUIViewを最後に挿入しContentHuggingPriorityを高く(数値を低く)します。

Android

f8ab153528aa4dfbb5d80ca719c76017.png

Weightを指定せずにWrapContentにすると、そうなります。

要素を右寄せっぽくする

iOS

09b2aea4065de061aca27649a557b3eb.png

void Sample4()
{
    _container = new UIStackView {
        Axis = UILayoutConstraintAxis.Horizontal, //並べる方向
        Alignment = UIStackViewAlignment.Center, //垂直位置属性
        Distribution = UIStackViewDistribution.Fill, //余白分配設定
    };

    //先頭にダミーを追加
    var dummy = new UIView();
    _container.AddArrangedSubview(dummy);

    _container.AddArrangedSubview(_label1);
    _container.AddArrangedSubview(_label2);
    _container.AddArrangedSubview(_label3);


    //余った領域を広げる優先度の設定(低いものが優先して拡大する)
    dummy.SetContentHuggingPriority(1f, UILayoutConstraintAxis.Horizontal); //最優先
    _label1.SetContentHuggingPriority(999f, UILayoutConstraintAxis.Horizontal);
    _label2.SetContentHuggingPriority(999f, UILayoutConstraintAxis.Horizontal);
    _label3.SetContentHuggingPriority(999f, UILayoutConstraintAxis.Horizontal);
}

ダミーのUIViewを先頭に挿入して、ContentHuggingPriorityを高く(数値を低く)します。

Android

1b2aefb0fade5f42bb65cd0e4a62904c.png

void Sample4()
{
    _container = new LinearLayout(Context);
    _container.Orientation = Orientation.Horizontal; //並べる方向
    _container.SetGravity(GravityFlags.End); //右寄せ

    var param = new LinearLayout.LayoutParams(LayoutParams.WrapContent, LayoutParams.WrapContent) {
        Gravity = GravityFlags.CenterVertical //垂直方向位置
    };
    _container.AddView(_label1, param);
    _container.AddView(_label2, param);
    _container.AddView(_label3, param);
}

LinearLayoutに右寄せのGravityを設定します。

両端に寄せる

iOS

b2d1109a87ea1964f63bb2c9f8eac265.png

    //略
    
    _container.AddArrangedSubview(_label1);

    //分けたいところにダミーを追加
    var dummy = new UIView();
    _container.AddArrangedSubview(dummy);

    _container.AddArrangedSubview(_label2);
    _container.AddArrangedSubview(_label3);

    //余った領域を広げる優先度の設定(低いものが優先して拡大する)
    _label1.SetContentHuggingPriority(999f, UILayoutConstraintAxis.Horizontal);
    dummy.SetContentHuggingPriority(1f, UILayoutConstraintAxis.Horizontal); //最優先
    _label2.SetContentHuggingPriority(999f, UILayoutConstraintAxis.Horizontal);
    _label3.SetContentHuggingPriority(999f, UILayoutConstraintAxis.Horizontal);

ダミーのUIViewを分けたい位置に挿入して、ContentHuggingPriorityを高く(数値を低く)します。

Android

47a15ec4dec8a424f89dff402acee59f.png

    //略

    var param = new LinearLayout.LayoutParams(LayoutParams.WrapContent, LayoutParams.WrapContent) {
        Gravity = GravityFlags.CenterVertical //垂直方向位置
    };
    _container.AddView(_label1, param);
    _container.AddView(_label2, param);
    _container.AddView(_label3, param);

    var param2 = new LinearLayout.LayoutParams(LayoutParams.WrapContent, LayoutParams.WrapContent) {
        Width = 0,
        Weight = 1
    };

    //分けたい箇所にWeight1のダミーを挿入
    _container.AddView(new LinearLayout(Context), 1, param2);

ダミーのViewGroupをWeight=1にして分けたい箇所に挿入します。
というかこういう場合はRelativeLayoutを使った方が良いですね。

全ての要素を均等割り付け

iOS

8040e5fb31f9d12ae0892588b48c22ca.png

void Sample6()
{
    _container = new UIStackView {
        Axis = UILayoutConstraintAxis.Horizontal, //並べる方向
        Alignment = UIStackViewAlignment.Center, //垂直位置属性
        Distribution = UIStackViewDistribution.FillEqually, //余白分配設定 均等
    };

    //略
}

均等に配置するにはDistributionをFillEquallyにします。

Android

6537254d1229d5a24d99e73e58d10f85.png

void Sample6()
{
    //略
    var param = new LinearLayout.LayoutParams(LayoutParams.WrapContent, LayoutParams.WrapContent) {
        Width = 0,
        Weight = 1,//余白は平等に
        Gravity = GravityFlags.CenterVertical //垂直方向位置
    };
    _container.AddView(_label1, param);
    _container.AddView(_label2, param);
    _container.AddView(_label3, param);
}

全ての要素のWeightを1にします。

要素間に余白を置く

iOS

4f563bb59aa7f922882d83f15f96ead9.png

void Sample7()
{
    _container = new UIStackView {
        Axis = UILayoutConstraintAxis.Horizontal, //並べる方向
        Alignment = UIStackViewAlignment.Center, //垂直位置属性
        Distribution = UIStackViewDistribution.FillEqually, //余白分配設定 均等
        Spacing = 6 //要素間のマージン
    };
    //略
}

UIStackViewのSpacingを設定します。

Android

3f4eff3ac46ac05316352ec67f3123a9.png

void Sample7()
{
    //略
    var param = new LinearLayout.LayoutParams(LayoutParams.WrapContent, LayoutParams.WrapContent) {
        Width = 0,
        Weight = 1,//余白は平等に
        Gravity = GravityFlags.CenterVertical, //垂直方向位置
        RightMargin = (int)Context.ToPixels(6), //要素間マージン
    };
    _container.AddView(_label1, param);
    _container.AddView(_label2, param);

    //最後の要素にはマージンは不要
    var param2 = new LinearLayout.LayoutParams(LayoutParams.WrapContent, LayoutParams.WrapContent) {
        Width = 0,
        Weight = 1,
        Gravity = GravityFlags.CenterVertical
    };
    _container.AddView(_label3, param2);
}

要素個別にRightMarginを設定します。(最後の要素には設定しません)
当然LeftMarginを先頭以外に設定する方法でも問題ありません。

高さを目一杯にする

iOS

b8b678b5768bba5dd2d36d054b98c5a9.png

void Sample8()
{
    _container = new UIStackView {
        Axis = UILayoutConstraintAxis.Horizontal, //並べる方向
        Alignment = UIStackViewAlignment.Fill, //垂直位置属性
        Distribution = UIStackViewDistribution.FillEqually, //余白分配設定 均等
        Spacing = 6 //要素間のマージン
    };
    //略
}

UIStackViewのAlignmentをFillにします。

Android

5ff0984efcf0892e7e2a88e39e20fda2.png

void Sample8()
{
    //略
    var param = new LinearLayout.LayoutParams(LayoutParams.WrapContent, LayoutParams.MatchParent) { //高さは親に合わせる
        Width = 0,
        Weight = 1,//余白は平等に
        Gravity = GravityFlags.CenterVertical, //垂直方向位置
        RightMargin = (int)Context.ToPixels(6), //要素間マージン
    };
    _container.AddView(_label1, param);
    _container.AddView(_label2, param);

    //最後の要素にはマージンは不要
    var param2 = new LinearLayout.LayoutParams(LayoutParams.WrapContent, LayoutParams.MatchParent) { //高さは親に合わせる
        Width = 0,
        Weight = 1,
        Gravity = GravityFlags.CenterVertical
    };
    _container.AddView(_label3, param2);
}

各要素のLayoutParamsのLayoutHeight(LayoutParamsの第二引数)をMatchParentにします。
TextViewの文字列垂直中央揃えはTextViewのGravityで設定しています。

レイアウトにPaddingを設定する

iOS

1f1d2176d451b883135f746b48e1739c.png

void Sample9()
{
    //略

    //レイアウトの余白設定
    _container.LayoutMargins = new UIEdgeInsets(6, 6, 6, 6);
    _container.LayoutMarginsRelativeArrangement = true;
}

UIStackViewのLayoutMarginsを設定しLayoutMarginsRelativeArrangementをtrueにします。

Android

45f70a8b58163900909503bbdb2fc2bd.png

void Sample9()
{
    _container = new LinearLayout(Context);
    _container.Orientation = Orientation.Horizontal; //並べる方向

    //レイアウトの余白設定
    var padding = (int)Context.ToPixels(6); //ToPixelsはFormsの拡張メソッドでdpをpxに変換する
    _container.SetPadding(padding, padding, padding, padding);

    //略
}

LinearLayoutにPaddingを設定します。

特定の要素を幅固定にする

iOS

ec2d2ee3e0c17e3a317aaea928902c4b.png

void Sample10()
{
    _container = new UIStackView {
        Axis = UILayoutConstraintAxis.Horizontal, //並べる方向
        Alignment = UIStackViewAlignment.Fill, //垂直位置属性
        Distribution = UIStackViewDistribution.Fill, //余白分配設定
        Spacing = 6 //要素間のマージン
    };

    _container.AddArrangedSubview(_label1);
    _container.AddArrangedSubview(_label2);
    _container.AddArrangedSubview(_label3);

    //レイアウトの余白設定
    _container.LayoutMargins = new UIEdgeInsets(6, 6, 6, 6);
    _container.LayoutMarginsRelativeArrangement = true;

    _label1.SetContentHuggingPriority(999f, UILayoutConstraintAxis.Horizontal);
    _label2.SetContentHuggingPriority(999f, UILayoutConstraintAxis.Horizontal);
    _label3.SetContentHuggingPriority(1f, UILayoutConstraintAxis.Horizontal);

    //固定の幅を指定
    _label2.WidthAnchor.ConstraintEqualTo(120).Active = true;
}

要素に制約で幅を指定します。この場合は真ん中のUILabelを120固定にしています。
なお、iOS8.0未満の場合はこの書き方はできません。

DistributionがFillEquallyでは機能しません。またAndroidのWeightのように割合で余白を割り振ることはできません。

Android

836e1c724bc9e35cb9e485ddb2c3de88.png

void Sample10()
{
    //略
    var param = new LinearLayout.LayoutParams(LayoutParams.WrapContent, LayoutParams.MatchParent) {
        Gravity = GravityFlags.CenterVertical, //垂直方向位置
        RightMargin = (int)Context.ToPixels(6), //要素間マージン
    };
    _container.AddView(_label1, param);

    var param2 = new LinearLayout.LayoutParams(LayoutParams.WrapContent, LayoutParams.MatchParent) {
        Width = (int)Context.ToPixels(120), //幅固定値指定
        Gravity = GravityFlags.CenterVertical, //垂直方向位置
        RightMargin = (int)Context.ToPixels(6), //要素間マージン
    };
    _container.AddView(_label2, param2);

    //略
}

要素個別にWidthを指定します。この場合は真ん中のTextViewの幅を120dpにしています。

要素の高さを固定値にする

iOS

c6b48c09d74e26e25babe4d0b6f0a792.png

void Sample11()
{
    _container = new UIStackView {
        Axis = UILayoutConstraintAxis.Horizontal, //並べる方向
        Alignment = UIStackViewAlignment.Center, //垂直位置属性
        Distribution = UIStackViewDistribution.FillEqually, //余白分配設定 均等
    };

    _container.AddArrangedSubview(_label1);
    _container.AddArrangedSubview(_label2);
    _container.AddArrangedSubview(_label3);

    //個別の高さを指定
    _label1.HeightAnchor.ConstraintEqualTo(50).Active = true;
    _label2.HeightAnchor.ConstraintEqualTo(80).Active = true;
}

要素に制約で高さを指定します。この場合は最初のUILabelを50、真ん中を80、最後を未指定にしています。

Android

9e28bfd8817970962cbe541e8fe5798b.png

void Sample11()
{
    //略
    var param = new LinearLayout.LayoutParams(LayoutParams.WrapContent, LayoutParams.WrapContent) {
        Height = (int)Context.ToPixels(50),//個別の高さ
        Width = 0,
        Weight = 1,//余白は平等に
        Gravity = GravityFlags.CenterVertical //垂直方向位置
    };
    _container.AddView(_label1, param);

    var param2 = new LinearLayout.LayoutParams(LayoutParams.WrapContent, LayoutParams.WrapContent) {
        Height = (int)Context.ToPixels(80),//個別の高さ
        Width = 0,
        Weight = 1,//余白は平等に
        Gravity = GravityFlags.CenterVertical //垂直方向位置
    };
    _container.AddView(_label2, param2);

    var param3 = new LinearLayout.LayoutParams(LayoutParams.WrapContent, LayoutParams.WrapContent) {
        Width = 0,
        Weight = 1,//余白は平等に
        Gravity = GravityFlags.CenterVertical //垂直方向位置
    };
    _container.AddView(_label3, param3);
}

要素個別にHeightを指定します。この場合は最初のTextViewを50dp、真ん中を80dp、最後を未指定にしています。

要素ごとに垂直属性を変える

iOS

0c929a3e152144781080364e794c8562.png

void Sample12()
{
    _container = new UIStackView {
        Axis = UILayoutConstraintAxis.Horizontal, //並べる方向
        Alignment = UIStackViewAlignment.Fill, //垂直位置属性
        Distribution = UIStackViewDistribution.FillEqually, //余白分配設定 均等
    };

    //ラッパーのUIStackViewを作成する(それぞれ垂直属性を変える)
    var wrapper1 = new UIStackView { Alignment = UIStackViewAlignment.Top };
    var wrapper2 = new UIStackView { Alignment = UIStackViewAlignment.Bottom };
    var wrapper3 = new UIStackView { Alignment = UIStackViewAlignment.Center };

    //ラッパーに要素を詰める
    wrapper1.AddArrangedSubview(_label1);
    wrapper2.AddArrangedSubview(_label2);
    wrapper3.AddArrangedSubview(_label3);

    _container.AddArrangedSubview(wrapper1);
    _container.AddArrangedSubview(wrapper2);
    _container.AddArrangedSubview(wrapper3);
}

UIStackViewはAlignmentは1つしか設定できないので要素ごとに変えることはできません。
ですがUIStackViewを入れ子にすることで個別にAlignmentを設定したかのようにすることができます。

親UIStackViewのAlignmentをFillにして、各要素をUIStackViewでラップして、そのUIStackViewのAlignmentに個別の属性を設定します。

Android

89e83a4ca25a7ab3e8af0b5aa379f361.png

void Sample12()
{
    //略
    var param = new LinearLayout.LayoutParams(LayoutParams.WrapContent, LayoutParams.WrapContent) {
        Width = 0,
        Weight = 1,//余白は平等に
        Gravity = GravityFlags.Top //垂直方向位置 上揃え
    };
    _container.AddView(_label1, param);

    var param2 = new LinearLayout.LayoutParams(LayoutParams.WrapContent, LayoutParams.WrapContent) {
        Width = 0,
        Weight = 1,//余白は平等に
        Gravity = GravityFlags.Bottom //垂直方向位置 下揃え
    };
    _container.AddView(_label2, param2);

    var param3 = new LinearLayout.LayoutParams(LayoutParams.WrapContent, LayoutParams.WrapContent) {
        Width = 0,
        Weight = 1,//余白は平等に
        Gravity = GravityFlags.CenterVertical //垂直方向位置 中央揃え
    };
    _container.AddView(_label3, param3);
}

要素ごとにGravityを変えれば可能です。

指定の位置に要素を重ねて配置する

iOS

f04df0e2b8e968d62e947236134f9224.png

void Sample13()
{
    _parentView = new UIView();

    _container = new UIStackView {
        Axis = UILayoutConstraintAxis.Horizontal, //並べる方向
        Alignment = UIStackViewAlignment.Fill, //垂直位置属性
        Distribution = UIStackViewDistribution.FillEqually, //余白分配設定 均等
        Spacing = 6 //要素間のマージン
    };

    _container.AddArrangedSubview(_label1);
    _container.AddArrangedSubview(_label2);
    _container.AddArrangedSubview(_label3);

    //親Viewにサイズぴったりに配置する
    _parentView.AddSubview(_container);
    _container.TranslatesAutoresizingMaskIntoConstraints = false;
    _container.TopAnchor.ConstraintEqualTo(_parentView.TopAnchor, 0).Active = true;
    _container.LeftAnchor.ConstraintEqualTo(_parentView.LeftAnchor, 0).Active = true;
    _container.BottomAnchor.ConstraintEqualTo(_parentView.BottomAnchor, 0).Active = true;
    _container.RightAnchor.ConstraintEqualTo(_parentView.RightAnchor, 0).Active = true;


    var label4 = new UILabel { Text = "Four", BackgroundColor = UIColor.FromRGBA(0, 0, 0, 125), TextColor = UIColor.White };

    _parentView.AddSubview(label4);

    //左上から6,6の位置に置く
    label4.TranslatesAutoresizingMaskIntoConstraints = false;
    label4.TopAnchor.ConstraintEqualTo(_parentView.TopAnchor, 6).Active = true;
    label4.LeftAnchor.ConstraintEqualTo(_parentView.LeftAnchor, 6).Active = true;

    var label5 = new UILabel { Text = "Five", BackgroundColor = UIColor.FromRGBA(0, 0, 0, 125), TextColor = UIColor.White };

    _parentView.AddSubview(label5);

    //右上から6,6の位置に置く(RightとBottomから内側へのマージンはマイナス指定)
    label5.TranslatesAutoresizingMaskIntoConstraints = false;
    label5.TopAnchor.ConstraintEqualTo(_parentView.TopAnchor, 6).Active = true;
    label5.RightAnchor.ConstraintEqualTo(_parentView.RightAnchor, -6).Active = true;

    var label6 = new UILabel { Text = "Six", BackgroundColor = UIColor.FromRGBA(0, 0, 0, 125), TextColor = UIColor.White };

    _parentView.AddSubview(label6);

    //右下から6,6の位置に置く
    label6.TranslatesAutoresizingMaskIntoConstraints = false;
    label6.RightAnchor.ConstraintEqualTo(_parentView.RightAnchor, -6).Active = true;
    label6.BottomAnchor.ConstraintEqualTo(_parentView.BottomAnchor, -6).Active = true;

    var label7 = new UILabel { Text = "Seven", BackgroundColor = UIColor.FromRGBA(0, 0, 0, 125), TextColor = UIColor.White };

    _parentView.AddSubview(label7);

    //左下から6,6,の位置に置く
    label7.TranslatesAutoresizingMaskIntoConstraints = false;
    label7.LeftAnchor.ConstraintEqualTo(_parentView.LeftAnchor, 6).Active = true;
    label7.BottomAnchor.ConstraintEqualTo(_parentView.BottomAnchor, -6).Active = true;

    var label8 = new UILabel { Text = "Eight", BackgroundColor = UIColor.FromRGBA(0, 0, 0, 125), TextColor = UIColor.White };

    _parentView.AddSubview(label8);

    //ど真ん中に置く
    label8.TranslatesAutoresizingMaskIntoConstraints = false;
    label8.CenterXAnchor.ConstraintEqualTo(_parentView.CenterXAnchor).Active = true;
    label8.CenterYAnchor.ConstraintEqualTo(_parentView.CenterYAnchor).Active = true;
}

UIStackViewの親ViewにAutoLayoutで配置していきます。
RightAnchorとBottomAnchorは余白のように固定値を入れる場合はマイナス値を使います。
サンプルでは親Viewをわざわざ生成してますが、普通に使う時は何らかの親Viewがあるはずなので、そちらに設定していけば良いと思います。
制約設定前のTranslatesAutoresizingMaskIntoConstraints=falseは必須です。

Android

e14bdf5fd192a6f7d398f5f61b90a9b3.png

void Sample13()
{
    _parentView = new RelativeLayout(Context);

    _container = new LinearLayout(Context);
    _container.Orientation = Orientation.Horizontal; //並べる方向

    var param = new LinearLayout.LayoutParams(LayoutParams.WrapContent, LayoutParams.MatchParent) {
        Width = 0,
        Weight = 1,//余白は平等に
        Gravity = GravityFlags.CenterVertical, //垂直方向位置
        RightMargin = (int)Context.ToPixels(6), //要素間マージン
    };
    _container.AddView(_label1, param);
    _container.AddView(_label2, param);

    //最後の要素にはマージンは不要
    var param2 = new LinearLayout.LayoutParams(LayoutParams.WrapContent, LayoutParams.MatchParent) {
        Width = 0,
        Weight = 1,
        Gravity = GravityFlags.CenterVertical
    };
    _container.AddView(_label3, param2);

    using (var p = new RelativeLayout.LayoutParams(LayoutParams.MatchParent, LayoutParams.MatchParent))
    {
        //親Viewにぴったりに合わせて挿入
        _parentView.AddView(_container, p);
    }

    var margin = (int)Context.ToPixels(6);

    var label4 = new TextView(Context) { Text = "Four" };
    label4.SetBackgroundColor(Android.Graphics.Color.Argb(125, 0, 0, 0));
    label4.SetTextColor(Android.Graphics.Color.White);


    using (var p = new RelativeLayout.LayoutParams(LayoutParams.WrapContent, LayoutParams.WrapContent))
    {
        p.AddRule(LayoutRules.AlignParentTop);  //親の上に合わせる
        p.AddRule(LayoutRules.AlignParentLeft); //親の左に合わせる
        p.TopMargin = margin;
        p.LeftMargin = margin;
        _parentView.AddView(label4, p);
    }

    var label5 = new TextView(Context) { Text = "Five" };
    label5.SetBackgroundColor(Android.Graphics.Color.Argb(125, 0, 0, 0));
    label5.SetTextColor(Android.Graphics.Color.White);

    using (var p = new RelativeLayout.LayoutParams(LayoutParams.WrapContent, LayoutParams.WrapContent))
    {
        p.AddRule(LayoutRules.AlignParentTop);   //親の上に合わせる
        p.AddRule(LayoutRules.AlignParentRight); //親の右に合わせる
        p.TopMargin = margin;
        p.RightMargin = margin;
        _parentView.AddView(label5, p);
    }

    var label6 = new TextView(Context) { Text = "Six" };
    label6.SetBackgroundColor(Android.Graphics.Color.Argb(125, 0, 0, 0));
    label6.SetTextColor(Android.Graphics.Color.White);

    using (var p = new RelativeLayout.LayoutParams(LayoutParams.WrapContent, LayoutParams.WrapContent))
    {
        p.AddRule(LayoutRules.AlignParentBottom); //親の下に合わせる
        p.AddRule(LayoutRules.AlignParentRight);  //親の右に合わせる
        p.BottomMargin = margin;
        p.RightMargin = margin;
        _parentView.AddView(label6, p);
    }

    var label7 = new TextView(Context) { Text = "Seven" };
    label7.SetBackgroundColor(Android.Graphics.Color.Argb(125, 0, 0, 0));
    label7.SetTextColor(Android.Graphics.Color.White);

    using (var p = new RelativeLayout.LayoutParams(LayoutParams.WrapContent, LayoutParams.WrapContent))
    {
        p.AddRule(LayoutRules.AlignParentLeft);     //親の左に合わせる
        p.AddRule(LayoutRules.AlignParentBottom);   //親の下に合わせる
        p.LeftMargin = margin;
        p.BottomMargin = margin;
        _parentView.AddView(label7, p);
    }

    var label8 = new TextView(Context) { Text = "Eight" };
    label8.SetBackgroundColor(Android.Graphics.Color.Argb(125, 0, 0, 0));
    label8.SetTextColor(Android.Graphics.Color.White);

    using (var p = new RelativeLayout.LayoutParams(LayoutParams.WrapContent, LayoutParams.WrapContent))
    {
        p.AddRule(LayoutRules.CenterInParent);  //親の水平垂直ど真ん中に合わせる
        _parentView.AddView(label8, p);
    }
}

LinearLayoutの親をRelativeLayoutにして、そちらに重ねたい要素を追加していきます。

応用 いろいろ組み合わせる

iOS

5daecf2fdc98d92264f521538f43c11d.png

void Sample14()
{
    _parentView = new UIView();

    _container = new UIStackView {
        Axis = UILayoutConstraintAxis.Horizontal, //並べる方向
        Alignment = UIStackViewAlignment.Center, //垂直位置属性
        Distribution = UIStackViewDistribution.Fill, //余白分配設定
        Spacing = 6 //要素間のマージン
    };

    //レイアウトの余白設定
    _container.LayoutMargins = new UIEdgeInsets(6, 6, 6, 6);
    _container.LayoutMarginsRelativeArrangement = true;

    var image = new UIImageView(UIImage.FromBundle("icon.png"));
    image.ContentMode = UIViewContentMode.ScaleAspectFit;

    _container.AddArrangedSubview(image);

    image.WidthAnchor.ConstraintEqualTo(80).Active = true;
    image.HeightAnchor.ConstraintEqualTo(46).Active = true;

    var vStack = new UIStackView {
        Axis = UILayoutConstraintAxis.Vertical,
        Alignment = UIStackViewAlignment.Fill,
        Distribution = UIStackViewDistribution.Fill,
        Spacing = 6
    };

    var hStack = new UIStackView {
        Axis = UILayoutConstraintAxis.Horizontal,
        Alignment = UIStackViewAlignment.Center,
        Distribution = UIStackViewDistribution.Fill,
        Spacing = 6
    };

    var label1 = new UILabel { Text = "Xamarin Native Layout Sample TextTextTextTextText" };
    var label2 = new UILabel { Text = "******" };
    label1.Font = label1.Font.WithSize(16);
    label2.Font = label2.Font.WithSize(16);

    hStack.AddArrangedSubview(label1);
    hStack.AddArrangedSubview(label2);

    //余った領域を広げる優先度の設定(低いものが優先して拡大する)
    label1.SetContentHuggingPriority(1f, UILayoutConstraintAxis.Horizontal);
    label2.SetContentHuggingPriority(999f, UILayoutConstraintAxis.Horizontal);

    //縮まりやすさの設定(低いものが優先して縮まる)
    label1.SetContentCompressionResistancePriority(1f, UILayoutConstraintAxis.Horizontal);
    label2.SetContentCompressionResistancePriority(999f, UILayoutConstraintAxis.Horizontal);

    vStack.AddArrangedSubview(hStack);

    var label3 = new UILabel { Text = "複雑なレイアウトも入れ子にすることで実現可能です。Androidの方が簡単なのでiOS側からレイアウトを組んで行く方が良いかもしれません。" };
    label3.LineBreakMode = UILineBreakMode.CharacterWrap;
    label3.Lines = 2;
    label3.Font = label3.Font.WithSize(12);

    vStack.AddArrangedSubview(label3);

    hStack.SetContentHuggingPriority(999f, UILayoutConstraintAxis.Vertical);
    label3.SetContentHuggingPriority(1f, UILayoutConstraintAxis.Vertical);

    _container.AddArrangedSubview(vStack);

    image.SetContentHuggingPriority(999f, UILayoutConstraintAxis.Horizontal);
    vStack.SetContentHuggingPriority(1f, UILayoutConstraintAxis.Horizontal);

    image.SetContentCompressionResistancePriority(999f, UILayoutConstraintAxis.Horizontal);
    vStack.SetContentCompressionResistancePriority(1f, UILayoutConstraintAxis.Horizontal);

    _parentView.AddSubview(_container);
    _container.TranslatesAutoresizingMaskIntoConstraints = false;
    _container.TopAnchor.ConstraintEqualTo(_parentView.TopAnchor, 0).Active = true;
    _container.LeftAnchor.ConstraintEqualTo(_parentView.LeftAnchor, 0).Active = true;
    _container.BottomAnchor.ConstraintEqualTo(_parentView.BottomAnchor, 0).Active = true;
    _container.RightAnchor.ConstraintEqualTo(_parentView.RightAnchor, 0).Active = true;


    var label4 = new UILabel { Text = "Sample", BackgroundColor = UIColor.FromRGBA(0, 0, 0, 125), TextColor = UIColor.White };
    label4.Font = label4.Font.WithSize(12);

    _parentView.AddSubview(label4);

    label4.TranslatesAutoresizingMaskIntoConstraints = false;
    label4.TopAnchor.ConstraintEqualTo(_parentView.TopAnchor, 3).Active = true;
    label4.LeftAnchor.ConstraintEqualTo(_parentView.LeftAnchor, 3).Active = true;
}

Android

cea1d8df0ffa523bf007339094a59db1.png

void Sample14()
{
    //xmlリソースからレイアウトを呼び出す
    var view = LayoutInflater.FromContext(Context).Inflate(Resource.Layout.NativeLayout, null);
    _parentView = view as RelativeLayout;
}

すみません、コードはきついのでxmlで書きました。xmlからのインスタンス生成はこのようにします。

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <ImageView
        android:id="@+id/NativeLayoutImage"
        android:layout_width="80dp"
        android:layout_height="46dp"
        android:layout_alignParentLeft="true"
        android:layout_centerVertical="true"
        android:scaleType="center"
        android:src="@drawable/icon"
        android:layout_marginLeft="10dp"
        android:layout_marginRight="6dp" />
    <LinearLayout
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentEnd="true"
        android:layout_centerVertical="true"
        android:layout_toRightOf="@+id/NativeLayoutImage"
        android:layout_marginRight="10dp">
        <LinearLayout
            android:orientation="horizontal"
            android:layout_width="match_parent"
            android:layout_height="wrap_content">
            <TextView
                android:layout_width="wrap_content"
                android:width="0dp"
                android:layout_weight="1"
                android:layout_height="wrap_content"
                android:text="Xamarin Native Layout Sample Text Text Text Text Text Text Text Text Text"
                android:ellipsize="end"
                android:singleLine="true"
                android:layout_marginEnd="6dp"
                android:textSize="16sp"
                android:textColor="@android:color/black" />
            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="******"
                android:ellipsize="end"
                android:singleLine="true"
                android:textSize="16sp"
                android:textColor="@android:color/black" />
        </LinearLayout>
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="複雑なレイアウトも入れ子にすることで実現可能です。Androidの方が簡単なのでiOS側からレイアウトを組んで行く方が良いかもしれません。"
            android:ellipsize="end"
            android:maxLines="2"
            android:layout_marginTop="6dp"
            android:textSize="12sp"
            android:textColor="@android:color/black" />
    </LinearLayout>
    <TextView
        android:layout_alignParentTop="true"
        android:layout_alignParentLeft="true"
        android:layout_marginTop="3dp"
        android:layout_marginLeft="3dp"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="#80000000"
        android:textColor="#FFFFFF"
        android:text="Sample"
        android:textSize="12sp" />
</RelativeLayout>

おまけ UIStackViewのBackgroundColorを有効にする

UIStackViewは通常BackgroundColorが機能しません。なので親ViewのBackgroundColorなどで指定したりするんですが、それだと面倒くさいのでUIStackViewのサブクラスで対応した方が良いかもしれません。
StackOverflowからの移植です。これを使うとBackgroundColorの指定が可能になります。

// Apply BackgroundColor https://stackoverflow.com/questions/34868344/how-to-change-the-background-color-of-uistackview
public class UIStackViewEx : UIStackView {
    Lazy<CAShapeLayer> _backgroundLayer;

    public UIStackViewEx()
    {
        _backgroundLayer = new Lazy<CAShapeLayer>(() => {
            var layer = new CAShapeLayer();
            this.Layer.InsertSublayer(layer, 0);
            return layer;
        });
    }

    UIColor _backgroundColor;
    public override UIColor BackgroundColor
    {
        get
        {
            return _backgroundColor;
        }
        set
        {
            _backgroundColor = value;
            SetNeedsLayout();
        }
    }

    public override void LayoutSubviews()
    {

        base.LayoutSubviews();
        _backgroundLayer.Value.Path = UIBezierPath.FromRect(this.Bounds).CGPath;
        _backgroundLayer.Value.FillColor = BackgroundColor?.CGColor;

    }
}

おわりに

とりあえず思いつく限りのパターンを書いてみましたがいかがでしたでしょうか。
まぁほぼ自分が忘れるのでiOS/Androidを対にしたメモが必要だったので書いた感じですが(笑)、少しでもお役に立てれば幸いです。
ありがとうございました。

16
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
16
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?