前言:
這是我參與8月更文挑戰的第 30 天,活動詳情檢視:8月更文挑戰。為應掘金的八月更文挑戰
,我準備在本月挑選 31
個以前沒有介紹過的元件,進行全面分析和屬性介紹。這些文章將來會作為 Flutter 元件集錄
的重要素材。希望可以堅持下去,你的支援將是我最大的動力~
一、認識 Draggable 元件
Draggable
顧名思義,是可拖動的元件,它繼承自 StatefulWidget
,且可接受一個泛型。 構造方法有非常多的入參,其中必須傳入的是 child
和 feedback
兩個元件。
final Widget child;
final Widget feedback;
複製程式碼
1. 拖動的方向: axis
下面先通過一個小案例認識一下 Draggable
:下面是三個 Draggable
元件,其中 child
是藍色小圓,feedback
是紅色小圓,三者的區別在於 axis
屬性不同。左邊 axis
為 null
,表示不限定軸向,可以自由拖動;中間 axis
為 vertical
,只能在豎直方向拖動;中間 axis
為 horizontal
,只能在水平方向拖動。
class CustomDraggable extends StatelessWidget {
@override
Widget build(BuildContext context) {
List<Axis?> axis = [null, Axis.vertical, Axis.horizontal];
return Wrap(
spacing: 30,
children: axis
.map((Axis? axis) => Draggable(
axis: axis,
child: buildContent(),
feedback: buildFeedback(),
))
.toList());
}
Widget buildContent() {
return Container(
width: 30,
height: 30,
alignment: Alignment.center,
decoration: BoxDecoration(
color: Colors.blue,
shape: BoxShape.circle,
),
);
}
Widget buildFeedback() {
return Container(
width: 30,
height: 30,
decoration: BoxDecoration(
color: Colors.red,
shape: BoxShape.circle,
),
);
}
}
複製程式碼
2.拖動時原位置元件: childWhenDragging
Draggable
可以通過 childWhenDragging
屬性指定在拖拽過程中原來位置
的元件。如下,拖動時原來的位置顯示為 橙色小圓
和 刪除圖示。
Draggable(
axis: axis,
childWhenDragging: buildWhenDragging(),
child: buildContent(),
feedback: buildFeedback(),
))
Widget buildWhenDragging() {
return Container(
width: 30,
height: 30,
decoration: BoxDecoration(
color: Colors.orange,
shape: BoxShape.circle,
),
child: Icon(
Icons.delete_outline,
size: 20,
color: Colors.white,
),
);
}
複製程式碼
二、Draggable 與 DragTarget 聯合使用
1. 綜合測試案例
下面通過一個示例測試一下 Draggable
與 DragTarget
的聯合使用。如下,上面的小球是 Draggable
,下面的區域是 DragTarget
。可以拖動小球來為 DragTarget
著色,並且顯示當前操作的資訊。
Draggable
可以監聽五個回撥:
onDragStarted
:開始拖動時回撥,無回撥資料。onDragEnd
:結束拖動時回撥,可以獲取DraggableDetails
資料。onDragUpdate
:拖動更新時回撥,可以獲取DraggableDetails
資料。onDragCompleted
: 拖入目標區域,並鬆手完成時回撥,無回撥資料。onDraggableCanceled
:未在目標區域,拖拽取消回撥,可以獲取Velocity
、Offset
資料。
List<Widget> _buildDraggable() {
return colors.map(
(Color color) => Draggable<Color>(
onDragStarted: _onDragStarted,
onDragEnd: _onDragEnd,
onDragUpdate: _onDragUpdate,
onDragCompleted: _onDragCompleted,
onDraggableCanceled: _onDraggableCanceled,
childWhenDragging: childWhenDragging(colors.indexOf(color).toString()),
child: buildContent(color),
data: color,
feedback: buildFeedback(color)),
).toList();
}
void _onDragUpdate(DragUpdateDetails details) {
print('座標:'
'(${details.localPosition.dx.toStringAsFixed(1)},'
'${details.localPosition.dy.toStringAsFixed(1)})');
}
void _onDraggableCanceled(Velocity velocity, Offset offset) {
_info = '拖拽取消';
}
void _onDragCompleted() {
_info = '拖拽完成';
}
void _onDragEnd(DraggableDetails details) {
setState(() => _info = '結束拖拽');
}
void _onDragStarted() {
setState(() => _info = '開始拖拽');
}
複製程式碼
DragTarget
會通過 builder
回撥來構建元件,其中會回撥 candidateData
和 rejectedData
兩個列表,其中包含接受
和 拒絕
的資料。由於 Draggable
支援多個同時拖動,使用是資料列表。
DragTarget<Color>(
onLeave: _onLeave,
onAccept: _onAccept,
onWillAccept: _onWillAccept,
builder: _buildTarget,
)
Widget _buildTarget(BuildContext context, List<Color?> candidateData, List rejectedData) {
return Container(
width: 150.0,
height: 50.0,
color: _color,
child: Center(
child: Text(
_info,
style: TextStyle(color: Colors.white),
),
));
}
void _onLeave(Color? data) {
print("onLeave: data = $data ");
setState(() => _info = 'onLeave');
}
void _onAccept(Color data) {
print("onAccept: data = $data ");
setState(() => _color = data);
}
bool _onWillAccept(Color? data) {
print("onWillAccept: data = $data ");
setState(() => _info = 'onWillAccept');
return data != null;
}
複製程式碼
onWillAccept
是 DragTarget
中比較重要的一個回撥,當拖動的元件到達目標區域後,onWillAccept
會觸發。從下面原始碼中可以看出 _candidateAvatars
和 _rejectedAvatars
和 onWillAccept
的返回值有關。如果 onWillAccept
返回 false ,則資料會被簡入到 _rejectedAvatars
。
而 builder
中的回撥入參 candidateData
和 rejectedData
就是根據上面兩個列表計算的。
2.拖拽刪除案例
如下示例,通過擴充元件目標到指定位置進行移除,通過 Draggable 和 DragTarget
聯合就很容易實現。
程式碼實現如下,通過顏色陣列 colors
生成不同顏色的 Draggable
,並擁有 int
泛型,傳遞的數值為可拖拽元件的索引,這樣在 DragTarget
的 onAccept
中可以獲取拖入進的索引資料,從而實現刪除功能。
class DeleteDraggable extends StatefulWidget {
@override
_DeleteDraggableState createState() => _DeleteDraggableState();
}
class _DeleteDraggableState extends State<DeleteDraggable> {
List<Color> colors = [
Colors.red, Colors.yellow, Colors.blue, Colors.green,
Colors.orange, Colors.purple, Colors.cyanAccent];
@override
Widget build(BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Wrap(
children: _buildDraggable(),
spacing: 10,
),
SizedBox(
height: 20,
),
DragTarget<int>(
onAccept: _onAccept,
onWillAccept: (data) => data != null,
builder: buildTarget
)
],
);
}
Widget buildTarget(context, candidateData, rejectedData) => Container(
width: 40.0,
height: 40.0,
decoration: BoxDecoration(color: Colors.red, shape: BoxShape.circle),
child: Center(
child: Icon(Icons.delete_sweep, color: Colors.white),
));
List<Widget> _buildDraggable() => colors
.map((Color color) => Draggable<int>(
child: buildContent(color),
data: colors.indexOf(color),
childWhenDragging: buildWhenDragging(),
feedback: buildFeedback(color)),
).toList();
Widget buildContent(Color color) {
return Container(
width: 30,
height: 30,
alignment: Alignment.center,
child: Text(
colors.indexOf(color).toString(),
style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold),
),
decoration: BoxDecoration(color: color, shape: BoxShape.circle),
);
}
Widget buildFeedback(Color color) {
return Container(
width: 25,
height: 25,
decoration:
BoxDecoration(color: color.withAlpha(100), shape: BoxShape.circle),
);
}
Widget buildWhenDragging() {
return Container(
width: 30,
height: 30,
decoration: BoxDecoration(color: Colors.red, shape: BoxShape.circle),
child: Icon(Icons.delete_outline, size: 20, color: Colors.white,
),
);
}
void _onAccept(int data) {
setState(() {
colors.removeAt(data);
});
}
}
複製程式碼
通過Draggable 和 DragTarget
的聯合使用,不需要我們自己去實現拖拽邏輯,可以很輕鬆解決很多目標拖拽的問題。那本文到這裡就結束了,謝謝觀看,明天見~