drag_and_drop.dart 7.89 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
2 3 4
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

5 6
import 'dart:math' as math;

7
import 'package:flutter/foundation.dart';
8
import 'package:flutter/material.dart';
9

10
class ExampleDragTarget extends StatefulWidget {
11
  @override
12
  ExampleDragTargetState createState() => ExampleDragTargetState();
13
}
14

15
class ExampleDragTargetState extends State<ExampleDragTarget> {
16
  Color _color = Colors.grey;
17

Hixie's avatar
Hixie committed
18
  void _handleAccept(Color data) {
19
    setState(() {
Hixie's avatar
Hixie committed
20
      _color = data;
21 22 23
    });
  }

24
  @override
25
  Widget build(BuildContext context) {
26
    return DragTarget<Color>(
27
      onAccept: _handleAccept,
28
      builder: (BuildContext context, List<Color> data, List<dynamic> rejectedData) {
29
        return Container(
30
          height: 100.0,
31
          margin: const EdgeInsets.all(10.0),
32
          decoration: BoxDecoration(
33
            color: data.isEmpty ? _color : Colors.grey.shade200,
34
            border: Border.all(
35
              width: 3.0,
36
              color: data.isEmpty ? Colors.white : Colors.blue,
37
            ),
38
          ),
39
        );
40
      },
41 42 43 44
    );
  }
}

45
class Dot extends StatefulWidget {
46
  const Dot({ Key key, this.color, this.size, this.child, this.tappable = false }) : super(key: key);
47

Hixie's avatar
Hixie committed
48
  final Color color;
49
  final double size;
Hixie's avatar
Hixie committed
50
  final Widget child;
51
  final bool tappable;
52 53

  @override
54
  DotState createState() => DotState();
55 56 57
}
class DotState extends State<Dot> {
  int taps = 0;
58 59

  @override
60
  Widget build(BuildContext context) {
61
    return GestureDetector(
62
      onTap: widget.tappable ? () { setState(() { taps += 1; }); } : null,
63
      child: Container(
64 65
        width: widget.size,
        height: widget.size,
66
        decoration: BoxDecoration(
67
          color: widget.color,
68
          border: Border.all(width: taps.toDouble()),
69
          shape: BoxShape.circle,
70
        ),
71 72
        child: widget.child,
      ),
73 74 75 76
    );
  }
}

77
class ExampleDragSource extends StatelessWidget {
78
  const ExampleDragSource({
Hixie's avatar
Hixie committed
79 80
    Key key,
    this.color,
81 82
    this.heavy = false,
    this.under = true,
83
    this.child,
Hixie's avatar
Hixie committed
84 85
  }) : super(key: key);

Hixie's avatar
Hixie committed
86
  final Color color;
Hixie's avatar
Hixie committed
87 88 89
  final bool heavy;
  final bool under;
  final Widget child;
90

Hixie's avatar
Hixie committed
91 92 93
  static const double kDotSize = 50.0;
  static const double kHeavyMultiplier = 1.5;
  static const double kFingerSize = 50.0;
94

95
  @override
Hixie's avatar
Hixie committed
96
  Widget build(BuildContext context) {
Hixie's avatar
Hixie committed
97
    double size = kDotSize;
98
    if (heavy)
Hixie's avatar
Hixie committed
99 100
      size *= kHeavyMultiplier;

101
    final Widget contents = DefaultTextStyle(
102
      style: Theme.of(context).textTheme.bodyText2,
103
      textAlign: TextAlign.center,
104
      child: Dot(
Hixie's avatar
Hixie committed
105 106
        color: color,
        size: size,
107 108
        child: Center(child: child),
      ),
Hixie's avatar
Hixie committed
109 110
    );

111
    Widget feedback = Opacity(
Hixie's avatar
Hixie committed
112
      opacity: 0.75,
113
      child: contents,
Hixie's avatar
Hixie committed
114 115 116 117 118
    );

    Offset feedbackOffset;
    DragAnchor anchor;
    if (!under) {
119 120
      feedback = Transform(
        transform: Matrix4.identity()
Hixie's avatar
Hixie committed
121
                     ..translate(-size / 2.0, -(size / 2.0 + kFingerSize)),
122
        child: feedback,
Hixie's avatar
Hixie committed
123 124 125 126 127 128 129 130
      );
      feedbackOffset = const Offset(0.0, -kFingerSize);
      anchor = DragAnchor.pointer;
    } else {
      feedbackOffset = Offset.zero;
      anchor = DragAnchor.child;
    }

131
    if (heavy) {
132
      return LongPressDraggable<Color>(
133 134 135 136
        data: color,
        child: contents,
        feedback: feedback,
        feedbackOffset: feedbackOffset,
137
        dragAnchor: anchor,
138 139
      );
    } else {
140
      return Draggable<Color>(
141 142 143 144
        data: color,
        child: contents,
        feedback: feedback,
        feedbackOffset: feedbackOffset,
145
        dragAnchor: anchor,
146 147
      );
    }
Hixie's avatar
Hixie committed
148 149 150
  }
}

151 152 153 154
class DashOutlineCirclePainter extends CustomPainter {
  const DashOutlineCirclePainter();

  static const int segments = 17;
155
  static const double deltaTheta = math.pi * 2 / segments; // radians
156 157 158
  static const double segmentArc = deltaTheta / 2.0; // radians
  static const double startOffset = 1.0; // radians

159
  @override
160 161
  void paint(Canvas canvas, Size size) {
    final double radius = size.shortestSide / 2.0;
162
    final Paint paint = Paint()
163
      ..color = const Color(0xFF000000)
164
      ..style = PaintingStyle.stroke
165
      ..strokeWidth = radius / 10.0;
166
    final Path path = Path();
167
    final Rect box = Offset.zero & size;
168
    for (double theta = 0.0; theta < math.pi * 2.0; theta += deltaTheta)
169 170 171 172
      path.addArc(box, theta + startOffset, segmentArc);
    canvas.drawPath(path, paint);
  }

173
  @override
174
  bool shouldRepaint(DashOutlineCirclePainter oldDelegate) => false;
175 176
}

177
class MovableBall extends StatelessWidget {
178
  const MovableBall(this.position, this.ballPosition, this.callback);
179

180 181 182
  final int position;
  final int ballPosition;
  final ValueChanged<int> callback;
183

184
  static final GlobalKey kBallKey = GlobalKey();
185
  static const double kBallSize = 50.0;
186

187
  @override
188
  Widget build(BuildContext context) {
189
    final Widget ball = DefaultTextStyle(
190
      style: Theme.of(context).primaryTextTheme.bodyText2,
191
      textAlign: TextAlign.center,
192
      child: Dot(
193
        key: kBallKey,
194
        color: Colors.blue.shade700,
195
        size: kBallSize,
196
        tappable: true,
197 198
        child: const Center(child: Text('BALL')),
      ),
199
    );
200
    final Widget dashedBall = Container(
201 202
      width: kBallSize,
      height: kBallSize,
203
      child: const CustomPaint(
204
        painter: DashOutlineCirclePainter()
205
      ),
206 207
    );
    if (position == ballPosition) {
208
      return Draggable<bool>(
209 210 211 212
        data: true,
        child: ball,
        childWhenDragging: dashedBall,
        feedback: ball,
213
        maxSimultaneousDrags: 1,
214 215
      );
    } else {
216
      return DragTarget<bool>(
217
        onAccept: (bool data) { callback(position); },
218
        builder: (BuildContext context, List<bool> accepted, List<dynamic> rejected) {
219
          return dashedBall;
220
        },
221 222 223 224 225
      );
    }
  }
}

226
class DragAndDropApp extends StatefulWidget {
227
  @override
228
  DragAndDropAppState createState() => DragAndDropAppState();
229 230 231 232
}

class DragAndDropAppState extends State<DragAndDropApp> {
  int position = 1;
233

234 235 236
  void moveBall(int newPosition) {
    setState(() { position = newPosition; });
  }
237 238

  @override
239
  Widget build(BuildContext context) {
240 241
    return Scaffold(
      appBar: AppBar(
242
        title: const Text('Drag and Drop Flutter Demo'),
243
      ),
244
      body: Column(
245
        children: <Widget>[
246 247
          Expanded(
            child: Row(
248 249
              crossAxisAlignment: CrossAxisAlignment.center,
              mainAxisAlignment: MainAxisAlignment.spaceAround,
250
              children: <Widget>[
251
                ExampleDragSource(
252
                  color: Colors.yellow.shade300,
253 254
                  under: true,
                  heavy: false,
255
                  child: const Text('under'),
256
                ),
257
                ExampleDragSource(
258
                  color: Colors.green.shade300,
259 260
                  under: false,
                  heavy: true,
261
                  child: const Text('long-press above'),
262
                ),
263
                ExampleDragSource(
264
                  color: Colors.indigo.shade300,
265 266
                  under: false,
                  heavy: false,
267
                  child: const Text('above'),
268 269
                ),
              ],
270
            ),
271
          ),
272 273
          Expanded(
            child: Row(
274
              children: <Widget>[
275 276 277 278
                Expanded(child: ExampleDragTarget()),
                Expanded(child: ExampleDragTarget()),
                Expanded(child: ExampleDragTarget()),
                Expanded(child: ExampleDragTarget()),
279 280
              ],
            ),
281
          ),
282 283
          Expanded(
            child: Row(
284
              mainAxisAlignment: MainAxisAlignment.spaceAround,
285
              children: <Widget>[
286 287 288
                MovableBall(1, position, moveBall),
                MovableBall(2, position, moveBall),
                MovableBall(3, position, moveBall),
289
              ],
290
            ),
291
          ),
292 293
        ],
      ),
294 295 296 297
    );
  }
}

298
void main() {
299
  runApp(MaterialApp(
Hixie's avatar
Hixie committed
300
    title: 'Drag and Drop Flutter Demo',
301
    home: DragAndDropApp(),
Hixie's avatar
Hixie committed
302
  ));
303
}