为了账号安全,请及时绑定邮箱和手机立即绑定

Flutter 史上最牛拖动控件 Draggable

标签:
CSS3

不多说,我们肯定遇到过这个需求:

把一个控件从当前位置移动到另一个位置。可能需求最多的就像是支付宝应用页面的编辑:
图片描述
比如,我想把最近使用的 红包 添加到 我的应用 当中,支付宝这里是用的 + 号。
那如果产品说我就想让它拖过去,这可咋整?
不慌,Flutter 也为我们提供了相关的 Widget。

Draggable

Flutter 如果要实现这种效果,那么非 Draggable 不可。
照例我们查看官方文档。

1.  `A widget that can be dragged from to a DragTarget.`
    
2.    
    
3.  `可拖动到  DragTarget  的小部件。`
    

那也就是说,除了 Draggable ,还有一个 DragTarget。
DragTarget 是用来接收我们拖过去的 Widget 的,我们后面再说。
先说Draggable。
在官方文档找了一圈没发现Demo,那没办法了,直接开撸。
先看构造函数:

1.  `class  Draggable<T>  extends  StatefulWidget  {`
    
2.   `/// Creates a widget that can be dragged to a [DragTarget].`
    
3.   `///`
    
4.   `/// The [child] and [feedback] arguments must not be null. If`
    
5.   `/// [maxSimultaneousDrags] is non-null, it must be non-negative.`
    
6.   `const  Draggable({`
    
7.   `Key key,`
    
8.   `@required  this.child,`
    
9.   `@required  this.feedback,`
    
10.   `this.data,`
    
11.   `this.axis,`
    
12.   `this.childWhenDragging,`
    
13.   `this.feedbackOffset =  Offset.zero,`
    
14.   `this.dragAnchor =  DragAnchor.child,`
    
15.   `this.affinity,`
    
16.   `this.maxSimultaneousDrags,`
    
17.   `this.onDragStarted,`
    
18.   `this.onDraggableCanceled,`
    
19.   `this.onDragEnd,`
    
20.   `this.onDragCompleted,`
    
21.   `this.ignoringFeedbackSemantics =  true,`
    
22.   `})  :  assert(child !=  null),`
    
23.   `assert(feedback !=  null),`
    
24.   `assert(ignoringFeedbackSemantics !=  null),`
    
25.   `assert(maxSimultaneousDrags ==  null  || maxSimultaneousDrags >=  0),`
    
26.   `super(key: key);`
    
27.  `}`

可以看到该类还支持泛型,这个泛型是来界定 data的,后面再说。
先来看看必须的参数,child 和 feedback。
child 应该不比多说,说一下 feedback。
点击查看feedback 参数,上面的注释这样写着:
当拖动正在进行时在指针下显示的小部件。
既然搞懂了必传参数,那就开撸:

1.  `Widget _createGridView(List<String> _items)  {`
    
2.   `return  GridView.builder(`
    
3.   `itemCount: _items.length,`
    
4.   `shrinkWrap:  true,  // 相当于Android的 wrap_content ,也就是包裹住该 widget的高度`
    
5.   `physics:  NeverScrollableScrollPhysics(),  // 不允许滑动`
    
6.   `padding:  EdgeInsets.all(10),`
    
7.   `gridDelegate:  SliverGridDelegateWithFixedCrossAxisCount(`
    
8.   `crossAxisCount:  5,  // 设置 gridview 每一行的数量`
    
9.   `mainAxisSpacing:  10,`
    
10.   `crossAxisSpacing:  10,`
    
11.   `),`
    
12.   `itemBuilder:  (context, index)  {`
    
13.   `return  Draggable(  // 返回一个Draggable`
    
14.   `// 必须要一个Material,不然拖动时Text会有双下划线`
    
15.   `feedback:  Material(`
    
16.   `child:  Container(`
    
17.   `height:  100,`
    
18.   `width:  100,`
    
19.   `color:  Colors.blueAccent,`
    
20.   `alignment:  Alignment.center,`
    
21.   `child:  Text(`
    
22.   `_items[index],`
    
23.   `style:  TextStyle(color:  Colors.white),`
    
24.   `),`
    
25.   `),`
    
26.   `),`
    
27.   `child:  Container(`
    
28.   `color:  Colors.blueAccent,`
    
29.   `alignment:  Alignment.center,`
    
30.   `child:  Text(`
    
31.   `_items[index],`
    
32.   `style:  TextStyle(color:  Colors.white),`
    
33.   `),`
    
34.   `),`
    
35.   `);`
    
36.   `},`
    
37.   `);`
    
38.  `}`
    

我们定义一个 GridView,里面每一个 item 都是一个 Draggable。
来运行一下看效果:
图片描述
可以看到我们确实是可以拖动了,大功已经告成一半了。
那么我们下面开始定义接收的部件 DragTarget。

DragTarget

废话也不多说,直接看构造函数,看看什么是必填:

1.  `class  DragTarget<T>  extends  StatefulWidget  {`
    
2.   `/// Creates a widget that receives drags.`
    
3.   `///`
    
4.   `/// The [builder] argument must not be null.`
    
5.   `const  DragTarget({`
    
6.   `Key key,`
    
7.   `@required  this.builder,`
    
8.   `this.onWillAccept,`
    
9.   `this.onAccept,`
    
10.   `this.onLeave,`
    
11.   `})  :  super(key: key);`
    
12.  `}`

可以看到必传的参数也就是一个builder,用来构建我们的页面使用。
其他参数看名字也都能明白:

  • onWillAccept 拖到该控件上时调用

  • onAccept 放到该控件时调用

  • onLeave 没有放到该控件时调用
    那我们这里只需要一个确认已经放到该控件时的回调,来接收我们传过来的值。
    撸码:

1.  `Widget build(BuildContext context)  {`
    
2.   `return  Scaffold(`
    
3.   `appBar:  AppBar(`
    
4.   `title:  Text('DraggablePage'),`
    
5.   `),`
    
6.   `body:  Column(`
    
7.   `children:  <Widget>[`
    
8.   `_createGridView(_items1),`
    
9.   `SizedBox(height:  100,),// 占位`
    
10.   `DragTarget<String>(  // 用来接收数据的 Widget`
    
11.   `builder:  (`
    
12.   `BuildContext context,`
    
13.   `List<dynamic> accepted,`
    
14.   `List<dynamic> rejected,`
    
15.   `)  {`
    
16.   `return _createGridView(_items2);`
    
17.   `},`
    
18.   `// 用来接收数据`
    
19.   `onAccept:  (String data)  {`
    
20.   `setState(()  {`
    
21.   `_items2.add(data);`
    
22.   `});`
    
23.   `},`
    
24.   `)`
    
25.   `],`
    
26.   `),`
    
27.   `);`
    
28.  `}`

这是完整的 build 代码,但是还需要改造一下我们的 Draggable 。
再看一下Draggable的构造函数:

1.  `class  Draggable<T>  extends  StatefulWidget  {`
    
2.   `/// Creates a widget that can be dragged to a [DragTarget].`
    
3.   `///`
    
4.   `/// The [child] and [feedback] arguments must not be null. If`
    
5.   `/// [maxSimultaneousDrags] is non-null, it must be non-negative.`
    
6.   `const  Draggable({`
    
7.   `Key key,`
    
8.   `@required  this.child,`
    
9.   `@required  this.feedback,`
    
10.   `this.data,`
    
11.   `this.axis,`
    
12.   `this.childWhenDragging,`
    
13.   `this.feedbackOffset =  Offset.zero,`
    
14.   `this.dragAnchor =  DragAnchor.child,`
    
15.   `this.affinity,`
    
16.   `this.maxSimultaneousDrags,`
    
17.   `this.onDragStarted,`
    
18.   `this.onDraggableCanceled,`
    
19.   `this.onDragEnd,`
    
20.   `this.onDragCompleted,`
    
21.   `this.ignoringFeedbackSemantics =  true,`
    
22.   `})  :  assert(child !=  null),`
    
23.   `assert(feedback !=  null),`
    
24.   `assert(ignoringFeedbackSemantics !=  null),`
    
25.   `assert(maxSimultaneousDrags ==  null  || maxSimultaneousDrags >=  0),`
    
26.   `super(key: key);`
    
27.  `}`

因为如果想要传入数据,那也就必须要有数据可以传,也就是我们构造函数里看到的 data 字段。
还需要删除我们的源数据,那也就是要监听拖动结束的回调,这里就是 onDragCompleted
我们来看改造后的Draggable:

1.  `Widget _createGridView(List<String> _items)  {`
    
2.   `return  GridView.builder(`
    
3.   `itemCount: _items.length,`
    
4.   `shrinkWrap:  true,`
    
5.   `physics:  NeverScrollableScrollPhysics(),`
    
6.   `padding:  EdgeInsets.all(10),`
    
7.   `gridDelegate:  SliverGridDelegateWithFixedCrossAxisCount(`
    
8.   `crossAxisCount:  5,`
    
9.   `mainAxisSpacing:  10,`
    
10.   `crossAxisSpacing:  10,`
    
11.   `),`
    
12.   `itemBuilder:  (context, index)  {`
    
13.   `return  Draggable<String>(`
    
14.   `onDragCompleted:  (){`
    
15.   `// 在拖动结束后删除数据`
    
16.   `setState(()  {`
    
17.   `_items.removeAt(index);`
    
18.   `});`
    
19.   `},`
    
20.   `feedback:  Material(`
    
21.   `child:  Container(`
    
22.   `height:  100,`
    
23.   `width:  100,`
    
24.   `color:  Colors.blueAccent,`
    
25.   `alignment:  Alignment.center,`
    
26.   `child:  Text(`
    
27.   `_items[index],`
    
28.   `style:  TextStyle(color:  Colors.white),`
    
29.   `),`
    
30.   `),`
    
31.   `),`
    
32.   `// 当前组件的数据`
    
33.   `data: _items[index],`
    
34.   `child:  Container(`
    
35.   `color:  Colors.blueAccent,`
    
36.   `alignment:  Alignment.center,`
    
37.   `child:  Text(`
    
38.   `_items[index],`
    
39.   `style:  TextStyle(color:  Colors.white),`
    
40.   `),`
    
41.   `),`
    
42.   `);`
    
43.   `},`
    
44.   `);`
    
45.  `}`

运行看一下效果:
图片描述
可以看到这样就基本完成我们的需求了,但是有人说,能不能把我拖着的源控件加个特效?
没问题,我们通过 childWhenDragging参数来控制。
如,加个蒙层:

1.  `childWhenDragging:  Container(`
    
2.   `color:  Colors.blueAccent,`
    
3.   `alignment:  Alignment.center,`
    
4.   `foregroundDecoration:  BoxDecoration(color:  Colors.white30),`
    
5.   `child:  Text(`
    
6.   `_items[index],`
    
7.   `style:  TextStyle(color:  Colors.white),`
    
8.   `),`
    
9.  `),`

添加一个 foregroundDecoration 就ok了,效果如下:

总结

通过这个小例子我们可以实现特别多的效果。
而且默认拖动的控件时可以多指触控的,也就是说我们可以同时拖动N个控件。
可以通过 Draggable 的 maxSimultaneousDrags 来控制。
构造函数里其他的参数大家可以自行了解一下。

点击查看更多内容
1人点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
移动开发工程师
手记
粉丝
30
获赞与收藏
45

关注作者,订阅最新文章

阅读免费教程

感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消