drag_and_drop.dart 7.87 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 101
      size *= kHeavyMultiplier;

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

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

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

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

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

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

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

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

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

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

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

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

227
class DragAndDropApp extends StatefulWidget {
228
  const DragAndDropApp({super.key});
229

230
  @override
231
  DragAndDropAppState createState() => DragAndDropAppState();
232 233 234 235
}

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

237 238 239
  void moveBall(int newPosition) {
    setState(() { position = newPosition; });
  }
240 241

  @override
242
  Widget build(BuildContext context) {
243 244
    return Scaffold(
      appBar: AppBar(
245
        title: const Text('Drag and Drop Flutter Demo'),
246
      ),
247
      body: Column(
248
        children: <Widget>[
249 250
          Expanded(
            child: Row(
251
              mainAxisAlignment: MainAxisAlignment.spaceAround,
252
              children: <Widget>[
253
                ExampleDragSource(
254
                  color: Colors.yellow.shade300,
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
                  under: false,
266
                  child: const Text('above'),
267 268
                ),
              ],
269
            ),
270
          ),
271 272
          Expanded(
            child: Row(
273
              children: const <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(const MaterialApp(
Hixie's avatar
Hixie committed
299
    title: 'Drag and Drop Flutter Demo',
300
    home: DragAndDropApp(),
Hixie's avatar
Hixie committed
301
  ));
302
}