XamarinでListのような項目の順序をUIで変更するようなことができないか考えていた。
上の図はiOSのメーラーの表示なのだが、右側の三のような表示のところをドラッグすると、順番が入れ替わるようなやつ。
検索したら、以下のページがヒットした。
こちらはiOSのものだけど、このページの次にAndroid用のが掲載されている。
iOSのものはネイティブのUIを有効化する方法で、Androidの方は独自でUIを構築している。
Xamarin.Formsのみを純粋に使用しているのはこちらの方。
今回、githubの方をベースにもうちょっとブラッシュアップして作ってみた。
ベースとなるのは、Xamarin.Forms-SwipeViewと同じように、テンプレートで作成したプロジェクト。
上のGithub中のxamarin1フォルダにあるやつでDrag&Dropで順序指定ができるのは、ItemList.xamlがビューでItemsViewModel.csとItemViewModel.csがビューモデル。
基本的な考え方
表示する項目をCollectionViewとStackLayoutで構築するのは、テンプレートと同じ。
このStackLayoutにDragGestureRecognizerとDropGestureRecognizerを指定することで、項目を表示するStackLayoutに対してDrag&Drop可能なようにしている。
実際のDrag&Dropの処理と、順番の入れ替えなどの処理はItemsViewModel.cs側で行っている。
行っている内容を簡単に書くと以下の様になる。
Drag開始(DragStart/OnDragStart)
ドラッグするアイテムを一応記憶しておく。
また管理しているアイテムのIsBeingDraggedをTrueにしているが、これはDragアイテムの色付けのところで説明。
ドラッグ移動(DragOver/OnDragOver)
この部分が、処理の肝になるところ。
ドラッグ中のアイテム(自分自身)ではない場合に、下にあるアイテムの前後にドラッグ中のアイテムを配置するという処理になっている。
今回参考にした上のソースでは、下にあるアイテムの次にドラッグ中のアイテムを挿入するという形にしていたため、ちょっと違和感があるような動きになっている。
ドラッグ中のアイテムを下にあるアイテムの上下どちらに移動するかは、ドラッグ中のアイテムと下のアイテムの位置関係で決定するようにした。
ドラッグ中のアイテムをItem3、下のアイテムがItem2の以下の様な関係の場合、Item3はItem2の上に挿入するようにする。
次にドラッグ中のアイテムをItem2、下のアイテムがItem3の以下の様な関係は、Item2はItem3の下に挿入するようにする。
これで、思ったような移動をすることができた。
ここでちょっと問題が出たのがコマンド呼び出しの頻度。
1度目のDragOverコマンドで上の入れ替えの処理を行うのだが、キューにイベント詰まれているらしく、同じアイテムでDragOverコマンドがどんどん来てしまう。
そのまま処理を行うとリストの操作が何度も行われるため、画面の更新が頻繁に発生する事態となった。
そこで、1度イベント処理を行ったら無駄なリストの更新処理を行わない処理を入れた。
この処理は、DragOverが来たらそのアイテムを覚えておき、同一のアイテムに対しては処理をスキップするといったもの。
このアイテムはDragLeave/OnDragLeaveでクリアするようにしている。
ドロップ処理(Drop/OnDrop, DragEnd/OnDragEnd)
ドラッグ中のアイテムをクリアする。
リストの更新自体はDragOver/OnDragOverで行っているので、ここで何かを行う必要はないが、今回のプログラムでは、実際のモデルのリスト管理はMockDataStore.csで行っているので、そちら側をメンテナンスする処理を入れる必要があるかも。
追加で行っている処理
Drag&Dropで順番を入れ替える処理については前の段階の実装のみで問題はないが、追加で行っている処理について説明をする。
Dragアイテムの色付け
特に何もしないと、表示されているアイテムをそのままドラッグすることになる。
そうすると、何をドラッグしているのかわかりづらくなる。この対応をするため、IsBeingDraggedというフラグを設け、それによりStackLayoutのバックグラウンド色を変更するようにしている。
この処理はhttps://github.com/xamcat/grouped-list-reorderでも行っているもの。
私が作成したソースでは、元のモデル側データItemの構造を辺こするのが嫌だったので、それを保持しIsBeingDraggedを追加したItemViewModel.csで管理するようにしている。
xamlのView側はConverterを介しBool値を色の値に変換しているようにしている。
DragOverのDragEventArgs処理
これはView側のC#実装の処理になる。
Androidでは問題なかったのだが、UWPでドラッグを実行した時「コピー」という文字が表示された。
コピーというわけではないので、この表示を消したかったのだがそれはできないようだ。
代替で「コピー」ではない文字にしたかったので、試しにDragEventArgs.AcceptedOperationをNoneにしたら、以下の表示になったのでそれでいいかと、実装だけはしておいた。
コメント