拖拽组件包含 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;
@override
Widget 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)))));
@override
Widget 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);
@override
void 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));
}
@override
bool shouldRepaint(_Painter oldPainter) => false;
}