ドラッグ&ドロップは、一般的なモバイルアプリの操作です。
(すなわち、ウィジェットを長押し(タッチ&ホールド)し、(ユーザの指の下に別のウィジェットが表示されるので、(そのウィジェットを))最終的な位置までドラッグ&ドロップする、という操作です。 )
ここでは(例として)ユーザが料理の選択肢を長押しし、その料理をお金を払っている客の写真にドラッグする、というドラッグ&ドロップインタラクションの解説がされています。
1.Press and drag
FlutterはLongPressDraggableと呼ばれるウィジェットを提供しており、ドラッグアンドドロップのインタラクションを開始するために必要な動作を正確に提供します。
LongPressDraggableウィジェットは、長押しが発生したことを認識し、ユーザの指の近くに新しいウィジェットを表示します。
LongPressDraggableは、ユーザがドラッグするウィジェットを完全にコントロールできます。
(1)MenuListItem
各メニューリスト項目はカスタムMenuListItemウィジェットで表示されます。
MenuListItem(
name: item.name,
price: item.formattedTotalItemPrice,
photoProvider: item.imageProvider,
)
(2)LongPressDraggable
MenuListItem ウィジェットを LongPressDraggable ウィジェットでラップします。
LongPressDraggable<Item>(
data: item,
//dragAnchorStrategy プロパティ:pointerDragAnchorStrategy
// このプロパティ値は、LongPressDraggable が
//DraggableListItem の位置をユーザの指を基準にするように指示します。
dragAnchorStrategy: pointerDragAnchorStrategy,
//ユーザが MenuListItem ウィジェットを長押しすると、
// LongPressDraggable ウィジェットは DraggingListItem
//(選択された食品の写真 )を(ユーザの指の下の中央に)表示します。
feedback: DraggingListItem(
dragKey: _draggableKey,
photoProvider: item.imageProvider,
),
//長押し対象の MenuListItem ウィジェット
child: MenuListItem(
name: item.name,
price: item.formattedTotalItemPrice,
photoProvider: item.imageProvider,
),
);
この場合、ユーザが MenuListItem ウィジェットを長押しすると、LongPressDraggable ウィジェットは DraggingListItem(選択された食品の写真 )を(ユーザの指の下の中央に)表示します。
dragAnchorStrategy プロパティは pointerDragAnchorStrategy に設定されています。
このプロパティ値は、LongPressDraggable が DraggableListItem の位置をユーザの指を基準にするように指示します。
ドラッグ&ドロップは、アイテムがドロップされたときに情報が送信されなければ、ほとんど意味がないので、LongPressDraggableはデータ・パラメータ(ここでは、データの型はItemであり、ユーザーが押したフードメニュー項目に関する情報)を保有しています。
LongPressDraggableに関連付けられたデータは、DragTargetと呼ばれる特別なウィジェット(ユーザがドラッグジェスチャでドロップする場所)に送られます。
※後述にある様なドロップ後の動作は、↑上記処理の↓後ろに、実装することになります。
2.Drop the draggable
ユーザはLongPressDraggableを好きな場所にドロップできますが、DragTarget 以外の場所にドロップしても効果はありません。
ユーザがDragTargetウィジェットの上にDraggingListItemウィジェットをドロップすると、DragTargetウィジェットはDraggingListItemウィジェットのデータを受け入れ(ここでは、ユーザはメニュー項目を CustomerCart ウィジェットにドロップして、メニュー項目をユーザのカートに追加でき)ます(拒否することも可能です)。
CustomerCart(
hasItems: customer.items.isNotEmpty,
highlighted: candidateItems.isNotEmpty,
customer: customer,
);
CustomerCart ウィジェットを DragTarget ウィジェットで囲み、ドロップ先を設定します。
//CustomerCart ウィジェットを DragTarget ウィジェットで囲み、
//これを ドロップ先として設定しています。
DragTarget<Item>(
builder: (context, candidateItems, rejectedItems) {
//ドロップ先にしたいウィジェット
return CustomerCart(
hasItems: customer.items.isNotEmpty,
highlighted: candidateItems.isNotEmpty,
customer: customer,
);
},
onAcceptWithDetails: (details) {
_itemDroppedOnCustomerCart(
item: details.data,
customer: customer,
);
},
)
DragTargetは、既存のウィジェットが(表示され、またLongPressDraggableと連携して(ユーザによりDragTargetの上に))ドラッグされたことを認識します。(DragTargetは、ユーザがDragTargetウィジェットの上にドラッグ可能なものをドロップした場合も認識します。)
ユーザが DragTarget ウィジェット上でドラッグすると、candidateItems にユーザがドラッグするデータ項目が含まれます。この draggable を使用すると、ユーザがウィジェットの上をドラッグした時に、ウィジェットがどの様に見えるかを変更できます。 (ここでは、DragTargetウィジェットの上にアイテムがドラッグされると、Customerウィジェットを赤くしています。赤い外観は、CustomerCartウィジェット内のhighlightedプロパティで設定します。)
ユーザが DragTarget ウィジェットに draggable をドロップすると、onAcceptWithDetails コールバックが呼び出されます。この時点で、ドロップされたデータを受け入れるかどうかを決めることになります。
(ここでは、商品は常に受入れられ処理されますが、別の判断の為の入荷商品検査も選択余地もあります。)
DragTargetにドロップされるアイテムのタイプは、LongPressDraggableからドラッグされるアイテムのタイプと一致しなければならないことに注意して下さい。型に互換性がない場合、onAcceptWithDetailsメソッドは呼び出されません。
DragTargetウィジェットが必要なデータを受け付けるように設定されていれば、ドラッグ&ドロップでUIのある部分から別の部分にデータを転送できるようになります。
※次のステップでは、顧客のカートをドロップしたメニューアイテムで更新します。
3.Add a menu item to a cart
ここでは、各顧客はCustomerオブジェクトで表され、Customerオブジェクトは商品のカートと価格の合計を保持します。
class Customer {
Customer({
required this.name,
required this.imageProvider,
List<Item>? items,
}) : items = items ?? [];
final String name;
final ImageProvider imageProvider;
final List<Item> items;
String get formattedTotalItemPrice {
final totalPriceCents =
items.fold<int>(0, (prev, item) => prev + item.totalPriceCents);
return '\$${(totalPriceCents / 100.0).toStringAsFixed(2)}';
}
}
ここでは、CustomerCartウィジェットは、Customerインスタンスに基づいて、顧客の写真、名前、合計、アイテム数を表示しています。
メニューがドロップされた時に顧客のカートを更新する為に、関連するCustomerオブジェクトにドロップされたアイテム(customer.items)を追加します。
void _itemDroppedOnCustomerCart({
required Item item,
required Customer customer,
}) {
setState(() {
customer.items.add(item);
});
}
itemDroppedOnCustomerCartメソッドは、ユーザがメニュー項目をCustomerCartウィジェットにドロップすると、onAcceptWithDetails()内で呼び出されます。ドロップしたアイテムをcustomerオブジェクトに追加し、setState()を呼び出してレイアウトを更新すると、UIは新しい顧客の価格合計とアイテム数でリフレッシュされます。
以上で、顧客の買物カートに食品を追加するドラッグ&ドロップのインタラクションができました。
4.Interactive example
アプリは以下の様な仕様で動作します:
- 食品項目をスクロールします。
- 指で押したままにするか、マウスでクリックして押したままにします。
- 持っている間、リストの上に食べ物の画像が表示されます。
- 画像をドラッグして、画面下部の人物の 1 人にドロップします。画像の下のテキストが更新され、その人物の料金が反映されます。食品を追加し続けると、料金が蓄積されていきます。