drag_and_drop.dart 8.12 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() => new ExampleDragTargetState();
12
}
13

14
class ExampleDragTargetState extends State<ExampleDragTarget> {
Hixie's avatar
Hixie committed
15
  Color _color = Colors.grey[500];
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) {
Hixie's avatar
Hixie committed
25
    return new DragTarget<Color>(
26
      onAccept: _handleAccept,
27
      builder: (BuildContext context, List<Color> data, List<Color> rejectedData) {
28 29
        return new Container(
          height: 100.0,
30
          margin: new EdgeInsets.all(10.0),
31 32 33
          decoration: new BoxDecoration(
            border: new Border.all(
              width: 3.0,
34
              color: data.isEmpty ? Colors.white : Colors.blue[500]
35
            ),
Hixie's avatar
Hixie committed
36
            backgroundColor: data.isEmpty ? _color : Colors.grey[200]
37 38 39 40 41 42 43
          )
        );
      }
    );
  }
}

44
class Dot extends StatefulWidget {
45
  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 54 55 56
  DotState createState() => new DotState();
}
class DotState extends State<Dot> {
  int taps = 0;
57 58

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

76
class ExampleDragSource extends StatelessWidget {
Hixie's avatar
Hixie committed
77 78 79 80 81 82 83 84
  ExampleDragSource({
    Key key,
    this.color,
    this.heavy: false,
    this.under: true,
    this.child
  }) : 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 100
      size *= kHeavyMultiplier;

    Widget contents = new DefaultTextStyle(
101
      style: Theme.of(context).textTheme.body1.copyWith(textAlign: TextAlign.center),
Hixie's avatar
Hixie committed
102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128
      child: new Dot(
        color: color,
        size: size,
        child: new Center(child: child)
      )
    );

    Widget feedback = new Opacity(
      opacity: 0.75,
      child: contents
    );

    Offset feedbackOffset;
    DragAnchor anchor;
    if (!under) {
      feedback = new Transform(
        transform: new Matrix4.identity()
                     ..translate(-size / 2.0, -(size / 2.0 + kFingerSize)),
        child: feedback
      );
      feedbackOffset = const Offset(0.0, -kFingerSize);
      anchor = DragAnchor.pointer;
    } else {
      feedbackOffset = Offset.zero;
      anchor = DragAnchor.child;
    }

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

149 150 151 152 153 154 155 156
class DashOutlineCirclePainter extends CustomPainter {
  const DashOutlineCirclePainter();

  static const int segments = 17;
  static const double deltaTheta = math.PI * 2 / segments; // radians
  static const double segmentArc = deltaTheta / 2.0; // radians
  static const double startOffset = 1.0; // radians

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

171
  @override
172 173 174
  bool shouldRepaint(DashOutlineCirclePainter oldPainter) => false;
}

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

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

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

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

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

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

void main() {
Adam Barth's avatar
Adam Barth committed
299
  runApp(new MaterialApp(
Hixie's avatar
Hixie committed
300
    title: 'Drag and Drop Flutter Demo',
301 302
    routes: <String, WidgetBuilder>{
     '/': (BuildContext context) => new DragAndDropApp()
Hixie's avatar
Hixie committed
303 304
    }
  ));
305
}