拖拽组件包含 Draggable 、LongPressDraggable 和 DragTarget。

Draggable、LongPressDraggable 为可拖拽的组件,LongPressDraggable 继承自Draggable,因此用法和 Draggable 完全一样,唯一的区别就是 LongPressDraggable 触发拖动的方式是长按,而 Draggable 触发拖动的方式是按下。

DragTarget 为拖拽组件的目的地组件。

Draggable

定义如下:

  1. Draggable({
  2. Key key,
  3. @required this.child, // 原始组件
  4. @required this.feedback, // 拖拽中,临时的显示一个随拖拽运动的组件,拖拽结束后消失
  5. this.data, //和DragTarget配合使用,当用户将控件拖动到DragTarget时此数据会传递给DragTarget。
  6. this.axis, //控制拖动的方向。如只允许垂直拖动 axis: Axis.vertical,
  7. this.childWhenDragging, //拖拽中,控制原始组件显示其他样式
  8. this.feedbackOffset = Offset.zero,
  9. this.dragAnchor = DragAnchor.child,
  10. this.affinity,
  11. this.maxSimultaneousDrags,
  12. this.onDragStarted, //开始拖动时回调。 onDragStarted: (){}
  13. this.onDraggableCanceled, //未拖动到DragTarget控件上时回调。 onDraggableCanceled: (Velocity velocity, Offset offset){}
  14. this.onDragEnd, //拖动结束时回调。 onDragEnd: (DraggableDetails details){}
  15. this.onDragCompleted, //拖动到DragTarget控件上时回调。 onDragCompleted: (){}
  16. this.ignoringFeedbackSemantics = true,
  17. })

简单使用

asdf.gif

  1. Draggable(
  2. // 原始组件
  3. child: Container(
  4. width: 100,
  5. height: 100,
  6. color: Colors.red,
  7. child: Text('原始组件'),
  8. ),
  9. //拖拽中,控制原始组件显示其他样式
  10. childWhenDragging: Container(
  11. height: 100,
  12. width: 100,
  13. color: Colors.grey,
  14. child: Text('原始组件 改变样式'),
  15. ),
  16. // 拖拽中,临时的显示一个随拖拽运动的组件,拖拽结束后消失
  17. feedback: Container(
  18. width: 100,
  19. height: 100,
  20. color: Colors.red.withOpacity(0.3),
  21. child: Text('拖拽中'),
  22. ),
  23. ),

DragTarget

DragTarget就像他的名字一样,指定一个目的地,Draggable组件可以拖动到此控件。定义如下:

  1. DragTarget({
  2. Key key,
  3. @required this.builder,
  4. //Widget Function(BuildContext context, List<T> candidateData, List<dynamic> rejectedData);
  5. this.onWillAccept,
  6. this.onAccept,
  7. this.onAcceptWithDetails,
  8. this.onLeave,
  9. this.onMove,
  10. })

onWillAccept返回true时, candidateData参数的数据是Draggable的data数据。
onWillAccept返回false时, rejectedData参数的数据是Draggable的data数据。

DragTarget有几个回调,说明如下:

  • onWillAccept:拖到该控件上时调用,需要返回true或者false,返回true,松手后会回调onAccept,否则回调onLeave。
  • onAccept:onWillAccept返回true时,用户松手后调用。
  • onLeave:onWillAccept返回false时,用户松手后调用。
  • onAcceptWithDetails:精准下落位置回调,onWillAccept返回true时,用户松手后调用。

示例

asdf.gif

  1. \import 'package:flutter/cupertino.dart';
  2. import "package:flutter/material.dart";
  3. import 'package:app1/coms/base/empty_null/empty_null.dart';
  4. class TestPage extends StatefulWidget {
  5. @override
  6. _TestPageState createState() => _TestPageState();
  7. }
  8. class _TestPageState extends State<TestPage> {
  9. var _dragData;
  10. @override
  11. Widget build(BuildContext context) {
  12. return Scaffold(
  13. appBar: EmptyNull.appBar(),
  14. body: Container(
  15. width: double.infinity,
  16. child: Column(
  17. children: [
  18. Draggable(
  19. data: Color(0x000000FF),
  20. child: Container(
  21. width: 100,
  22. height: 100,
  23. color: Colors.red,
  24. child: Text('原始组件'),
  25. ),
  26. feedback: Container(
  27. width: 100,
  28. height: 100,
  29. color: Colors.red.withOpacity(0.3),
  30. child: Text('拖拽中'),
  31. ),
  32. ),
  33. SizedBox(height: 200),
  34. DragTarget<Color>(
  35. builder: (context, List<Color> candidateData, List<dynamic> rejectedData) {
  36. print('candidateData => $candidateData');
  37. print('rejectedData => $rejectedData');
  38. return Container(
  39. width: 100,
  40. height: 100,
  41. decoration: _dragData == null
  42. ? BoxDecoration(
  43. border: Border.all(color: Colors.red),
  44. )
  45. : BoxDecoration(
  46. color: Colors.red,
  47. ),
  48. child: Text('目标'),
  49. );
  50. },
  51. onWillAccept: (Color data) {
  52. print('onWillAccept => $data');
  53. return true;
  54. },
  55. onAccept: (Color data) {
  56. setState(() {
  57. _dragData = data;
  58. });
  59. print('onAccept => $data');
  60. },
  61. onLeave: (Object data) {
  62. print('onLeave: => $data');
  63. },
  64. onAcceptWithDetails: (DragTargetDetails<Color> details) {
  65. print('onAcceptWithDetails => ${details.offset}');
  66. },
  67. ),
  68. ],
  69. ),
  70. ),
  71. );
  72. }
  73. }

官方示例

拖拽组件 - 图3

  1. import 'dart:ui' as ui;
  2. import 'package:flutter/material.dart';
  3. const _strokeWidth = 8.0;
  4. class DragTargetDetailsExample extends StatefulWidget {
  5. @override
  6. _DragTargetDetailsExampleState createState() =>
  7. _DragTargetDetailsExampleState();
  8. }
  9. class _DragTargetDetailsExampleState extends State<DragTargetDetailsExample> {
  10. static const _feedbackSize = Size(100.0, 100.0);
  11. static const _padding = 16.0;
  12. static final _decoration = BoxDecoration(
  13. border: Border.all(
  14. color: Colors.blue,
  15. width: _strokeWidth,
  16. ),
  17. borderRadius: BorderRadius.circular(12),
  18. );
  19. Offset _lastDropOffset;
  20. int _lastDropIndex;
  21. Widget _buildSourceRowChild(int index) => Expanded(
  22. child: Padding(
  23. padding: EdgeInsets.all(_padding),
  24. child: Draggable<int>(
  25. data: index,
  26. dragAnchor: DragAnchor.pointer,
  27. feedback: Transform.translate(
  28. offset: Offset(
  29. -_feedbackSize.width / 2.0, -_feedbackSize.height / 2.0),
  30. child: Container(
  31. decoration: _decoration,
  32. width: _feedbackSize.width,
  33. height: _feedbackSize.height)),
  34. child: Container(
  35. decoration: _decoration,
  36. child: Column(
  37. mainAxisAlignment: MainAxisAlignment.center,
  38. children: [
  39. Text('drag me'),
  40. Text('$index', style: TextStyle(fontSize: 32.0))
  41. ])))));
  42. void _handleAcceptWithDetails(
  43. BuildContext dragTargetContext, DragTargetDetails details) {
  44. // Convert global to local coordinates.
  45. RenderBox renderBox = dragTargetContext.findRenderObject();
  46. final localOffset = renderBox.globalToLocal(details.offset);
  47. setState(() {
  48. _lastDropOffset = localOffset;
  49. _lastDropIndex = details.data;
  50. });
  51. }
  52. Widget _buildDragTargetChild() => Padding(
  53. padding: EdgeInsets.all(_padding),
  54. child: Container(
  55. decoration: _decoration,
  56. // Note use of builder to get a context for the [DragTarget] which is
  57. // available to pass to [_handleAcceptWithDetails].
  58. child: Builder(
  59. builder: (targetContext) => DragTarget<int>(
  60. builder: (_, candidateData, __) => Container(
  61. color: candidateData.isNotEmpty
  62. ? Color(0x2200FF00)
  63. : Color(0x00000000),
  64. child: CustomPaint(
  65. painter: _Painter(_lastDropOffset, _lastDropIndex))),
  66. onAcceptWithDetails: (details) =>
  67. _handleAcceptWithDetails(targetContext, details)))));
  68. @override
  69. Widget build(BuildContext context) {
  70. return Scaffold(
  71. appBar: AppBar(),
  72. body: Column(crossAxisAlignment: CrossAxisAlignment.stretch, children: [
  73. Expanded(
  74. flex: 1,
  75. child: Row(
  76. children: List<Widget>.generate(3, _buildSourceRowChild))),
  77. Expanded(flex: 4, child: _buildDragTargetChild())
  78. ]));
  79. }
  80. }
  81. class _Painter extends CustomPainter {
  82. static final _diameter = 24.0;
  83. static final _linePaint = Paint()
  84. ..style = PaintingStyle.stroke
  85. ..strokeWidth = _strokeWidth
  86. ..color = Colors.blue;
  87. static final _whiteFillPaint = Paint()
  88. ..style = PaintingStyle.fill
  89. ..color = Colors.white;
  90. static final _blueFillPaint = Paint()
  91. ..style = PaintingStyle.fill
  92. ..color = Colors.blue;
  93. final Offset _offset;
  94. final int _index;
  95. _Painter(this._offset, this._index);
  96. @override
  97. void paint(Canvas canvas, Size size) {
  98. if (_offset == null || _index == null) return;
  99. canvas.drawLine(
  100. Offset(_offset.dx, 0.0), Offset(_offset.dx, size.height), _linePaint);
  101. canvas.drawLine(
  102. Offset(0.0, _offset.dy), Offset(size.width, _offset.dy), _linePaint);
  103. canvas.drawCircle(_offset, _diameter + _strokeWidth, _blueFillPaint);
  104. canvas.drawCircle(_offset, _diameter, _whiteFillPaint);
  105. final paragraphBuilder =
  106. ui.ParagraphBuilder(ui.ParagraphStyle(textAlign: TextAlign.center))
  107. ..pushStyle(ui.TextStyle(
  108. fontStyle: FontStyle.normal,
  109. color: Colors.blue,
  110. fontSize: _diameter))
  111. ..addText('$_index');
  112. final paragraph = paragraphBuilder.build();
  113. paragraph.layout(ui.ParagraphConstraints(width: _diameter));
  114. canvas.drawParagraph(
  115. paragraph, _offset - Offset(_diameter / 2.0, _diameter / 2.0));
  116. }
  117. @override
  118. bool shouldRepaint(_Painter oldPainter) => false;
  119. }