drag_and_drop.dart 7.84 KB
Newer Older
1 2 3 4
// Copyright 2015 The Chromium Authors. All rights reserved.
// 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/material.dart';
8

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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