Xamarin.Formsで折り返し可能で子要素を等幅できっちり配置できるWrapLayoutを作成しました。
Xamarin.Formsには標準では、WPFのWrapPanelのように端まで来たら折り返して配置するというようなLayoutは今のところ存在しません。
そんなわけでそういうカスタムレイアウトを「色しらべ」 の時に作成していて、今作成中のアプリでも使用しているので、なかなか活躍の場があるのでは?と思い、今回そのCustomLayoutを整備してみました。
作成したもの
- WrapLayout
- RepeatableWrapLayout
Nuget
Install-Package AiForms.Layouts
これはプラットフォームはいじってないのでPCLだけにインストールでOKです。
概要
WrapLayoutは子要素を追加すると、自動的に水平方向に配置していき、親の幅を超えそうなところで次の行に折り返して配置するレイアウトです。
RepeatableWrapLayoutはWrapLayoutをItemSource・DataTemplateに対応させたものです。
単純に並べていく機能のほかに、1行に配置したい個数を指定して均等になるように幅を自動調整して配置していく機能もあります。
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>
デモ
WrapLayoutの様子。#Xamarin.Forms pic.twitter.com/met8P2w245
— かむ (@muak_x) 2017年2月10日
元にしたコード
- stacklayout with horizontal orientation, how to wrap vertically? — Xamarin Forums
- XForms needs an ItemsControl — Xamarin Forums
コード解説
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でもご自由にお使いください。