Draggable
最近做了一个Flutter项目,其中有一个需求是做出三个可以互相拖拽的任务列表,平时在做Android项目时,拖动的控件基本上都是自定义View来实现的,想看看在Fluter上大家都是怎么实现的,没想到flutter提供了一个非常方便的拖拽控件Draggable。
Draggable的构造函数
我个人在Flutter开发时,遇到没有见过的控件时,点开源码观察它的构造函数一定是了解它的功能的最优解,我们看Draggable的构造函数:
const Draggable({
Key key,
@required this.child, // child不用解释了吧
@required this.feedback, // 拖动时显示的组件
this.data, // 控件携带的数据(一般提供给DragTarget,后面会讲)
this.axis, // 对滑动的方向做限制(横向或者纵向)
this.childWhenDragging, //多点触控拖动时显示的组件
this.feedbackOffset = Offset.zero, // 拖动后显示的位置,默认(0,0)
this.dragAnchor = DragAnchor.child, // 开始拖动时feedback显示的位置(原child位置还是触摸的位置)
this.affinity, // 让Draggable可以共享垂直或水平方向上的滑动事件(例如拖动同时滚动Scrollable)
this.maxSimultaneousDrags, // 多点触控时最大响应数
this.onDragStarted, // 开始拖动时的回调
this.onDraggableCanceled, // 未拖动到DragTarget控件上时回调
this.onDragEnd, // 拖动结束时的回调
this.onDragCompleted, // 拖动到DragTarget控件上时回调
this.ignoringFeedbackSemantics = true, // 控制是否显示feedback
}) : assert(child != null),
assert(feedback != null),
assert(ignoringFeedbackSemantics != null),
assert(maxSimultaneousDrags == null || maxSimultaneousDrags >= 0),
super(key: key);
可以看到Draggable还是很强大的,把拖动时显示的weiget也给封装好了,我们只用传入一个weiget就能自动显示,对拖拽事件的回调也很丰富,其中的DragTarget是另一个跟Draggable搭配的强大控件,下面先实现一个简单的Draggable:
Draggable的实现
Container(
alignment: Alignment.center,
child: Draggable(
child: Text("我可以被拖动!"),
feedback: Text("我正在被拖动!"),
),
),
是不是很简单,我们可以加一点其他的属性,比如对水平拖动进行限制
Container(
alignment: Alignment.center,
child: Draggable(
axis: Axis.vertical,
child: Text("我可以被拖动!"),
feedback: Text("我正在被拖动!"),
),
),
DragTarget
但是一般我们的拖动逻辑都是将一个控件挪到指定的位置(或者控件)上,以往我们都是通过拖动后的坐标等方法判断,然而观察Draggable的回调发现还有一个与Draggable配套的控件DragTarget,用DragTarget就可以把拖动到指定位置上的判断交给系统判断,话不多说,放码过来
DragTarget的构造函数
const DragTarget({
Key key,
@required this.builder, // 构造器
this.onWillAccept, // 判断该数据是否符合要求
this.onAccept, // 接收 Data 数据的回调
this.onLeave, //
}) : super(key: key);
DragTarget的构造函数就简单许多了:
builder:构造器;
其中参数包括三个属性,分别为 context
上下文环境,candidateData
为 onWillAccept 回调为 true 时可接收的数据列表,rejectedData
为 onWillAccept 回调为 false 时拒绝时的数据列表;builder的返回值就是DragTarget的child
typedef DragTargetBuilder<T> = Widget Function(BuildContext context, List<T> candidateData, List<dynamic> rejectedData);
onWillAccept:Draggable拖拽到 DragTarget 时的回调
用于判断该数据是否符合要求,返回true 时会将 Data 数据添加到 candidateData 列表中,且会调用onAccept;false 时会将 Data 数据添加到 rejectedData 列表中,且不会调用onAccept;
onAccept:用于接收Draggable中Data数据的回调;
onLeave:为Draggable离开时的回调;
DragTarget的实现
String data = "默认数据";
@override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.only(top: 200),
color: Colors.white,
child: Column(
children: [
Draggable<String>(
data: "Draggable数据",,
child: Text("我可以被拖动!"),
feedback: Text("我正在被拖动!"),
),
DragTarget<String>(
builder: (BuildContext context, List<dynamic> accepted, List<dynamic> rejected,) {
return Text(data);
},
onAccept: (data) {
setState(() {
this.data = data;
});
},
)
],
),
);
}
同理,我们可以应用到更复杂的画面中,还记得我一开始的需求么:做出三个可以互相拖拽的任务列表,于是我们可以将DragTarget和Draggable结合起来,将list的item做为Draggable,将每个list作为DragTarget,加上亿点点细节:
List<String> list1 = ["list1_1", "list1_2", "list1_3"];
List<String> list2 = ["list2_1", "list2_2"];
List<String> list3 = ["list3_1", "list3_2"];
@override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.only(top: 200),
color: Colors.white,
child: Column(
children: [
_createListView(list1),
_createListView(list2),
_createListView(list3),
],
),
);
}
Widget _createListView(List<String> _items) {
return DragTarget<String>(
builder: (
BuildContext context,
List<dynamic> accepted,
List<dynamic> rejected,
) {
return ListView.builder(
itemCount: _items.length,
shrinkWrap: true,
padding: EdgeInsets.all(10),
itemBuilder: (context, index) {
return Draggable<String>(
onDragCompleted: () {
// 在拖动到DragTarget后删除数据
setState(() {
_items.removeAt(index);
});
},
feedback: Material(
child: Container(
height: 60,
width: 200,
color: Colors.blueAccent,
alignment: Alignment.center,
child: Text(
_items[index],
style: TextStyle(color: Colors.white),
),
),
),
data: _items[index],
child: Container(
height: 50,
width: 200,
color: Colors.blueAccent,
alignment: Alignment.center,
child: Text(
_items[index],
style: TextStyle(color: Colors.white, fontSize: 20),
),
),
);
},
);
},
onAccept: (String data) {
setState(() {
// 添加Draggable数据到list
_items.add(data);
});
},
);
}
搞定!