1// Copyright 2015 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5import 'dart:math' as math; 6 7import 'package:flutter/material.dart'; 8 9class ExampleDragTarget extends StatefulWidget { 10 @override 11 ExampleDragTargetState createState() => ExampleDragTargetState(); 12} 13 14class ExampleDragTargetState extends State<ExampleDragTarget> { 15 Color _color = Colors.grey; 16 17 void _handleAccept(Color data) { 18 setState(() { 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 44class Dot extends StatefulWidget { 45 const Dot({ Key key, this.color, this.size, this.child, this.tappable = false }) : super(key: key); 46 47 final Color color; 48 final double size; 49 final Widget child; 50 final bool tappable; 51 52 @override 53 DotState createState() => DotState(); 54} 55class DotState extends State<Dot> { 56 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 width: widget.size, 64 height: widget.size, 65 decoration: BoxDecoration( 66 color: widget.color, 67 border: Border.all(width: taps.toDouble()), 68 shape: BoxShape.circle, 69 ), 70 child: widget.child, 71 ), 72 ); 73 } 74} 75 76class ExampleDragSource extends StatelessWidget { 77 const ExampleDragSource({ 78 Key key, 79 this.color, 80 this.heavy = false, 81 this.under = true, 82 this.child, 83 }) : super(key: key); 84 85 final Color color; 86 final bool heavy; 87 final bool under; 88 final Widget child; 89 90 static const double kDotSize = 50.0; 91 static const double kHeavyMultiplier = 1.5; 92 static const double kFingerSize = 50.0; 93 94 @override 95 Widget build(BuildContext context) { 96 double size = kDotSize; 97 if (heavy) 98 size *= kHeavyMultiplier; 99 100 final Widget contents = DefaultTextStyle( 101 style: Theme.of(context).textTheme.body1, 102 textAlign: TextAlign.center, 103 child: Dot( 104 color: color, 105 size: size, 106 child: Center(child: child), 107 ), 108 ); 109 110 Widget feedback = Opacity( 111 opacity: 0.75, 112 child: contents, 113 ); 114 115 Offset feedbackOffset; 116 DragAnchor anchor; 117 if (!under) { 118 feedback = Transform( 119 transform: Matrix4.identity() 120 ..translate(-size / 2.0, -(size / 2.0 + kFingerSize)), 121 child: feedback, 122 ); 123 feedbackOffset = const Offset(0.0, -kFingerSize); 124 anchor = DragAnchor.pointer; 125 } else { 126 feedbackOffset = Offset.zero; 127 anchor = DragAnchor.child; 128 } 129 130 if (heavy) { 131 return LongPressDraggable<Color>( 132 data: color, 133 child: contents, 134 feedback: feedback, 135 feedbackOffset: feedbackOffset, 136 dragAnchor: anchor, 137 ); 138 } else { 139 return Draggable<Color>( 140 data: color, 141 child: contents, 142 feedback: feedback, 143 feedbackOffset: feedbackOffset, 144 dragAnchor: anchor, 145 ); 146 } 147 } 148} 149 150class DashOutlineCirclePainter extends CustomPainter { 151 const DashOutlineCirclePainter(); 152 153 static const int segments = 17; 154 static const double deltaTheta = math.pi * 2 / segments; // radians 155 static const double segmentArc = deltaTheta / 2.0; // radians 156 static const double startOffset = 1.0; // radians 157 158 @override 159 void paint(Canvas canvas, Size size) { 160 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 path.addArc(box, theta + startOffset, segmentArc); 169 canvas.drawPath(path, paint); 170 } 171 172 @override 173 bool shouldRepaint(DashOutlineCirclePainter oldDelegate) => false; 174} 175 176class MovableBall extends StatelessWidget { 177 const MovableBall(this.position, this.ballPosition, this.callback); 178 179 final int position; 180 final int ballPosition; 181 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 child: const Center(child: Text('BALL')), 197 ), 198 ); 199 final Widget dashedBall = Container( 200 width: kBallSize, 201 height: kBallSize, 202 child: const CustomPaint( 203 painter: DashOutlineCirclePainter() 204 ), 205 ); 206 if (position == ballPosition) { 207 return Draggable<bool>( 208 data: true, 209 child: ball, 210 childWhenDragging: dashedBall, 211 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 225class DragAndDropApp extends StatefulWidget { 226 @override 227 DragAndDropAppState createState() => DragAndDropAppState(); 228} 229 230class DragAndDropAppState extends State<DragAndDropApp> { 231 int position = 1; 232 233 void moveBall(int newPosition) { 234 setState(() { position = newPosition; }); 235 } 236 237 @override 238 Widget build(BuildContext context) { 239 return Scaffold( 240 appBar: AppBar( 241 title: const Text('Drag and Drop Flutter Demo'), 242 ), 243 body: Column( 244 children: <Widget>[ 245 Expanded( 246 child: Row( 247 crossAxisAlignment: CrossAxisAlignment.center, 248 mainAxisAlignment: MainAxisAlignment.spaceAround, 249 children: <Widget>[ 250 ExampleDragSource( 251 color: Colors.yellow.shade300, 252 under: true, 253 heavy: false, 254 child: const Text('under'), 255 ), 256 ExampleDragSource( 257 color: Colors.green.shade300, 258 under: false, 259 heavy: true, 260 child: const Text('long-press above'), 261 ), 262 ExampleDragSource( 263 color: Colors.indigo.shade300, 264 under: false, 265 heavy: false, 266 child: const Text('above'), 267 ), 268 ], 269 ), 270 ), 271 Expanded( 272 child: Row( 273 children: <Widget>[ 274 Expanded(child: ExampleDragTarget()), 275 Expanded(child: ExampleDragTarget()), 276 Expanded(child: ExampleDragTarget()), 277 Expanded(child: ExampleDragTarget()), 278 ], 279 ), 280 ), 281 Expanded( 282 child: Row( 283 mainAxisAlignment: MainAxisAlignment.spaceAround, 284 children: <Widget>[ 285 MovableBall(1, position, moveBall), 286 MovableBall(2, position, moveBall), 287 MovableBall(3, position, moveBall), 288 ], 289 ), 290 ), 291 ], 292 ), 293 ); 294 } 295} 296 297void main() { 298 runApp(MaterialApp( 299 title: 'Drag and Drop Flutter Demo', 300 home: DragAndDropApp(), 301 )); 302} 303