Flutterのdrag_and_drop_listsで並び替え可能かつ開閉可能かつ編集可能なリストを作成する

この記事は2023/07/31に作成されました。
作成日:2023/07/31
最終更新日:2023/07/31
環境
※動作確認はAndroidでのみ行いました。
drag_and_drop_lists:0.3.3
flutter:3.7.10
はじめに
単純にTextだけのDragAndDropItemの場合はデモ通りでできたのですが、リストをカスタマイズする際に少し手間取ったので成功したパターンを共有します。
今回は、
DragAndDropListExpansionに
・ExpansionIconはそのままに、dragHandleの作成
・他機能用ボタンの追加
childに
・dragHandleの作成
・他機能用ボタンの追加
をします。
デフォルトのリスト
まず、以下はデモに記載のあるリストです。
※必要な部分のみ抜粋しています。
DragAndDropLists(
        children: List.generate(_lists.length, (index) => _buildList(index)),
        onItemReorder: _onItemReorder,
        onListReorder: _onListReorder,
)
_buildList(int outerIndex) {
    var innerList = _lists[outerIndex];
    return DragAndDropListExpansion(
      title: Text('List ${innerList.name}'),
      subtitle: Text('Subtitle ${innerList.name}'),
      leading: const Icon(Icons.ac_unit),
      children: List.generate(innerList.children.length,
          (index) => _buildItem(innerList.children[index])),
      listKey: ObjectKey(innerList),
    );
  }
_buildItem(String item) {
    return DragAndDropItem(
      child: ListTile(
        title: Text(item),
      ),
    );
  }まずは、親のリストを変更していきます。
親:ExpansionIconはそのままに、dragHandleの作成
DragAndDropListsにlistDragHandleを追加します。
DragAndDropLists(
      ...
      listDragHandle: const DragHandle(
       child: 
       Icon(
            Icons.menu,
            color: Colors.white,
          ),
      ),
    );これで、Iconの表示はできました。しかし、位置がExpansionIconとかぶってしまっています。

このままでは操作ができないので、dragHandleを右側に、ExpansionIconを少し左寄りにします。
まずは、dragHandleの位置をPaddingで調節します
listDragHandle: const DragHandle(
        verticalAlignment: DragHandleVerticalAlignment.top,
        child: Padding(
          padding: EdgeInsets.only(right: 10, top: 18),
          child: Icon(
            Icons.menu,
            color: Colors.white,
          ),
        ),
      ),次に、ExpansionIconの位置を調節します。
_buildList内のDragAndDropListExpansionにtrailingを追加します。
_buildList(int outerIndex) {
    return DragAndDropListExpansion(
      ...
      trailing: Container(
        padding: EdgeInsets.only(right: 20),
        child: Icon(
          listItem.isExpanded
              ? Icons.keyboard_arrow_up
              : Icons.keyboard_arrow_down,
          color: Colors.white,
        ),
      ),
    );
  }ContainerとPaddingで位置を調節しました。
以上で、ExpansionIconはそのままに、dragHandleの作成することができました。
親:他機能用ボタンの追加
次に、DragAndDropListExpansionに他の機能を持ったボタンを追加してみます。
今回追加するボタンはシンプルに、Titleの内容を編集できるボタンにします。
Titleの横にボタンを付けたいので、TitleのTextとその横のアイコン用に、Flexで幅をとります。
DragAndDropListExpansion(
      title: Row(
        children: [
          Expanded(
            flex: 75,
            child: _isEditing ? 
             TextFormField(
                style: TextStyle(
                    color: Colors.white,
                    fontWeight: FontWeight.bold
                ),
              )
             :Text(
              listItem.name,
              style: TextStyle(
                color: Colors.white,
                fontWeight: FontWeight.bold,
              ),
              softWrap: true,
            ),
          ),
          Expanded(
            flex: 25,
            child: Transform.translate(
              offset: Offset(20, 0),
              child: IconButton(
                onPressed: (){
                  setState(() {
                    _isEditing = true;
                  });
                },
                icon: Icon(
                    Icons.edit,
                    color: Colors.white
                ),
              ),
            ),
          )
        ],
      ),Icons.editのonPressedで_isEditingをトグルし、_isEditingの結果によりTextかTextFieldかを出し分けます。
このようにして、機能をもったボタンを作成することができます。

これで、親のカスタマイズは完成です。
子:dragHandleの作成
次に、子のカスタマイズに入ります。
最初に、dragHandleを付けます。
親にdragHandleをつけたときと同様に、大元のDragAndDropListsに追加します。
私はここで、Paddingで位置の微調整をしました。
DragAndDropLists(
      ...
      itemDragHandle: const DragHandle(
        child: Padding(
          padding: EdgeInsets.only(right: 10),
          child: Icon(
            Icons.menu,
            color: MyColors.text,
          ),
        ),
      ),
    );子:他機能用ボタンの追加
次に、親と同じようにリストのTextを編集できるボタンを追加します。
ここでは、編集ボタンを押したら、2列と1列の2行のTextをTextFieldにするようにしています。
DragAndDropItem(
      child: Container(
        child: Row(
          children: [
            SizedBox(
              width: MediaQuery.of(context).size.width,
              child: Container(
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Row(
                      children: [
                        Container(
                          padding: EdgeInsets.only(right: 10),
                          child: _isEditing 
                           ? TextFormField(
                        style: TextStyle(
                           color: Colors.white,
                           fontWeight: FontWeight.bold
                        ),
                      )
                           :Text(
                            childList.name,
                            overflow: TextOverflow.visible,
                            softWrap: true,
                            style: TextStyle(
                              fontWeight: FontWeight.bold,
                            ),
                          ),
                        ),
                        SizedBox(
                          width: MediaQuery.of(context).size.width * 0.1,
                          child: IconButton(
                            onPressed: () {
                              setState(() {
                                _isEditing = true;
                                //その他処理
                              });
                            },
                            icon: const Icon(Icons.edit),
                          ),
                        ),
                      ],
                    ),
                    _isEditing 
                     ? TextFormField(
                     style: TextStyle(
                        color: Colors.white,
                        fontWeight: FontWeight.bold
                     ),
                   )
                     :Text(
                        childList.memo2,
                        overflow: TextOverflow.visible,
                        softWrap: true,
                      ),
                  ],
                ),
              ),
            ),
          ],
        ),2行にした関係で少し長くなってしまいましたが、このように行数や列数を増やしても対応できます。
また、コード例ではControllerを省いていますが、childList.memo2の値をもとから入れたい場合などにはControllerを作成して設定することで、実装が可能です。
以上で、並び替え可能かつ開閉可能かつ編集可能なリストを作成することができました。

まとめ
FlutterのExpansionTileでならできることが、DragAndDropListだとできないことも多かったので結構苦戦しました。
このようにどちらの機能も存在しているのではなく、どこかのボタンで切り替えて、デフォルトはExpansionTile、ボタンタップでDragAndDropにすればもう少し楽にできたかもしれませんが・・・。
今回のように開閉できて並び替えができるリストは結構需要があると思うので、誰かの参考になればと思います。
プログラマー/A.A
                 
              
 
   
  
 
  
 
  