kamulog

xamarin.formsのネタなど

Xamarin.Formsで折り返し可能で子要素を等幅できっちり配置できるWrapLayoutを作成しました。

Xamarin.Formsには標準では、WPFのWrapPanelのように端まで来たら折り返して配置するというようなLayoutは今のところ存在しません。

そんなわけでそういうカスタムレイアウトを「色しらべ」 の時に作成していて、今作成中のアプリでも使用しているので、なかなか活躍の場があるのでは?と思い、今回そのCustomLayoutを整備してみました。

作成したもの

github.com

  • WrapLayout
  • RepeatableWrapLayout

Nuget

www.nuget.org

Install-Package AiForms.Layouts

これはプラットフォームはいじってないのでPCLだけにインストールでOKです。

概要

WrapLayoutは子要素を追加すると、自動的に水平方向に配置していき、親の幅を超えそうなところで次の行に折り返して配置するレイアウトです。

RepeatableWrapLayoutはWrapLayoutをItemSource・DataTemplateに対応させたものです。

単純に並べていく機能のほかに、1行に配置したい個数を指定して均等になるように幅を自動調整して配置していく機能もあります。

f:id:kamusoft:20170210231431p:plain:w200

WrapLayoutプロパティ

  • Spacing
    • 子要素間の余白。
    • レイアウトの端には適用されません。端にも余白が欲しい時はPaddingを設定してください。
  • UniformColumns
    • 1行に置く子要素の数 (default 0)
    • 0の場合は子要素のWidthRequestに従います。
    • 1以上の場合は親の幅をこの数で割った幅が子要素の幅となります。
  • IsSquare
    • UniformColumns>0の時、高さを幅と同じにして正方形にするかどうか (default false)

RepeatableWrapLayoutプロパティ

  • ItemTapCommand
    • 子要素をタップしたときに発火するコマンド
  • ItemSource
    • ListViewとかでおなじみのやつ
  • ItemTemplate
    • ListViewとかでおなじみのやつ

Xaml使用例

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
        xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
        xmlns:l="clr-namespace:AiForms.Layouts;assembly=AiForms.Layouts"
        x:Class="Sample.Views.MainPage">
    <StackLayout>
        <ScrollView HorizontalOptions="FillAndExpand">
        <l:RepeatableWrapLayout
             ItemTapCommand="{Binding TapCommand}"
              ItemsSource="{Binding BoxList}"
              Spacing="3" UniformColumns="{Binding UniformColumns}"
              IsSquare="{Binding IsSquare}" >
            <l:RepeatableWrapLayout.ItemTemplate>
                <DataTemplate>
                    <StackLayout BackgroundColor="{Binding Color}" >
                        <Label
                            VerticalTextAlignment="Center" HorizontalTextAlignment="Center"
                            Text="{Binding Name}"  />
                    </StackLayout>
                </DataTemplate>
            </l:RepeatableWrapLayout.ItemTemplate>
        </l:RepeatableWrapLayout>
        </ScrollView>
    </StackLayout>
</ContentPage>

デモ

元にしたコード

コード解説

Layout<View>を継承して作成していくんですが、
えーと…OnMeasureとLayoutChildrenで領域サイズの計算と配置の処理がほぼすべてです(笑)

いや、冗談ではなく本当にそうでした。 なので解説するようなコードは無いんですよね。もし気になる場合はgithubのソースを参照してください。

一応それぞれで何をしているのかだけざっくり書いておきます。

OnMeasure

MarginやPaddingをさっぴいたLayoutのwidthとheightが入ってきます。 ここで実際に子要素が配置される場合のトータルのwidthとheightを計算してSizeRequestとして返却するようにします。

LayoutChildren

OnMeasureでSizeRequestした値がwidth,heightに入ってきます。x,yは開始座標。paddingが設定されていると0以外になります。

このx,yを起点として実際に子要素を配置していく処理を書きます。

結局OnMeasureとほぼ同じことをする必要があるので、計算処理は別メソッドに切り出しておくのが良いと思います。

苦労した箇所

均等に配置は計算しやすかったんでそんなに苦労しなかったんですが、可変要素の位置計算が大変でした。いや数学が得意な人なら楽勝なんでしょうけど。

実際アプリで使っていたのは均等の方だけで、可変の方は元ネタからほぼ変更してなかったんですが、今回の整備で試してみたらバグだらけだったので、そこはほぼ書き直しました(笑)

課題

要素を追加するたびLayoutChildrenが呼ばれて毎回描画しなおしてるっぽいので、その辺をなんとかしたいですね。

あとは現状水平方向のみなので、垂直方向にも対応できれば良いかも知れませんが、それを使うべき場面が全く想像できないので多分放置ですね。

おわりに

Layoutは計算が得意な人なら結構自由自在なんではないかと思いますので、挑戦してみると良いと思います。Native知識も不要ですしね。

今回作成したWrapLayoutですが、結構使える場面があると思うので良かったら、nugetでもgit forkでもご自由にお使いください。