drag_and_drop.dart 7.88 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/material.dart';
8

9
class ExampleDragTarget extends StatefulWidget {
10
  const ExampleDragTarget({super.key});
11

12
  @override
13
  ExampleDragTargetState createState() => ExampleDragTargetState();
14
}
15

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

19
  void _handleAccept(Color data) {
20
    setState(() {
21
      _color = data;
22 23 24
    });
  }

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

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

49 50 51
  final Color? color;
  final double? size;
  final Widget? child;
52
  final bool tappable;
53 54

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

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

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

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

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

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

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

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

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

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

153 154 155 156
class DashOutlineCirclePainter extends CustomPainter {
  const DashOutlineCirclePainter();

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

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

176
  @override
177
  bool shouldRepaint(DashOutlineCirclePainter oldDelegate) => false;
178 179
}

180
class MovableBall extends StatelessWidget {
181
  const MovableBall(this.position, this.ballPosition, this.callback, {super.key});
182

183 184 185
  final int position;
  final int ballPosition;
  final ValueChanged<int> callback;
186

187
  static final GlobalKey kBallKey = GlobalKey();
188
  static const double kBallSize = 50.0;
189

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

229
class DragAndDropApp extends StatefulWidget {
230
  const DragAndDropApp({super.key});
231

232
  @override
233
  DragAndDropAppState createState() => DragAndDropAppState();
234 235 236 237
}

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

239 240 241
  void moveBall(int newPosition) {
    setState(() { position = newPosition; });
  }
242 243

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

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