不多说,我们肯定遇到过这个需求:
把一个控件从当前位置移动到另一个位置。可能需求最多的就像是支付宝应用页面的编辑:
比如,我想把最近使用的 红包 添加到 我的应用 当中,支付宝这里是用的 + 号。
那如果产品说我就想让它拖过去,这可咋整?
不慌,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
来控制。
构造函数里其他的参数大家可以自行了解一下。
热门评论
跟我需求太贴合了 有没有demo?