Flutter DraggableとRiverpodを組み合わせる

Flutter DraggableとRiverpodを組み合わせる

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

下記のようなイメージです。

ドラッグアンドドロップ

まず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);
              },
            ),
          ],
        ),
      ),
    );
  }
}

以上でドロップした時にドラッグした要素が消えるようになりました。

Flutterカテゴリの最新記事