😀
[Flutter] 各要素を並び替えできる ListView & GridView を使ってみる
成果物
デモ
ソースコード
要素を並び替えられる ListView
デフォルトで入っているReorderableListView
&SliverReorderableList
を使います。
ReorderableListView の使い方
- 基本的には
ListView
と同じ使い方 -
itemBuilder
には並べたい要素を生成する関数を指定します。 -
ListView
と異なる点としては、各要素にユニークなKey
を指定する必要があることです。 -
onReorder
には並び替えが完了したとき(ドラッグしている要素を放したとき)に行う処理を記載します。 -
proxyDecorator
は任意に指定可能で、ドラッグしている要素をデコレーションできます(今回のコードでは、ドラッグする要素を半透明にしています。
reordable_list_view_page.dart
class ReorderableListViewPage extends ConsumerWidget {
const ReorderableListViewPage({Key? key}) : super(key: key);
Widget build(BuildContext context, WidgetRef ref) {
final items = ref.watch(itemsProvider);
return Scaffold(
appBar: AppBar(),
body: ReorderableListView.builder(
itemBuilder: (_, index) => ItemTile(
items[index],
key: Key('$index'), // 各要素にユニークなKeyをつける必要がある
),
itemCount: items.length,
onReorder: (int oldIndex, int newIndex) {
_onReorder(items, oldIndex, newIndex);
},
proxyDecorator: (widget, _, __) {
return Opacity(opacity: 0.5, child: widget);
},
),
);
}
void _onReorder(List<Item> items, int oldIndex, int newIndex) {
if (oldIndex < newIndex) {
newIndex -= 1;
}
items.insert(newIndex, items.removeAt(oldIndex));
}
}
ReorderableListView(children: [])
という書き方もできます。
SliverReorderableList の使い方
-
ReorderableListView
と異なり、並べたい要素をReorderableDragStartListener
またはReorderableDelayedDragStartListener
でラップする必要があります。 - その影響で、ユニークなkeyを付与する対処も異なるので注意。
itemBuilder: (_, index) => ReorderableDelayedDragStartListener(
index: index,
// ItemTileではなく、ReorderableDelayedDragStartListenerにkeyを付与
key: Key('$index'),
child: ItemTile(items[index]),
),
-
ReorderableDragStartListener
とReorderableDelayedDragStartListener
の違いはドラッグ開始までのタップ時間。ReorderableDragStartListener
はほぼタップした瞬間にドラッグが始まりますが、ReorderableDelayedDragStartListener
は長押ししたらドラッグが始まります。 -
ReorderableDragStartListener
は画面全体のスクロールがしづらくなってしまうため、要素が多くスクロールが必要な場合はReorderableDelayedDragStartListener
を使うのがおすすめです。 -
ReorderableListView
と同様、proxyDecorator
を指定することでドラッグ中の要素をデコレーションできます。
class SliverReorderableListPage extends ConsumerWidget {
const SliverReorderableListPage({Key? key}) : super(key: key);
Widget build(BuildContext context, WidgetRef ref) {
final items = ref.watch(itemsProvider);
return Scaffold(
body: CustomScrollView(
slivers: [
const SliverAppBar(),
SliverReorderableList(
// ReorderableListViewと異なり、
// 各要素を ReorderableDragStartListener または ReorderableDelayedDragStartListener
// でラップする必要がある。
// その影響もあり、keyを付与する対象は ReorderableDragStartListener または
// ReorderableDragStartListenerになるので注意。
//
// ReorderableDragStartListenerはタップで移動が開始されるが、
// ReorderableDelayedDragStartListenerはロングタップで移動開始となる。
// タップで要素の移動が始まってしまうと
// スクロールがしづらくなるので ReorderableDelayedDragStartListener のほうがいい
itemBuilder: (_, index) => ReorderableDelayedDragStartListener(
index: index,
key: Key('$index'),
child: ItemTile(items[index]),
),
itemCount: items.length,
onReorder: (int oldIndex, int newIndex) {
_onReorder(items, oldIndex, newIndex);
},
proxyDecorator: (widget, _, __) {
return Opacity(opacity: 0.5, child: widget);
},
),
],
),
);
}
void _onReorder(List<Item> items, int oldIndex, int newIndex) {
if (oldIndex < newIndex) {
newIndex -= 1;
}
items.insert(newIndex, items.removeAt(oldIndex));
}
}
並べ替えできるGridView
以下のパッケージを使いました。
使い方もReorderableListView
とほとんど同じで、非公式とは思えないほど使いやすかったです。
Sliver対応のSliverReorderableGrid
クラスもあります。
ReorderableGridView の使い方
- ほとんど
ReorderableListView
と同じ。 -
itemBuilder
に並べたい要素を返す関数を指定し、各要素にはユニークなkey
を指定します。 - コードにはないですが、こちらも
proxyDecorator
を指定することでドラッグ中の要素の見た目を変えることができます。 -
gridDelegate
にはGridView
でおなじみのSliverGridDelegateWithFixedCrossAxisCount
を指定。(他にもあった気がします) -
ReorderableListView
と同じく、.builder
を使わない書き方もOK。
class ReorderableGridViewSample extends ConsumerWidget {
const ReorderableGridViewSample({Key? key}) : super(key: key);
Widget build(BuildContext context, WidgetRef ref) {
final items = ref.watch(itemsProvider);
return Scaffold(
appBar: AppBar(),
body: ReorderableGridView.builder(
itemBuilder: (_, index) => ItemCard(
items[index],
key: Key('$index'), // 各要素にユニークなKeyをつける必要がある
),
itemCount: items.length,
onReorder: (int oldIndex, int newIndex) =>
_onReorder(items, oldIndex, newIndex),
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: kIsWeb ? 4 : 2,
),
),
);
}
void _onReorder(List<Item> items, int oldIndex, int newIndex) {
final item = items.removeAt(oldIndex);
items.insert(newIndex, item);
}
}
SliverReorderableGrid の使い方
- こちらもほぼ
SliverReorderableList
と同じ。すごい。 - ドラッグリスナーとしては
ReorderableGridDragStartListener
またはReorderableGridDelayedDragStartListener
が用意されており、各要素をいずれかでラップする。 - ユニークな
key
は並べたい要素自体(ここではItemCard
)ではなく上記のいずれかのリスナーに付ける必要があるので注意。
class SliverReorderableGridPage extends ConsumerWidget {
const SliverReorderableGridPage({Key? key}) : super(key: key);
Widget build(BuildContext context, WidgetRef ref) {
final items = ref.watch(itemsProvider);
return Scaffold(
body: CustomScrollView(
slivers: [
const SliverAppBar(),
SliverReorderableGrid(
// SliverReorderableListと同様、こちらも各要素を
// ReorderableGridDragStartListener または ReorderableGridDelayedDragStartListenerで
// ラップする必要がある。
itemBuilder: (_, index) => ReorderableGridDelayedDragStartListener(
index: index,
key: Key('$index'),
child: ItemCard(
items[index],
key: Key('$index'), // 各要素にユニークなKeyをつける必要がある
),
),
itemCount: items.length,
onReorder: (int oldIndex, int newIndex) =>
_onReorder(items, oldIndex, newIndex),
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: kIsWeb ? 4 : 2,
),
proxyDecorator: (widget, _, __) {
return Opacity(opacity: 0.5, child: widget);
},
),
],
),
);
}
void _onReorder(List<Item> items, int oldIndex, int newIndex) {
final item = items.removeAt(oldIndex);
items.insert(newIndex, item);
}
}
不具合?
- そんなに大したことではありませんが、ドラッグ中に元の位置に戻せないです(
v1.0.3
時点)。
Discussion