微信交流群

老孟公众号

# 拖拽组件

拖拽组件包含 DraggableLongPressDraggableDragTarget

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

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

# Draggable

Draggable 基础用法:

Draggable(
  child: Container(
    height: 100,
    width: 100,
    alignment: Alignment.center,
    decoration: BoxDecoration(
      color: Colors.red,
      borderRadius: BorderRadius.circular(10)
    ),
    child: Text('孟',style: TextStyle(color: Colors.white,fontSize: 18),),
  ),
  feedback: Container(
    height: 100,
    width: 100,
    alignment: Alignment.center,
    decoration: BoxDecoration(
        color: Colors.blue,
        borderRadius: BorderRadius.circular(10)
    ),
    child: Text('孟',style: TextStyle(color: Colors.white,fontSize: 18),),
  ),
)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

蓝色的组件是feedback,如果想在拖动的时候子组件显示其他样式可以使用childWhenDragging参数,用法如下:

Draggable(
  childWhenDragging: Container(
    height: 100,
    width: 100,
    alignment: Alignment.center,
    decoration: BoxDecoration(
        color: Colors.grey, borderRadius: BorderRadius.circular(10)),
    child: Text(
      '孟',
      style: TextStyle(color: Colors.white, fontSize: 18),
    ),
  ),
  ...
)
1
2
3
4
5
6
7
8
9
10
11
12
13
14

控制拖动的方向,比如只允许垂直方向移动,代码如下:

Draggable(
  axis: Axis.vertical,
  ...
)

1
2
3
4
5

Draggable组件为我们提供了4中拖动过程中的回调事件,用法如下:

Draggable(
  onDragStarted: (){
    print('onDragStarted');
  },
  onDragEnd: (DraggableDetails details){
    print('onDragEnd:$details');
  },
  onDraggableCanceled: (Velocity velocity, Offset offset){
    print('onDraggableCanceled velocity:$velocity,offset:$offset');
  },
  onDragCompleted: (){
    print('onDragCompleted');
  },
  ...
)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

说明如下:

  • onDragStarted:开始拖动时回调。
  • onDragEnd:拖动结束时回调。
  • onDraggableCanceled:未拖动到DragTarget控件上时回调。
  • onDragCompleted:拖动到DragTarget控件上时回调。

Draggable有一个data参数,这个参数是和DragTarget配合使用的,当用户将控件拖动到DragTarget时此数据会传递给DragTarget。

# DragTarget

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

DragTarget(
  builder: (BuildContext context, List<dynamic> candidateData,
      List<dynamic> rejectedData) {
      ...
  }
)

1
2
3
4
5
6
7

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

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

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

  • onWillAccept:拖到该控件上时调用,需要返回true或者false,返回true,松手后会回调onAccept,否则回调onLeave。
  • onAccept:onWillAccept返回true时,用户松手后调用。
  • onLeave:onWillAccept返回false时,用户松手后调用。
var _dragData;


Widget build(BuildContext context) {
  return Center(
    child: Column(
      children: <Widget>[
        _buildDraggable(),
        SizedBox(
          height: 200,
        ),
        DragTarget<Color>(
          builder: (BuildContext context, List<Color> candidateData,
              List<dynamic> rejectedData) {
            print('candidateData:$candidateData,rejectedData:$rejectedData');
            return _dragData == null
                ? Container(
                    height: 100,
                    width: 100,
                    alignment: Alignment.center,
                    decoration: BoxDecoration(
                        borderRadius: BorderRadius.circular(10),
                        border: Border.all(color: Colors.red)),
                  )
                : Container(
                    height: 100,
                    width: 100,
                    alignment: Alignment.center,
                    decoration: BoxDecoration(
                        color: Colors.red,
                        borderRadius: BorderRadius.circular(10)),
                    child: Text(
                      '孟',
                      style: TextStyle(color: Colors.white, fontSize: 18),
                    ),
                  );
          },
          onWillAccept: (Color color) {
            print('onWillAccept:$color');
            return true;
          },
          onAccept: (Color color) {
            setState(() {
              _dragData = color;
            });
            print('onAccept:$color');
          },
          onLeave: (Color color) {
            print('onLeave:$color');
          },
        ),
      ],
    ),
  );
}

_buildDraggable() {
  return Draggable(
    data: Color(0x000000FF),
    child: Container(
      height: 100,
      width: 100,
      alignment: Alignment.center,
      decoration: BoxDecoration(
          color: Colors.red, borderRadius: BorderRadius.circular(10)),
      child: Text(
        '孟',
        style: TextStyle(color: Colors.white, fontSize: 18),
      ),
    ),
    feedback: Container(
      height: 100,
      width: 100,
      alignment: Alignment.center,
      decoration: BoxDecoration(
          color: Colors.blue, borderRadius: BorderRadius.circular(10)),
      child: DefaultTextStyle.merge(
        style: TextStyle(color: Colors.white, fontSize: 18),
        child: Text(
          '孟',
        ),
      ),
    ),
  );
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86

Flutter 1.20 DragTarget 版本新增了精确下落位置,通过 DragTarget onAcceptDetails 方法获得该信息,下面是官方提供的相关用法:

import 'dart:ui' as ui;

import 'package:flutter/material.dart';

const _strokeWidth = 8.0;

///
/// desc:
///

class DragTargetDetailsExample extends StatefulWidget {
  
  _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)))));

  
  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);

  
  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));
  }

  
  bool shouldRepaint(_Painter oldPainter) => false;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143