// Copyright 2014 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'dart:math' as math; import 'package:flutter/material.dart'; class ExampleDragTarget extends StatefulWidget { const ExampleDragTarget({super.key}); @override ExampleDragTargetState createState() => ExampleDragTargetState(); } class ExampleDragTargetState extends State<ExampleDragTarget> { Color _color = Colors.grey; void _handleAccept(Color data) { setState(() { _color = data; }); } @override Widget build(BuildContext context) { return DragTarget<Color>( onAccept: _handleAccept, builder: (BuildContext context, List<Color?> data, List<dynamic> rejectedData) { return Container( height: 100.0, margin: const EdgeInsets.all(10.0), decoration: BoxDecoration( color: data.isEmpty ? _color : Colors.grey.shade200, border: Border.all( width: 3.0, color: data.isEmpty ? Colors.white : Colors.blue, ), ), ); }, ); } } class Dot extends StatefulWidget { const Dot({ super.key, this.color, this.size, this.child, this.tappable = false }); final Color? color; final double? size; final Widget? child; final bool tappable; @override DotState createState() => DotState(); } class DotState extends State<Dot> { int taps = 0; @override Widget build(BuildContext context) { return GestureDetector( onTap: widget.tappable ? () { setState(() { taps += 1; }); } : null, child: Container( width: widget.size, height: widget.size, decoration: BoxDecoration( color: widget.color, border: Border.all(width: taps.toDouble()), shape: BoxShape.circle, ), child: widget.child, ), ); } } class ExampleDragSource extends StatelessWidget { const ExampleDragSource({ super.key, this.color, this.heavy = false, this.under = true, this.child, }); final Color? color; final bool heavy; final bool under; final Widget? child; static const double kDotSize = 50.0; static const double kHeavyMultiplier = 1.5; static const double kFingerSize = 50.0; @override Widget build(BuildContext context) { double size = kDotSize; if (heavy) { size *= kHeavyMultiplier; } final Widget contents = DefaultTextStyle( style: Theme.of(context).textTheme.bodyMedium!, textAlign: TextAlign.center, child: Dot( color: color, size: size, child: Center(child: child), ), ); Widget feedback = Opacity( opacity: 0.75, child: contents, ); Offset feedbackOffset; DragAnchorStrategy dragAnchorStrategy; if (!under) { feedback = Transform( transform: Matrix4.identity() ..translate(-size / 2.0, -(size / 2.0 + kFingerSize)), child: feedback, ); feedbackOffset = const Offset(0.0, -kFingerSize); dragAnchorStrategy = pointerDragAnchorStrategy; } else { feedbackOffset = Offset.zero; dragAnchorStrategy = childDragAnchorStrategy; } if (heavy) { return LongPressDraggable<Color>( data: color, feedback: feedback, feedbackOffset: feedbackOffset, dragAnchorStrategy: dragAnchorStrategy, child: contents, ); } else { return Draggable<Color>( data: color, feedback: feedback, feedbackOffset: feedbackOffset, dragAnchorStrategy: dragAnchorStrategy, child: contents, ); } } } 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 @override void paint(Canvas canvas, Size size) { final double radius = size.shortestSide / 2.0; final Paint paint = Paint() ..color = const Color(0xFF000000) ..style = PaintingStyle.stroke ..strokeWidth = radius / 10.0; final Path path = Path(); final Rect box = Offset.zero & size; for (double theta = 0.0; theta < math.pi * 2.0; theta += deltaTheta) { path.addArc(box, theta + startOffset, segmentArc); } canvas.drawPath(path, paint); } @override bool shouldRepaint(DashOutlineCirclePainter oldDelegate) => false; } class MovableBall extends StatelessWidget { const MovableBall(this.position, this.ballPosition, this.callback, {super.key}); final int position; final int ballPosition; final ValueChanged<int> callback; static final GlobalKey kBallKey = GlobalKey(); static const double kBallSize = 50.0; @override Widget build(BuildContext context) { final Widget ball = DefaultTextStyle( style: Theme.of(context).primaryTextTheme.bodyMedium!, textAlign: TextAlign.center, child: Dot( key: kBallKey, color: Colors.blue.shade700, size: kBallSize, tappable: true, child: const Center(child: Text('BALL')), ), ); const Widget dashedBall = SizedBox( width: kBallSize, height: kBallSize, child: CustomPaint( painter: DashOutlineCirclePainter() ), ); if (position == ballPosition) { return Draggable<bool>( data: true, childWhenDragging: dashedBall, feedback: ball, maxSimultaneousDrags: 1, child: ball, ); } else { return DragTarget<bool>( onAccept: (bool data) { callback(position); }, builder: (BuildContext context, List<bool?> accepted, List<dynamic> rejected) { return dashedBall; }, ); } } } class DragAndDropApp extends StatefulWidget { const DragAndDropApp({super.key}); @override DragAndDropAppState createState() => DragAndDropAppState(); } class DragAndDropAppState extends State<DragAndDropApp> { int position = 1; void moveBall(int newPosition) { setState(() { position = newPosition; }); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Drag and Drop Flutter Demo'), ), body: Column( children: <Widget>[ Expanded( child: Row( mainAxisAlignment: MainAxisAlignment.spaceAround, children: <Widget>[ ExampleDragSource( color: Colors.yellow.shade300, child: const Text('under'), ), ExampleDragSource( color: Colors.green.shade300, under: false, heavy: true, child: const Text('long-press above'), ), ExampleDragSource( color: Colors.indigo.shade300, under: false, child: const Text('above'), ), ], ), ), const Expanded( child: Row( children: <Widget>[ Expanded(child: ExampleDragTarget()), Expanded(child: ExampleDragTarget()), Expanded(child: ExampleDragTarget()), Expanded(child: ExampleDragTarget()), ], ), ), Expanded( child: Row( mainAxisAlignment: MainAxisAlignment.spaceAround, children: <Widget>[ MovableBall(1, position, moveBall), MovableBall(2, position, moveBall), MovableBall(3, position, moveBall), ], ), ), ], ), ); } } void main() { runApp(const MaterialApp( title: 'Drag and Drop Flutter Demo', home: DragAndDropApp(), )); }