拖拽组件包含 Draggable 、LongPressDraggable 和 DragTarget。
Draggable、LongPressDraggable 为可拖拽的组件,LongPressDraggable 继承自Draggable,因此用法和 Draggable 完全一样,唯一的区别就是 LongPressDraggable 触发拖动的方式是长按,而 Draggable 触发拖动的方式是按下。
DragTarget 为拖拽组件的目的地组件。
Draggable
定义如下:
Draggable({Key key,@required this.child, // 原始组件@required this.feedback, // 拖拽中,临时的显示一个随拖拽运动的组件,拖拽结束后消失this.data, //和DragTarget配合使用,当用户将控件拖动到DragTarget时此数据会传递给DragTarget。this.axis, //控制拖动的方向。如只允许垂直拖动 axis: Axis.vertical,this.childWhenDragging, //拖拽中,控制原始组件显示其他样式this.feedbackOffset = Offset.zero,this.dragAnchor = DragAnchor.child,this.affinity,this.maxSimultaneousDrags,this.onDragStarted, //开始拖动时回调。 onDragStarted: (){}this.onDraggableCanceled, //未拖动到DragTarget控件上时回调。 onDraggableCanceled: (Velocity velocity, Offset offset){}this.onDragEnd, //拖动结束时回调。 onDragEnd: (DraggableDetails details){}this.onDragCompleted, //拖动到DragTarget控件上时回调。 onDragCompleted: (){}this.ignoringFeedbackSemantics = true,})
简单使用

Draggable(// 原始组件child: Container(width: 100,height: 100,color: Colors.red,child: Text('原始组件'),),//拖拽中,控制原始组件显示其他样式childWhenDragging: Container(height: 100,width: 100,color: Colors.grey,child: Text('原始组件 改变样式'),),// 拖拽中,临时的显示一个随拖拽运动的组件,拖拽结束后消失feedback: Container(width: 100,height: 100,color: Colors.red.withOpacity(0.3),child: Text('拖拽中'),),),
DragTarget
DragTarget就像他的名字一样,指定一个目的地,Draggable组件可以拖动到此控件。定义如下:
DragTarget({Key key,@required this.builder,//Widget Function(BuildContext context, List<T> candidateData, List<dynamic> rejectedData);this.onWillAccept,this.onAccept,this.onAcceptWithDetails,this.onLeave,this.onMove,})
当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时,用户松手后调用。
示例

\import 'package:flutter/cupertino.dart';import "package:flutter/material.dart";import 'package:app1/coms/base/empty_null/empty_null.dart';class TestPage extends StatefulWidget {@override_TestPageState createState() => _TestPageState();}class _TestPageState extends State<TestPage> {var _dragData;@overrideWidget build(BuildContext context) {return Scaffold(appBar: EmptyNull.appBar(),body: Container(width: double.infinity,child: Column(children: [Draggable(data: Color(0x000000FF),child: Container(width: 100,height: 100,color: Colors.red,child: Text('原始组件'),),feedback: Container(width: 100,height: 100,color: Colors.red.withOpacity(0.3),child: Text('拖拽中'),),),SizedBox(height: 200),DragTarget<Color>(builder: (context, List<Color> candidateData, List<dynamic> rejectedData) {print('candidateData => $candidateData');print('rejectedData => $rejectedData');return Container(width: 100,height: 100,decoration: _dragData == null? BoxDecoration(border: Border.all(color: Colors.red),): BoxDecoration(color: Colors.red,),child: Text('目标'),);},onWillAccept: (Color data) {print('onWillAccept => $data');return true;},onAccept: (Color data) {setState(() {_dragData = data;});print('onAccept => $data');},onLeave: (Object data) {print('onLeave: => $data');},onAcceptWithDetails: (DragTargetDetails<Color> details) {print('onAcceptWithDetails => ${details.offset}');},),],),),);}}
官方示例

import 'dart:ui' as ui;import 'package:flutter/material.dart';const _strokeWidth = 8.0;class DragTargetDetailsExample extends StatefulWidget {@override_DragTargetDetailsExampleState createState() =>_DragTargetDetailsExampleState();}class _DragTargetDetailsExampleState extends State<DragTargetDetailsExample> {static const _feedbackSize = Size(100.0, 100.0);static const _padding = 16.0;static final _decoration = BoxDecoration(border: Border.all(color: Colors.blue,width: _strokeWidth,),borderRadius: BorderRadius.circular(12),);Offset _lastDropOffset;int _lastDropIndex;Widget _buildSourceRowChild(int index) => Expanded(child: Padding(padding: EdgeInsets.all(_padding),child: Draggable<int>(data: index,dragAnchor: DragAnchor.pointer,feedback: Transform.translate(offset: Offset(-_feedbackSize.width / 2.0, -_feedbackSize.height / 2.0),child: Container(decoration: _decoration,width: _feedbackSize.width,height: _feedbackSize.height)),child: Container(decoration: _decoration,child: Column(mainAxisAlignment: MainAxisAlignment.center,children: [Text('drag me'),Text('$index', style: TextStyle(fontSize: 32.0))])))));void _handleAcceptWithDetails(BuildContext dragTargetContext, DragTargetDetails details) {// Convert global to local coordinates.RenderBox renderBox = dragTargetContext.findRenderObject();final localOffset = renderBox.globalToLocal(details.offset);setState(() {_lastDropOffset = localOffset;_lastDropIndex = details.data;});}Widget _buildDragTargetChild() => Padding(padding: EdgeInsets.all(_padding),child: Container(decoration: _decoration,// Note use of builder to get a context for the [DragTarget] which is// available to pass to [_handleAcceptWithDetails].child: Builder(builder: (targetContext) => DragTarget<int>(builder: (_, candidateData, __) => Container(color: candidateData.isNotEmpty? Color(0x2200FF00): Color(0x00000000),child: CustomPaint(painter: _Painter(_lastDropOffset, _lastDropIndex))),onAcceptWithDetails: (details) =>_handleAcceptWithDetails(targetContext, details)))));@overrideWidget build(BuildContext context) {return Scaffold(appBar: AppBar(),body: Column(crossAxisAlignment: CrossAxisAlignment.stretch, children: [Expanded(flex: 1,child: Row(children: List<Widget>.generate(3, _buildSourceRowChild))),Expanded(flex: 4, child: _buildDragTargetChild())]));}}class _Painter extends CustomPainter {static final _diameter = 24.0;static final _linePaint = Paint()..style = PaintingStyle.stroke..strokeWidth = _strokeWidth..color = Colors.blue;static final _whiteFillPaint = Paint()..style = PaintingStyle.fill..color = Colors.white;static final _blueFillPaint = Paint()..style = PaintingStyle.fill..color = Colors.blue;final Offset _offset;final int _index;_Painter(this._offset, this._index);@overridevoid paint(Canvas canvas, Size size) {if (_offset == null || _index == null) return;canvas.drawLine(Offset(_offset.dx, 0.0), Offset(_offset.dx, size.height), _linePaint);canvas.drawLine(Offset(0.0, _offset.dy), Offset(size.width, _offset.dy), _linePaint);canvas.drawCircle(_offset, _diameter + _strokeWidth, _blueFillPaint);canvas.drawCircle(_offset, _diameter, _whiteFillPaint);final paragraphBuilder =ui.ParagraphBuilder(ui.ParagraphStyle(textAlign: TextAlign.center))..pushStyle(ui.TextStyle(fontStyle: FontStyle.normal,color: Colors.blue,fontSize: _diameter))..addText('$_index');final paragraph = paragraphBuilder.build();paragraph.layout(ui.ParagraphConstraints(width: _diameter));canvas.drawParagraph(paragraph, _offset - Offset(_diameter / 2.0, _diameter / 2.0));}@overridebool shouldRepaint(_Painter oldPainter) => false;}
