CustomRenderer等でNativeのLayoutを使ってiOS/Android両方で同じようなLayoutにするコーディングのサンプルを紹介します。サンプルの環境ではFormsを使っていますが、Nativeでも同じようにできると思います。
使用するLayoutはiOSはUIStackView + (必要に応じて)Constraint、AndroidはLinearLayout + (必要に応じて)RelativeLayoutを使います。
なお、UIStackViewよりもLinearLayoutの方が自由度が高いのでこのサンプルでは、UIStackViewをベースとして比較します。
諸注意
- 水平方向のみのレイアウトがメインです。垂直方向はほぼ同じなのでここでは扱いません。
- レイアウトの背景はわかりやすくするために薄いグレーにしています。
iOS
- _label1〜3はUILabelです。
- UIStackView自体の細かい説明はしません。詳しい説明以下の記事ががすごくわかりやすくて良いです。
Android
- サンプル中に出てくる
Context
はクラスのメンバにあるものを使っています。 -
Context.ToPixels(6)
のようなToPixelsはFormsの拡張メソッドでdpをpxに変換するものです。Native単体の場合は変換処理を行うか、xmlでdp指定してください。 - _label1〜3はTextViewです。
- コードの方が対比しやすいのでコードでのサンプルですが、実際はxmlで記述する方が良いと思います。
ソース一式
最後の要素に余白を割り当てる 各要素は垂直中央揃え
iOS
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
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
//余った領域を広げる優先度の設定(低いものが優先して拡大する)
_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
要素が縮められることはないので何もする必要はありません。
余白を振り分けない
iOS
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
Weightを指定せずにWrapContentにすると、そうなります。
要素を右寄せっぽくする
iOS
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
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
//略
_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
//略
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
void Sample6()
{
_container = new UIStackView {
Axis = UILayoutConstraintAxis.Horizontal, //並べる方向
Alignment = UIStackViewAlignment.Center, //垂直位置属性
Distribution = UIStackViewDistribution.FillEqually, //余白分配設定 均等
};
//略
}
均等に配置するにはDistributionをFillEquallyにします。
Android
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
void Sample7()
{
_container = new UIStackView {
Axis = UILayoutConstraintAxis.Horizontal, //並べる方向
Alignment = UIStackViewAlignment.Center, //垂直位置属性
Distribution = UIStackViewDistribution.FillEqually, //余白分配設定 均等
Spacing = 6 //要素間のマージン
};
//略
}
UIStackViewのSpacingを設定します。
Android
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
void Sample8()
{
_container = new UIStackView {
Axis = UILayoutConstraintAxis.Horizontal, //並べる方向
Alignment = UIStackViewAlignment.Fill, //垂直位置属性
Distribution = UIStackViewDistribution.FillEqually, //余白分配設定 均等
Spacing = 6 //要素間のマージン
};
//略
}
UIStackViewのAlignmentをFillにします。
Android
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
void Sample9()
{
//略
//レイアウトの余白設定
_container.LayoutMargins = new UIEdgeInsets(6, 6, 6, 6);
_container.LayoutMarginsRelativeArrangement = true;
}
UIStackViewのLayoutMarginsを設定しLayoutMarginsRelativeArrangementをtrueにします。
Android
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
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
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
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
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
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
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
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
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
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
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を対にしたメモが必要だったので書いた感じですが(笑)、少しでもお役に立てれば幸いです。
ありがとうございました。