【Flutter】Draggableでスムーズなドラッグ&ドロップ作成

対象者

  • Flutterを使ったアプリ開発経験があり、ドラッグアンドドロップ機能を取り入れたい方
  • Draggableウィジェットを理解し、実装方法やカスタマイズ方法を学びたい方
  • 自分のFlutter開発スキルを向上させ、評価やキャリアアップを目指す方

はじめに

Flutterを使ったアプリ開発において、ドラッグアンドドロップ機能は、ユーザーにとって直感的な操作感をもたらし、アプリの使いやすさを向上させます。しかしながら、その実装方法やカスタマイズ方法については、意外と知られていないことも多く、開発者にとっても悩みの種です。

この記事では、まずDraggableウィジェットの基本概念を解説し、その後実装方法やカスタマイズ方法について具体的な実例を交えながら詳しく解説しています。さらに、LongPressDraggableウィジェットやPositionedウィジェットとの組み合わせによる応用例も紹介していますので、幅広いスキルアップが期待できます。

この記事を読むことで、Flutter開発者はDraggableウィジェットを理解し、より高度なドラッグアンドドロップ機能を実現できるようになります。また、実用的なアプリ開発における様々なカスタマイズ方法を学ぶことで、開発者としての評価やキャリアアップにも繋がるでしょう。

あなたも是非、この記事を読んで、Flutterを使ったアプリ開発でドラッグアンドドロップ機能を取り入れてみてください。それにより、アプリのユーザビリティが向上し、ユーザーの満足度を高めることができます。さあ、あなたのアプリ開発を次のレベルへと引き上げましょう。

Draggableウィジェットの基本

Draggableウィジェットの基本概念

Draggableウィジェットは、ユーザーがアイテムをドラッグ&ドロップで操作できるようにするためのウィジェットです。これにより、ユーザーが指でアイテムをドラッグして、別の場所にドロップすることができます。Draggableウィジェットは、ドラッグジェスチャーの開始を認識すると、画面上で指を追跡するフィードバックウィジェットを表示します。ユーザーがDragTargetの上で指を離すと、そのターゲットはDraggableが持つデータを受け取ります。

childとfeedback

Draggableウィジェットには、主に2つの重要なパラメータがあります。それがchildとfeedbackです。childはドラッグできる要素そのもので、feedbackはドラッグ中に表示される要素の見え方を表します。この2つのパラメータを使って、ドラッグ&ドロップの挙動を実現しています。

例えば、次のようなソースコードで、ドラッグできるアイコンを作成できます。

Draggable(
  child: Icon(Icons.drag_handle),
  feedback: Icon(Icons.drag_handle, color: Colors.blue),
)

DragTargetとの連携

Draggableウィジェットは、DragTargetというウィジェットと連携することで、ドロップ先の領域を作成し、その領域に対してデータを受け渡すことができます。DragTargetは、ドロップされるデータを受け入れるかどうかを判断し、受け入れた場合は、そのデータを利用して何らかの処理を行うことができます。

次の例では、ドラッグされたアイテムがDragTargetにドロップされた際に、そのデータを受け取り、リストに追加する処理を行っています。

DragTarget<String>(
  onAccept: (data) {
    setState(() {
      _list.add(data);
    });
  },
  builder: (BuildContext context, List<String> candidateData, List<dynamic> rejectedData) {
    return Container(
      height: 100,
      width: 100,
      color: Colors.blue,
      child: Center(child: Text("Drop here")),
    );
  },
)

Draggableウィジェットの活用方法

この記事では、Draggableウィジェットの基本概念から応用までを解説しました。Draggableウィジェットは、アプリケーションのユーザビリティを向上させる強力なツールであり、以下の点が特徴となっています。

  • ドラッグアンドドロップ機能の簡単な実装
  • カスタマイズ可能なドラッグ中の表示要素
  • 他のウィジェットとの組み合わせで柔軟な配置が可能

Draggableウィジェットの実装方法

Draggableウィジェットの基本的な実装

Draggableウィジェットの基本的な実装では、ドラッグできる要素を作成し、その要素をドラッグ&ドロップすることができます。以下の例では、簡単なテキストウィジェットをドラッグできるようにしています。

Draggable(
  child: Text("Drag me!"),
  feedback: Text("Dragging!"),
)

画像のドラッグと指定領域での関数実行

画像をドラッグして、指定された領域にドロップすると関数を実行することもできます。以下の例では、ドラッグできる画像を作成し、DragTargetウィジェットを用いて指定領域での関数実行を行っています。

Draggable(
  data: "drag_data",
  child: Image.asset("assets/image.png"),
  feedback: Image.asset("assets/image.png"),
)

DragTarget<String>(
  onAccept: (data) {
    if (data == "drag_data") {
      // 関数実行
    }
  },
  builder: (BuildContext context, List<String> candidateData, List<dynamic> rejectedData) {
    return Container(
      height: 100,
      width: 100,
      color: Colors.blue,
      child: Center(child: Text("Drop here")),
    );
  },
)

ドラッグ中の表示要素のカスタマイズ

ドラッグ中の要素の見た目をカスタマイズすることもできます。例えば、透明度を変更することでドラッグ中の要素が半透明になるようにカスタマイズできます。

Draggable(
  child: Icon(Icons.drag_handle),
  feedback: Opacity(
    opacity: 0.5,
    child: Icon(Icons.drag_handle, color: Colors.blue),
  ),
)

LongPressDraggableウィジェットについて

長押しによるドラッグの開始

LongPressDraggableウィジェットを使用することで、長押しによってドラッグを開始することができます。このウィジェットは、Draggableウィジェットと同じように扱うことができます。以下の例では、長押しでドラッグが開始されるアイコンを作成しています。

LongPressDraggable(
  child: Icon(Icons.drag_handle),
  feedback: Icon(Icons.drag_handle, color: Colors.blue),
)

ドラッグ中のウィジェットの表示

LongPressDraggableウィジェットでも、ドラッグ中のウィジェットの表示をカスタマイズすることができます。以下の例では、ドラッグ中にアイコンの色が変更されるようにカスタマイズしています。

LongPressDraggable(
  child: Icon(Icons.drag_handle),
  feedback: Icon(Icons.drag_handle, color: Colors.red),
)

このように、LongPressDraggableウィジェットは通常のDraggableウィジェットと同様の方法で実装し、カスタマイズすることができます。ドラッグ操作をより直感的で使いやすいものにしたい場合は、このウィジェットを利用してみることをおすすめします。

DraggableとPositionedウィジェットの組み合わせ

自由配置の実現方法

DraggableウィジェットとPositionedウィジェットを組み合わせることで、アプリ内で自由に配置された要素をドラッグ&ドロップで移動できるようになります。これにより、ユーザーが直感的に操作できるインターフェイスを実現できます。以下は、DraggableとPositionedウィジェットを組み合わせた実装例です。

Stack(
  children: [
    Positioned(
      top: _top,
      left: _left,
      child: Draggable(
        child: Icon(Icons.drag_handle),
        feedback: Icon(Icons.drag_handle, color: Colors.blue),
        onDragEnd: (dragDetails) {
          setState(() {
            _top = dragDetails.offset.dy;
            _left = dragDetails.offset.dx;
          });
        },
      ),
    ),
  ],
)

座標の記録と更新

ドラッグ&ドロップ操作が完了した際に、要素の座標を更新する必要があります。上記の例では、onDragEnd コールバック内で setState を使って座標を更新しています。このようにして、ウィジェットの位置を記録し、次回の操作で正確な位置情報を利用できるようにします。

DraggableとPositionedウィジェットを組み合わせることで、自由配置を実現し、アプリの使いやすさを向上させることができます。座標の記録と更新を適切に行い、ユーザーが直感的に操作できるインターフェイスを提供してください。

実例と応用

実用的なDraggableウィジェットの例

Draggableウィジェットは、カードソートアプリやタスク管理アプリなど、さまざまなアプリケーションで活用できます。例えば、タスク管理アプリでは、以下のような実装が考えられます。

Draggable(
  child: ListTile(title: Text("タスク名")),
  feedback: Material(
    child: ListTile(title: Text("タスク名", style: TextStyle(color: Colors.blue))),
    elevation: 6,
  ),
  onDragEnd: (details) {
    // タスクの移動先の処理を行う
  },
)

この例では、タスクをリスト表示している部分をDraggableウィジェットでラップしています。ユーザーがタスクをドラッグすることで、他のカテゴリに移動させることができます。

ドラッグ&ドロップのカスタマイズ

Draggableウィジェットはカスタマイズが容易であり、例えば、ドラッグ時に影を表示したり、ドロップ時の挙動を変更することができます。以下は、ドラッグ時に影を表示し、ドロップ時の挙動をカスタマイズした実装例です。

Draggable(
  child: ListTile(title: Text("タスク名")),
  feedback: Material(
    child: ListTile(title: Text("タスク名", style: TextStyle(color: Colors.blue))),
    elevation: 6,
  ),
  onDragEnd: (details) {
    if (details.wasAccepted) {
      // ドロップ成功時の処理
    } else {
      // ドロップ失敗時の処理
    }
  },
)

この実装では、ドロップ時に details.wasAccepted を確認して、ドロップが成功したかどうかを判断しています。これにより、ドロップ成功時と失敗時に異なる処理を行うことができます。

Draggableウィジェットを活用し、実用的なアプリケーションを作成することができます。また、ドラッグ&ドロップの挙動をカスタマイズすることで、ユーザーの操作に応じた柔軟なインターフェイスを提供できるでしょう。

Draggableウィジェットのプロパティ

上記で解説していないプロパティについてまとめましょう。
dragAnchorStrategy: これは、ドラッグ操作が開始されたときの基準点を決定する戦略です。デフォルトでは、ドラッグ開始地点が基準点となります。

onDragStarted

ドラッグが開始されたときに呼び出されるコールバック関数です。これを利用することで、ドラッグ開始時の特定の操作を実装することができます。

onDragUpdate

ドラッグ中の位置が更新されるたびに呼び出されるコールバック関数です。これにより、ドラッグ中のウィジェットの位置をリアルタイムで取得することが可能となります。

onDraggableCanceled

ドラッグがキャンセルされたとき、または有効なDragTarget上にドロップされなかったときに呼び出されるコールバック関数です。この関数を利用することで、ドラッグが終了したときのクリーンアップ作業などを行うことができます。

onDragEnd

ドラッグが終了した時点で呼び出されるコールバック関数です。ここで、ドラッグ終了時の最終的な位置や速度を取得することができます。

onDragCompleted

有効なDragTarget上にドロップされたときに呼び出されるコールバック関数です。これにより、ドロップ成功時の特定の操作を実装することができます。

ignoringFeedbackSemantics

これは、セマンティクスツリーにフィードバックウィジェットが表示されるかどうかを制御します。これをtrueに設定すると、フィードバックウィジェットはセマンティクスツリーに表示されません。

ignoringFeedbackPointer

これは、ドラッグ操作中にフィードバックウィジェットがポインターイベントを無視するかどうかを制御します。これをtrueに設定すると、フィードバックウィジェットはポインターイベントを無視します。

rootOverlay

これは、フィードバックウィジェットがオーバーレイのルートに挿入されるかどうかを制御します。これをtrueに設定すると、フィードバックウィジェットはオーバーレイのルートに挿入されます。

hitTestBehavior

これは、ヒットテストの振る舞いを制御します。HitTestBehavior値を設定するこ
と、ドラッグ操作中にウィジェットが他のウィジェットに対してどのように反応するかを決定することができます。

allowedButtonsFilter

これはドラッグ操作を開始するために必要なポインターボタンの種類を指定します。特定のボタンのみを許可したい場合に使用します。

以上がDraggableウィジェットの主な引数になります。これらを適切に組み合わせることで、様々なドラッグ&ドロップの挙動を実現することが可能です。

Q&A

Q1: Draggableウィジェットとは何ですか?

A1: Draggableウィジェットは、Flutterでドラッグアンドドロップ機能を簡単に実装できるウィジェットです。ドラッグされる要素(child)とドラッグ中に表示される要素(feedback)を設定することで、独自のドラッグアンドドロップ機能を実現できます。

Q2: LongPressDraggableウィジェットとはどのようなものですか?

A2: LongPressDraggableウィジェットは、通常のDraggableウィジェットと同様にドラッグアンドドロップ機能を実装するためのウィジェットですが、長押しによってドラッグが開始される点が異なります。これにより、通常のタップ操作との違いを明確にすることができます。

Q3: DraggableとPositionedウィジェットの組み合わせによる自由配置の実現方法は何ですか?

A3: DraggableとPositionedウィジェットを組み合わせることで、自由に配置できるUIを実現できます。ドラッグアンドドロップした要素の座標を記録し、更新することで、動的なレイアウトを作成することができます。また、これを応用することで、より高度なカスタマイズが可能になります。

まとめ

Flutterでドラッグアンドドロップ機能を手軽に実装できるDraggableウィジェットについて学びました。基本概念から、childとfeedbackの役割、DragTargetとの連携方法まで理解しました。実装方法も学び、基本的な実装や画像のドラッグ、ドラッグ中の表示要素のカスタマイズ方法を習得しました。さらに、長押しによるドラッグの開始やドラッグ中のウィジェット表示を扱うLongPressDraggableウィジェットについても知りました。自由配置を実現するDraggableとPositionedウィジェットの組み合わせや座標の記録と更新方法も習得しました。最後に、実用的なDraggableウィジェットの例やドラッグ&ドロップのカスタマイズ方法を学び、今後の学習へのアドバイスを得ることができました。

重要なポイント:

Draggableウィジェットは、Flutterでドラッグアンドドロップ機能を簡単に実装できる
childはドラッグされる要素、feedbackはドラッグ中に表示される要素
DragTargetと連携して、ドロップ時の挙動を制御
LongPressDraggableウィジェットは、長押しでドラッグが開始される
DraggableとPositionedウィジェットの組み合わせで、自由な配置を実現
実用的なDraggableウィジェットの応用例やカスタマイズ方法を学ぶことができる

参考

ソース(main.dartにコピペして動作確認用)

以下に、Draggableウィジェットを用いたFlutterのサンプルソースコードを示します。このコードでは、基本的な実装、画像のドラッグ、指定領域での関数実行、ドラッグ中の表示要素のカスタマイズが盛り込まれています。
(なんか、自由に移動するやつがうまく動作しない、、、)

import 'package:flutter/material.dart';
void main() {
  runApp(MyApp());
}
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('Draggable Sample'),
        ),
        body: DragDropExample(),
      ),
    );
  }
}
class DragDropExample extends StatefulWidget {
  @override
  _DragDropExampleState createState() => _DragDropExampleState();
}
class _DragDropExampleState extends State<DragDropExample> {
  Color _currentColor = Colors.grey;
  double _top = 0, _left = 0;
  @override
  Widget build(BuildContext context) {
    return Stack(
      children: [
        Column(
          mainAxisAlignment: MainAxisAlignment.spaceEvenly,
          children: [
            Row(
              mainAxisAlignment: MainAxisAlignment.spaceEvenly,
              children: [
                _buildDraggable(Colors.red),
                _buildDraggable(Colors.green),
                _buildDraggable(Colors.blue),
                _buildLongPressDraggable(Colors.cyan)
              ],
            ),
            const Icon(Icons.arrow_downward),
            DragTarget<Color>(
              onWillAccept: (data) => data != null,
              onAccept: (data) {
                setState(() {
                  _currentColor = data;
                });
              },
              builder: (context, candidateData, rejectedData) {
                return Container(
                  width: 100,
                  height: 100,
                  color: _currentColor,
                );
              },
            ),
          ],
        ),
        Positioned(
          top: _top,
          left: _left,
          child: Draggable(
            child: Icon(
              Icons.star,
              size: 64,
              color: Colors.yellow,
            ),
            feedback: Icon(Icons.drag_handle, color: Colors.blue),
            onDragUpdate: (dragUpdateDetails) {
              print(
                  '${dragUpdateDetails.globalPosition.dx} ${dragUpdateDetails.globalPosition.dy}');
            },
            onDragEnd: (dragDetails) {
              setState(() {
                _top = dragDetails.offset.dy;
                _left = dragDetails.offset.dx;
              });
            },
          ),
        ),
      ],
    );
  }
  Widget _buildDraggable(Color color) {
    return Draggable(
      data: color,
      child: Container(
        width: 50,
        height: 50,
        color: color,
        child: Text('child'),
      ),
      feedback: Container(
        width: 50,
        height: 50,
        color: color.withOpacity(0.5),
        child: Text('F'),
      ),
      childWhenDragging: Container(
        width: 50,
        height: 50,
        color: Colors.grey,
        child: Text('childWhenDragging'),
      ),
    );
  }
  Widget _buildLongPressDraggable(Color color) {
    return LongPressDraggable<Color>(
      data: color,
      child: Container(
        width: 50,
        height: 50,
        color: color,
        child: Text('LongPress'),
      ),
      feedback: Container(
        width: 50,
        height: 50,
        color: color.withOpacity(0.5),
        child: Text('F'),
      ),
      childWhenDragging: Container(
        width: 50,
        height: 50,
        color: Colors.grey,
        child: Text('childWhenDragging'),
      ),
    );
  }
}

このサンプルコードは、3つのドラッグ可能なカラーボックスを表示し、それらをドラッグアンドドロップして新しい領域で色を変更できるようにしています。ドラッグ中の表示要素もカスタマイズされています。ご参考にしてください。