小菜尝试做一个新闻类 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)]);
复制代码
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 为移动过程中锚点位置,分为 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);
复制代码
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);
复制代码
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 底层源码还不够熟悉,如有问题请多多指导!
来源: 阿策小和尚
评论