ぽぴなび

知って感動した技術情報・生活情報や買ってよかったものの雑記です。

ドラッグ&ドロップで並べ替え可能(Reorderable)なGridViewを実装しようとしたときのメモ

ReorderableListViewは標準で実装されているがReorderableGridViewは現状ないので、pub.devでパッケージを調査。 関連パッケージとしては、以下3つのパッケージ(+1ソースコード)が見つかった。 (他にあったら教えてください )

パッケージ化はされていないようだが、順番が入れ替わる際のアニメーションがキレイなソースコード

GitHub - spkersten/sliver_draggables

それぞれ実際に使ってみて、気づいた点を書いていく。

まとめ

評価軸 説明
サンプルコード量 pubdevに乗っているソースコードの量
カスタマイズ性 メインのWidgetのパラメータの多さ
ドラッグ中の要素入れ替え ドラッグ中に、ドラッグ位置に応じて要素が入れ替わるかどうか
アニメーション ドラッグ中の要素入れ替えがアニメーション付きで行われるかどうか
オートスクロール ドラッグしている要素の位置が画面上端・下端に来たときに自動でスクロールされるかどうか
パッケージ サンプルコード量(pubdev) カスタマイズ性 ドラッグ中の要素入れ替え アニメーション オートスクロール
drag_and_drop_gridview ×
reorderableitemsview × ×
reorderables ※ ×
sliver_draggables ×

※ GridViewは未サポートだが、要素の幅を指定することでGridViewっぽく見せることが可能。常に正常に動くかは不明。

実装難易度的にはアニメーション>オートスクロールだと思うので、sliver_draggablesにオートスクロールを追加できるのがベストだと考える。 アニメーションやドラッグ中の要素移動はないが、動作が安定していそうなのはreorderableitemsview。 スクロールの不具合を直せるならdrag_and_drop_gridviewも選択肢に入ってくる。

drag_and_drop_gridview

まさにやりたいことを実現してくれているパッケージ。

  • GridViewと同じパラメータを扱っているため後から導入しやすい。
  • 並び替えたい要素をドラッグしている際中に、ドラッグ位置に応じて並び方が変わるサンプルがあるので、実装にもそんなに困らない。
  • ドラッグ中に、ドラッグ元となった要素を変えたり(childWhenDragging)、ドラッグ中だけドラッグしている要素を変えるためのフィードバック(feedback)を変えられるのでカスタマイズ性が高い。
  • 要素が並べ替わる際のアニメーションがない。
  • ドラッグ位置が上端or下端になったときのオートスクロールの挙動が怪しい。
  • 関数を指定できるプロパティ(onWillAcceptなど)に指定する関数の要件がパッと見わからないので、間違っていると謎のエラーに遭遇する。(例: onWillAcceptbool Function(int, int)を渡さないといけないが、Functionとしか書いてない)

要素が並べ替わる際のアニメーションはないが、onWillAcceptの実装によってはドラッグ中に並べ替わる様子を表現はできるので、スクロールの部分さえ直せればかなり良い。

reorderableitemsview

flutter_staggered_grid_viewという GridViewの各セルを自由な大きさにできるパッケージを並べ替え可能にしたパッケージ。各セルの大きさを同じにすればやりたいことができる。

  • ドラッグ位置が上端or下端になったときのオートスクロールは◎
  • ドラッグ開始のトリガーが長押しか普通のタップかどうかを選べる。(longPressToDrag)
  • onWillAcceptパラメータが指定できないので、ドラッグ中の処理を定義できない。
  • 上記故に、ドラッグ中にドラッグ位置に応じて各要素を並び替えられない。
  • 要素が入れ替わる際のアニメーションはない。

ドラッグ中に各要素が入れ替わる演出を気にしなければこれでOK。

reorderables

GridViewには対応していないが、ReorderableWrapWidgetを使って並べる要素の幅/高さをMediaQuery.of(context).size.width / 2,とかで指定すればそれっぽくはなる。

  • ドラッグ位置が上端or下端になったときのオートスクロール機能はない。
  • ドラッグ開始のトリガーが長押しか普通のタップかどうかを選べる。(longPressToDrag)
  • ドラッグ中に各要素が入れ替わるアニメーションがある。
  • トリッキーな方法でGridっぽくしているため、不安がある

コード例

  @override
  Widget build(BuildContext context) {
    List<Widget> _tiles = List.generate(
      imageUris.length,
      (int index) => Container(
        width: MediaQuery.of(context).size.width / 2,
        height: MediaQuery.of(context).size.width / 2,
        child: Card(
          child: Image.network(
            imageUris[index],
            fit: BoxFit.cover,
          ),
        ),
      ),
    );
    return Scaffold(
      appBar: AppBar(),
      body: ListView(
        controller: _scrollController,
        children: [
          ReorderableWrap(
            children: _tiles,
            onReorder: (int oldIndex, int newIndex) {
              print('$newIndex');
              setState(() {
                final String oldUri = imageUris.removeAt(oldIndex);
                imageUris.insert(newIndex, oldUri);
              });
            },
          ),
        ],
      ),
    );
  }

sliver_draggables

下記のIssueから見つけたソースコードRenderSliverGrid assert on SliverGridLayout too strict · Issue #28056 · flutter/flutter · GitHub

  • ドラッグ中のアニメーションは完璧。
  • オートスクロールはない。
  • SliverLongPressReorderableGridに渡せるパラメーターが少ないのでカスタマイズ性は低そう。

スクロールなしで収まる量の要素を並び替えるのであれば、これが一番キレイ。