小菜嘗試做一個新聞類 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;其中 child 和 feedback 是兩個必填屬性,分别代表預設情況展示的子 Widget 和拖拽過程中移動時的子 Widget;
案例嘗試
- 小菜先嘗試一個最基本的 Draggable 效果,然後逐漸添加屬性效果;
Draggable(
child: Image.asset('images/icon_hzw01.jpg', width: 150.0),
feedback: Image.asset('images/icon_hzw02.jpg', width: 150.0));
- 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)
]);
- 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));
- 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));
- dragAnchor 為移動過程中錨點位置,分為 child 和 pointer 兩種;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);
- 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);
- ignoringFeedbackSemantics 當子 child 和 feedback 為同一個 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);
- 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'));
- data 為 T 任意類型資料,主要是向 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 建構;
- builder 為構造器,其中包括三個屬性,分别為 context 上下文環境,candidateData 為 onWillAccept 回調為 true 時可接收的資料清單,rejectedData 為 onWillAccept 回調為 false 時拒絕時的資料清單;
- onWillAccept 為拖拽到 DragTarget 時的回調,true 時會将 Data 資料添加到 candidateData 清單中;false 時會将 Data 資料添加到 rejectedData 清單中;
- onAccept 用于接收 Data 資料;
- onLeave 為離開時的回調;且小菜測試過程中,當 onWillAccept 傳回 true 時,onAccept 和 onLeave 臨界為手勢拖拽的最後的坐标是否在 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 底層源碼還不夠熟悉,如有問題請多多指導!
來源: 阿策小和尚