shuhelohelo’s blog

Xamarin.Forms多めです.

Xamarin.Formsでコントロールのサイズを縦横比で指定する

コントロールのサイズを縦,横それぞれにピクセルの値を指定するのではなく,縦を決めれば自動的に横が縦の2分の1のサイズになるようにしたい,と思いました.例えばです.

名刺と同じ縦横比のカード風コントロールを作成するとします.

日本の名刺の一般的なサイズは横×縦=91mm × 55mmですので,この比率に合わせてFrameコントロールWidthRequestHeightRequestという2つのプロパティにサイズを指定すれば,名刺らしいコントロールが作れることになります.

しかし,少し調整するたびにこの比率を計算するのは手間です.縦横比は固定なのだから,横サイズを決めたら縦サイズが決まってほしいものです.

考えて思いついたのが,以下の2つです.もっと色々あるはずですが,できるだけ簡単に済ませたいのです.

x:Referenceを使う

データバインディングのバインド先としてコントロールのプロパティを参照するものです.

コントロールに名前(x:Name属性)をつけておき,その名前を使ってプロパティを参照します.

以下の例ではButtonAと名前がつけられたコントロールの横幅(WidthRequest)を,ButtonBのWidthRequestプロパティで参照しています.

        <Button
            x:Name="ButtonA"
            HorizontalOptions="Center"
            Text="ButtonA"
            VerticalOptions="Center"
            WidthRequest="100" />

        <Button
            x:Name="ButtonB"
            HorizontalOptions="Center"
            Text="ButtonB"
            VerticalOptions="Center"
            WidthRequest="{Binding Source={x:Reference ButtonA}, Path=WidthRequest}" /><!-- これ -->

このように幅が同じボタンができます. f:id:shuhelohelo:20200219172406p:plain

もちろん自分のプロパティを参照することもできます.

以下のように高さ(HeightRequest)に自分のWidthRequestを参照するように指定することができます.

        <Button
            x:Name="ButtonC"
            HeightRequest="{Binding Source={x:Reference ButtonC}, Path=Width}"
            HorizontalOptions="Center"
            Text="ButtonC"
            VerticalOptions="Center"
            WidthRequest="100" />

このように正方形のボタンになります. f:id:shuhelohelo:20200219172645p:plain

他のプロパティの値をバインドできることがわかりましたが,本来の目的である比率で指定することはXAMLだけではできないようです.

やるとしたらConverterを使って以下のようにする必要があります.

ticktack.hatenablog.jp

上記記事を参考にしてコンバータを作ります.とてもわかり易い内容でした.ありがとうございます. こちらの記事はXAMLから倍率をパラメータとしてConverterに渡すことについても書かれていて,IValueConverterについて詳しく学べると思います.

バインドされた値を2倍にする単純なコンバーターです.

    class AspectRatioConverter : IValueConverter,IMarkupExtension
    {
        //バインドされた値に処理を加えて返す.
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            //値を2倍にして返す
            return (double)value*2;
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }

        public object ProvideValue(IServiceProvider serviceProvider)
        {
            return this;
        }
    }

このコンバーターを使ってXAML側でこんなボタンを配置します.

        <Button
            x:Name="ButtonD"
            HeightRequest="{Binding Source={x:Reference ButtonD}, Path=WidthRequest, Converter={converter:AspectRatioConverter}}"
            HorizontalOptions="Center"
            Text="ButtonD"
            VerticalOptions="Center"
            WidthRequest="100" />

ButtonDは高さが幅の2倍ある縦長のボタンになります. f:id:shuhelohelo:20200219180546p:plain

ということで,コンバーター内の倍率を名刺の比率に変更すれば,名刺比率のコントロールになります.

今回のサンプルでは横(指定)に対して縦(比率で自動計算)というものなので,わかりにくくて申し訳ありませんが横縦比=0.6043を使います.

f:id:shuhelohelo:20200219181209p:plain

RelativeLayoutを使う

次に思いついたのがRelativeLayoutを使う方法です.

RelativeLayoutコントロールの中では,コントロールの位置,サイズに対して相対的な指定ができます.

例えば,「コントロールAの横幅をコントロールBの横幅の2倍にする」や「コントロールAの位置をコントロールBの位置から右へ30,下へ50移動した位置にする」といった相対的なレイアウトができます.

しかしながら,これは自身のプロパティを参照することはできないようです.

    <RelativeLayout Padding="0" BackgroundColor="Pink">
        <Button
            x:Name="ButtonA"
            HorizontalOptions="Center"
            Text="ButtonA"
            VerticalOptions="Center"
            WidthRequest="100" />
        <Button
            x:Name="ButtonE"
            RelativeLayout.HeightConstraint="{ConstraintExpression Type=RelativeToView,
                                                                   ElementName=ButtonE,
                                                                   Property=Width,
                                                                   Factor=0.6043}"
            Text="ButtonE"
            WidthRequest="250" />
    </RelativeLayout>

参照先のコントロールとしてElementNameに自分自身を指定すると,以下のメッセージとともに例外が発生します.

Constraints as specified contain an unsolvable loop

残念.

結論

Converterを使うのが良いということかなぁ.

ソースコード

github.com