【Flutter 專題】96 圖解 Draggable + DragTarget 基本拖拽效果

阿策小和尚發表於2021-07-13

      小菜嘗試做一個新聞類 app 常見的可以滑動新增和刪除 item 選項卡的小功能,小菜嘗試採用 Draggable + DragTarget 方式;今天先學習一下 Draggable 拖拽元件的基本應用;

Draggable

原始碼分析

const Draggable({
    Key key,
    @required this.child,
    @required this.feedback,
    this.data,
    this.axis,
    this.childWhenDragging,
    this.feedbackOffset = Offset.zero,
    this.dragAnchor = DragAnchor.child,
    this.affinity,
    this.maxSimultaneousDrags,
    this.onDragStarted,
    this.onDraggableCanceled,
    this.onDragEnd,
    this.onDragCompleted,
    this.ignoringFeedbackSemantics = true,
})
複製程式碼

      分析原始碼可得,Draggable 是有狀態的 StatefulWidget 元件,一般與 DragTarget 配合使用,拖拽至 DragTarget;其中 childfeedback 是兩個必填屬性,分別代表預設情況展示的子 Widget 和拖拽過程中移動時的子 Widget

案例嘗試

  1. 小菜先嚐試一個最基本的 Draggable 效果,然後逐步新增屬性效果;
Draggable(
    child: Image.asset('images/icon_hzw01.jpg', width: 150.0),
    feedback: Image.asset('images/icon_hzw02.jpg', width: 150.0));
複製程式碼

  1. affinity 屬性主要是用於與其他手勢競爭,例如在垂直列表中,且 affinity 設定 Axis.horizontal 水平屬性,則只允許水平方向拖拽,豎直方向則是列表的滾動;
return ListView(children: <Widget>[
  Icon(Icons.access_alarm, size: 100),
  Icon(Icons.print, size: 100),
  Icon(Icons.android, size: 100),
  Draggable(
      child: Icon(Icons.ac_unit, size: 150, color: Colors.blue),
      feedback: Icon(Icons.ac_unit, size: 200, color: Colors.red),
      affinity: Axis.horizontal),
  Icon(Icons.directions_car, size: 100),
  Icon(Icons.sync, size: 100),
  Icon(Icons.error, size: 100),
  Icon(Icons.send, size: 100),
  Icon(Icons.call, size: 100)
]);
複製程式碼

3. axis 用於限制拖拽方向,水平或豎直方向,若設定 null 則是全方向拖拽;其中在與其他滑動手勢衝突時與 affinity 配合使用;

Draggable(affinity: Axis.horizontal, axis: Axis.horizontal,
    child: Image.asset('images/icon_hzw01.jpg', width: 150.0),
    feedback: Image.asset('images/icon_hzw02.jpg', width: 150.0));
複製程式碼

4. childWhenDragging 為拖拽過程中,原位置子 Widget 對應展示內容;

Draggable(affinity: Axis.horizontal, axis: null,
    child: Image.asset('images/icon_hzw01.jpg', width: 150.0),
    feedback: Image.asset('images/icon_hzw02.jpg', width: 150.0),
    childWhenDragging: Image.asset('images/icon_hzw03.jpg', width: 150.0));
複製程式碼

5. dragAnchor 為移動過程中錨點位置,分為 childpointer 兩種;child 是以預設子 child 為基礎,起始點以 Offset.zero 左上角位置為準;pointer 以在子 child 範圍內,手勢點選時位置為準;

Draggable(affinity: Axis.horizontal, axis: null,
    child: Image.asset('images/icon_hzw01.jpg', width: 150.0),
    feedback: Image.asset('images/icon_hzw02.jpg', width: 150.0),
    dragAnchor: DragAnchor.pointer);
複製程式碼

6. maxSimultaneousDrags 為針對於同一個子 child 可以同時拖拽個數,小菜嘗試的兩個手指同時向兩個方向拖拽;

Draggable(affinity: Axis.horizontal, axis: null,
    child: Image.asset('images/icon_hzw01.jpg', width: 150.0),
    feedback: Image.asset('images/icon_hzw02.jpg', width: 150.0),
    maxSimultaneousDrags: 2);
複製程式碼

  1. ignoringFeedbackSemantics 當子 childfeedback 為同一個 Widget 時,可以通過 ignoringFeedbackSemantics 設為 false 配合 Key 確保是同一個 Widget 減少繪製;
Draggable(affinity: Axis.horizontal, axis: null,
    child: Image.asset('images/icon_hzw01.jpg', width: 150.0, key: _itemKey),
    feedback: Image.asset('images/icon_hzw01.jpg', width: 150.0),
    ignoringFeedbackSemantics: false);
複製程式碼
  1. onDraggableX 為拖拽過程中的回撥函式;onDragStarted 為開始拖拽時回撥;onDraggableCanceled 為在沒有被 DragTarget 接收時取消的回撥;onDragEnd 為拖拽結束時的回撥,不管是否被 DragTarget 接收;onDragCompleted 為被 DragTarget 接收成功時回撥;
Draggable(affinity: Axis.horizontal, axis: null,
    child: Image.asset('images/icon_hzw01.jpg', width: 150.0, key: _itemKey),
    feedback: Image.asset('images/icon_hzw01.jpg', width: 150.0),
    childWhenDragging: Container(),
    onDragCompleted: () => print('Draggable --> onDragCompleted'),
    onDragEnd: (DraggableDetails details) => print('Draggable --> onDragEnd --> ${details.offset}'),
    onDraggableCanceled: (Velocity velocity, Offset offset) => print('Draggable --> onDraggableCanceled --> $offset'),
    onDragStarted: () => print('Draggable --> onDragStarted'));
複製程式碼

  1. dataT 任意型別資料,主要是向 DragTarget 傳遞;
data: 'Draggable Data A !!!',
複製程式碼

DragTarget

原始碼分析

const DragTarget({
    Key key,
    @required this.builder,
    this.onWillAccept,
    this.onAccept,
    this.onLeave,
})
複製程式碼

      分析原始碼可得 DragTarget 同樣為 StatefulWidget 帶狀態的 Widget,其中 builder 構造器為必填屬性,用於構建接收 Draggable 後的 Widget 構建;

案例嘗試

  1. builder 為構造器,其中包括三個屬性,分別為 context 上下文環境,candidateDataonWillAccept 回撥為 true 時可接收的資料列表,rejectedDataonWillAccept 回撥為 false 時拒絕時的資料列表;

       2. onWillAccept 為拖拽到 DragTarget 時的回撥,true 時會將 Data 資料新增到 candidateData 列表中;false 時會將 Data 資料新增到 rejectedData 列表中; 3. onAccept 用於接收 Data 資料; 4. onLeave 為離開時的回撥;且小菜測試過程中,當 onWillAccept 返回 true 時,onAcceptonLeave 臨界為手勢拖拽的最後的座標是否在 DragTarget 範圍內;       

DragTarget<String>(builder: (BuildContext context, List<String> candidateData, List<dynamic> rejectedData) {
    print('DragTarget --> builder --> $candidateData --> $rejectedData -->$_dragState');
    return _dragState
        ? Image.asset('images/icon_hzw01.jpg', width: 150.0)
        : Container(height: 150.0, width: 150.0, color: Colors.blue.withOpacity(0.4));
  }, onAccept: (String data) {
    print('DragTarget --> onAccept --> $data -->$_dragState');
    setState(() {
      _dragState = true;
    });
  }, onLeave: (String data) {
    print('DragTarget --> onLeave --> $data');
  }, onWillAccept: (String data) {
    print('DragTarget --> onWillAccept --> $data');
    return true;
  });
複製程式碼

LongPressDraggable

原始碼分析

const LongPressDraggable({
    Key key,
    @required Widget child,
    @required Widget feedback,
    T data,
    Axis axis,
    Widget childWhenDragging,
    Offset feedbackOffset = Offset.zero,
    DragAnchor dragAnchor = DragAnchor.child,
    int maxSimultaneousDrags,
    VoidCallback onDragStarted,
    DraggableCanceledCallback onDraggableCanceled,
    DragEndCallback onDragEnd,
    VoidCallback onDragCompleted,
    this.hapticFeedbackOnStart = true,
    bool ignoringFeedbackSemantics = true,
})
複製程式碼

      分析原始碼可得,LongPressDraggable 繼承自 Draggable,屬性和方法基本完全一致,只是需要長按拖拽;


      Draggable + DragTarget 案例嘗試


      小菜簡答嘗試了 Draggable 拖拽 Widget 以及對應接收拖拽的 DragTarget,下節嘗試新聞類型別選項卡;小菜對 Draggable 底層原始碼還不夠熟悉,如有問題請多多指導!

來源: 阿策小和尚

相關文章