drag_and_drop.dart 8.32 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
import 'dart:io';
6 7
import 'dart:math' as math;

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

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

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

Hixie's avatar
Hixie committed
19
  void _handleAccept(Color data) {
20
    setState(() {
Hixie's avatar
Hixie committed
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({ Key key, this.color, this.size, this.child, this.tappable = false }) : super(key: key);
48

Hixie's avatar
Hixie committed
49
  final Color color;
50
  final double size;
Hixie's avatar
Hixie committed
51
  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({
Hixie's avatar
Hixie committed
80 81
    Key key,
    this.color,
82 83
    this.heavy = false,
    this.under = true,
84
    this.child,
Hixie's avatar
Hixie committed
85 86
  }) : super(key: key);

Hixie's avatar
Hixie committed
87
  final Color color;
Hixie's avatar
Hixie committed
88 89 90
  final bool heavy;
  final bool under;
  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 104
      style: Theme.of(context).textTheme.body1,
      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 118 119
    );

    Offset feedbackOffset;
    DragAnchor anchor;
    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 126 127 128 129 130 131
      );
      feedbackOffset = const Offset(0.0, -kFingerSize);
      anchor = DragAnchor.pointer;
    } else {
      feedbackOffset = Offset.zero;
      anchor = DragAnchor.child;
    }

132
    if (heavy) {
133
      return LongPressDraggable<Color>(
134 135 136 137
        data: color,
        child: contents,
        feedback: feedback,
        feedbackOffset: feedbackOffset,
138
        dragAnchor: anchor,
139 140
      );
    } else {
141
      return Draggable<Color>(
142 143 144 145
        data: color,
        child: contents,
        feedback: feedback,
        feedbackOffset: feedbackOffset,
146
        dragAnchor: anchor,
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);
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.body1,
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
    final Widget dashedBall = Container(
202 203
      width: kBallSize,
      height: kBallSize,
204
      child: const CustomPaint(
205
        painter: DashOutlineCirclePainter()
206
      ),
207 208
    );
    if (position == ballPosition) {
209
      return Draggable<bool>(
210 211 212 213
        data: true,
        child: ball,
        childWhenDragging: dashedBall,
        feedback: ball,
214
        maxSimultaneousDrags: 1,
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
  @override
229
  DragAndDropAppState createState() => DragAndDropAppState();
230 231 232 233
}

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

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

  @override
240
  Widget build(BuildContext context) {
241 242
    return Scaffold(
      appBar: AppBar(
243
        title: const Text('Drag and Drop Flutter Demo'),
244
      ),
245
      body: Column(
246
        children: <Widget>[
247 248
          Expanded(
            child: Row(
249 250
              crossAxisAlignment: CrossAxisAlignment.center,
              mainAxisAlignment: MainAxisAlignment.spaceAround,
251
              children: <Widget>[
252
                ExampleDragSource(
253
                  color: Colors.yellow.shade300,
254 255
                  under: true,
                  heavy: false,
256
                  child: const Text('under'),
257
                ),
258
                ExampleDragSource(
259
                  color: Colors.green.shade300,
260 261
                  under: false,
                  heavy: true,
262
                  child: const Text('long-press above'),
263
                ),
264
                ExampleDragSource(
265
                  color: Colors.indigo.shade300,
266 267
                  under: false,
                  heavy: false,
268
                  child: const Text('above'),
269 270
                ),
              ],
271
            ),
272
          ),
273 274
          Expanded(
            child: Row(
275
              children: <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 300 301 302 303
// Sets a platform override for desktop to avoid exceptions. See
// https://flutter.dev/desktop#target-platform-override for more info.
// TODO(gspencergoog): Remove once TargetPlatform includes all desktop platforms.
void _enablePlatformOverrideForDesktop() {
  if (!kIsWeb && (Platform.isWindows || Platform.isLinux)) {
304 305
    debugDefaultTargetPlatformOverride = TargetPlatform.fuchsia;
  }
306
}
307

308 309
void main() {
  _enablePlatformOverrideForDesktop();
310
  runApp(MaterialApp(
Hixie's avatar
Hixie committed
311
    title: 'Drag and Drop Flutter Demo',
312
    home: DragAndDropApp(),
Hixie's avatar
Hixie committed
313
  ));
314
}