個人開発中にドラッグアンドドロップを使用してドラッグのした要素をある対象の上にドロップすると要素が消えるというものを実装しようとしたのですがかなり苦戦したので備忘録として残しておきます。
下記のようなイメージです。

ドラッグアンドドロップ
まずDraggableの公式ドキュメントをみます。
Draggable: https://api.flutter.dev/flutter/widgets/Draggable-class.html
今回は初期のカウンターアプリのページにドラッグアンドドロップへのリンクを追加してページ遷移してドラッグアンドドロップの動作が確認できるようにします。
まずlibディレクトリの中にDragPage.dartファイルを作成します。ファイルを作成したらAppBarのみ記述しておきます。
lib/drag_page.dart
import 'package:flutter/material.dart';
class DragPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Draggable'),
),
);
}
}
次にmain.dartでDraggableページへのリンクを作成します。
lib/main.dart
import 'drag_page.dart';
...省略
TextButton(
style: ButtonStyle(
foregroundColor: MaterialStateProperty.all<Color>(Colors.blue),
),
onPressed: () async {
await Navigator.push(context,
MaterialPageRoute(builder: (context) => DragPage()));
},
child: Text('ドラッグページへ'),
),
「ドラッグページへ」のリンクをクリックするとAppBarのみ表示されるようになりました。
次はDraggableの公式と同じ実装をします。
lib/drag_page.dart
import 'package:flutter/material.dart';
class DragPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
int acceptedData = 0;
return Scaffold(
appBar: AppBar(
title: Text('Draggable'),
),
body: MyStatefulWidget(),
);
}
}
class MyStatefulWidget extends StatefulWidget {
const MyStatefulWidget({Key key}) : super(key: key);
@override
_MyStatefulWidgetState createState() => _MyStatefulWidgetState();
}
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
int acceptedData = 0;
@override
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Draggable<int>(
data: 10,
child: Container(
height: 100.0,
width: 100.0,
color: Colors.lightGreenAccent,
child: const Center(
child: Text('Draggable'),
),
),
feedback: Container(
color: Colors.deepOrange,
height: 100,
width: 100,
child: const Icon(Icons.directions_run),
),
childWhenDragging: Container(
height: 100.0,
width: 100.0,
color: Colors.pinkAccent,
child: const Center(
child: Text('Child When Dragging'),
),
),
),
DragTarget<int>(
builder: (
BuildContext context,
List<dynamic> accepted,
List<dynamic> rejected,
) {
return Container(
height: 100.0,
width: 100.0,
color: Colors.cyan,
child: Center(
child: Text('Value is updated to: $acceptedData'),
),
);
},
onAccept: (int data) {
setState(() {
acceptedData += data;
});
},
)
],
);
}
}
公式と同じようにドラッグするたびに値が10ずつ値が増えていく実装ができました。

Draggableのプロパティについて簡単に解説します。
data
ドロップされた時のデータです。今回は数値を定義していますが、文字列やオブジェクトも可能です。
child
デフォルトで表示されるウィジェットを記述します。後述するchildWhenDraggingがない場合はドラッグ中もchildで定義されたウィジェットが表示されます。
feedback
ドラッグ中に表示されるウィジェットを記述します。
childWhenDragging
ドラッグ中にchildの箇所に表示するウィジェットを記述します。
DragTagetのプロパティ
onAccept
ドロップされた時の処理を記述します。
続いてドロップした時に要素が消えるようにします。
状態管理のライブラリにはRiverpodを使用します。
riverpodの使用については公式のTODOのコードを参考に実装していきます。
riverpod: https://github.com/rrousselGit/river_pod/tree/master/examples/todos
まず、Memoクラスを作成します。
lib/memo.dart
import 'package:flutter_riverpod/flutter_riverpod.dart';
class Memo {
Memo({
this.content,
this.id,
this.done = false,
});
final String content;
final String id;
final bool done;
}
class MemoList extends StateNotifier<List<Memo>> {
MemoList(List<Memo> state) : super(state);
void remove(Memo target) {
state = state.where((memo) => memo.id != target.id).toList();
}
}
drag_page.dartの記述を修正します。
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'memo.dart';
final memoListProvider = StateNotifierProvider<MemoList, List<Memo>>((ref) {
return MemoList([
Memo(id: 'memo-1', content: 'memo1', done: false),
Memo(id: 'memo-2', content: 'memo2', done: false),
Memo(id: 'memo-3', content: 'memo3', done: false),
]);
});
final memos = Provider<List<Memo>>((ref) {
final memos = ref.watch(memoListProvider);
return memos;
});
class DragPage extends HookWidget {
const DragPage({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
final memoList = useProvider(memos);
final containerWidth = MediaQuery.of(context).size.width / 3;
return ProviderListener(
child: Scaffold(
appBar: AppBar(
title: Text('Draggableページ'),
),
body: Wrap(
children: <Widget>[
Container(
height: containerWidth,
width: MediaQuery.of(context).size.width,
child: Row(
children: [
for (var i = 0; i < memoList.length; i++) ...[
Draggable<Object>(
data: memoList[i],
child: Container(
height: containerWidth,
width: containerWidth,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Container(
color: Colors.lightGreenAccent,
child: Center(
child: Text(memoList[i].content),
),
),
),
),
feedback: Container(
color: Colors.deepOrange,
height: containerWidth,
width: containerWidth,
child: const Icon(Icons.directions_run),
),
childWhenDragging: Container(
height: containerWidth,
width: containerWidth,
color: Colors.pinkAccent,
child: const Center(
child: Text('Child When Dragging'),
),
),
),
],
],
),
),
DragTarget(
builder: (
BuildContext context,
List<dynamic> accepted,
List<dynamic> rejected,
) {
return Container(
height: MediaQuery.of(context).size.width / 2,
width: MediaQuery.of(context).size.width / 2,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Container(
color: Colors.cyan,
child: Center(
child: Text('category1'),
),
),
),
);
},
onAccept: (Object data) {
context.read(memoListProvider.notifier).remove(data);
},
),
],
),
),
);
}
}
以上でドロップした時にドラッグした要素が消えるようになりました。