Flutter Draggable and DragTarget

In many cases, we may need a UI element which can be dragged from one point to another point on the screen. So, for those cases, Flutter introduces Draggable widget. This widget allows the movement of widgets across the screen. When a widget is draggable, usually it needs to be dropped at a certain location. For that widget to be dropped, Flutter has a DragTarget widget. It acts as a target for the Draggable widget to be dropped into. In this article, we will be looking into the Draggable and DragTarget widgets in Flutter.

Flutter Draggable

Draggable(
  child: Container(
    color: Colors.blue,
    height: 80,
    width: 80,	
  ),
  feedback: Container(
    color: Colors.green,
    height: 80,
    width: 80,	
  ),
  childWhenDragging: Container(),
),

Here, child is the widget displayed when it is in its initial position, feedback is the widget displayed when the widget is being dragged across the screen and childWhenDragging is the widget which is displayed while the widget is being dragged where the child was initially positioned. Draggable can also take an extra parameter axis which allows the widget to be dragged in only one direction, either vertically or horizontally.

In addition to the following properties, the Draggable widget also contains the following callbacks:

onDragStarted
It is called when the draggable starts being dragged.

onDraggableCanceled
It is called when the Draggable widget is dropped without being accepted by the DragTarget, which means either the Draggable widget’s move to DragTarget is stopped midway or it is rejected by the DragTarget.

onDragCompleted
It is called when Draggable is dragged and accepted by the DragTarget.

onDragEnd
It is called when Draggable is dropped at any point in the screen.

onDragUpdate
It is called as long as draggable is being dragged.

Data

Data can also be associated with draggable widget. The data parameter takes the data to be sent along with draggable.

Draggable(
  data: 'Dragged data'
  child: Container(
    color: Colors.blue,
    height: 80,
    width: 80,	
  ),
  feedback: Container(
    color: Colors.green,
    height: 80,
    width: 80,	
  ),
  childWhenDragging: Container(),
),

Flutter DragTarget

DragTarget is the widget where Draggable widget is dropped. While dropping Draggable into the DragTarget, DragTarget also receives the data from the Draggable.

 DragTarget(
     builder: (BuildContext context, List<T> candidateData,
     List rejectedData) {
         return Container(
             color: Colors.green,
             height: 80,
             width: 80,
         );
      },
     onWillAccept: (data) {},
     onAccept: (data) {},
     onLeave:(data){},
     onMove:(dragTargetDetails){},
),

DragTarget takes the following parameters:

builder
This function builds the widget inside the DragTarget. It takes three parameters. The first one is context, the second one is the candidateData which takes a list of objects and the final one is rejectedData which also takes a list. The candidateData is the data coming from the draggable which the DragTarget is willing to accept whereas the rejectedData is the data coming from the draggable which is not accepted by the DragTarget.

onWillAccept
It is called to decide whether the DragTarget is willing to accept the data from the incoming draggable or not. It is called when the draggable is being hovered above the DragTarget. Only if this returns true, then the DragTarget accepts the incoming data.

onAccept
It is called when the data accepted by onWillAccept coming from Draggable is dropped into the DragTarget.

onAcceptWithDetails
It is the same as onAccept but it includes other details.

onLeave
It is called when the Draggable leaves the DragTarget without being dropped into the DragTarget.

onMove
It is called when the Draggable moves within the DragTarget.

Draggable Demo

The following code contains the code for Draggable widget demo:

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: DraggableDemo(),
    );
  }
}

class DraggableDemo extends StatelessWidget {
  const DraggableDemo({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    void showSnackBar(String text, {Color color = Colors.grey}) {
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(
          backgroundColor: color,
          content: Text(
            text,
            style: const TextStyle(color: Colors.white),
          ),
          duration: const Duration(seconds: 2),
        ),
      );
    }

    return Scaffold(
      appBar: AppBar(
        title: const Text("Draggable Demo"),
      ),
      body: SizedBox(
        width: double.infinity,
        child: Column(
          mainAxisAlignment: MainAxisAlignment.spaceEvenly,
          children: [
            const SizedBox(height: 15.0),
            Draggable<String>(
              data: 'Negative',
              onDragStarted: () {
                showSnackBar('Started');
              },
              onDragCompleted: () {
                showSnackBar('Success', color: Colors.green);
              },
              onDraggableCanceled: (_, __) {
                showSnackBar('Failure', color: Colors.red);
              },
              onDragEnd: (_) {
                showSnackBar('Ended');
              },
              child: const CircleAvatar(
                backgroundColor: Colors.orange,
                radius: 50,
              ),
              feedback: const CircleAvatar(
                backgroundColor: Colors.red,
                radius: 50,
              ),
              childWhenDragging: Container(),
            ),
            DragTarget<String>(
              builder: (context, candidateData, rejectedData) {
                return Container(
                  color: Colors.blue,
                  height: 150,
                  width: double.infinity,
                );
              },
            ),
          ],
        ),
      ),
    );
  }
}

Flutter Draggable and DragTarget

The following code contains the code for Draggable widget and DragTarget widget demo which is presented as a charge being dropped into positively charged object. The negative charged object is accepted by the object as opposite attracts whereas positive charged object is rejected as like charges repel each other.

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: DragWidgetTest(),
    );
  }
}

class DragWidgetTest extends StatefulWidget {
  const DragWidgetTest({Key? key}) : super(key: key);

  @override
  State<DragWidgetTest> createState() => _DragWidgetTestState();
}

class _DragWidgetTestState extends State<DragWidgetTest> {
  Color targetColor = Colors.lime;

  @override
  Widget build(BuildContext context) {
    void showSnackBar(String text, {Color color = Colors.grey}) {
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(
          backgroundColor: color,
          content: Text(
            text,
            style: const TextStyle(color: Colors.white),
          ),
          duration: const Duration(seconds: 2),
        ),
      );
    }

    return Scaffold(
      appBar: AppBar(
        title: const Text("Drag Demo"),
      ),
      body: Container(
        padding: const EdgeInsets.all(15.0),
        width: double.infinity,
        child: Column(
          mainAxisAlignment: MainAxisAlignment.spaceAround,
          children: [
            Row(
              mainAxisAlignment: MainAxisAlignment.spaceAround,
              children: [
                Draggable<String>(
                  data: 'Positive',
                  child: const CircleAvatar(
                    backgroundColor: Colors.blue,
                    radius: 40,
                    child: Icon(
                      Icons.add,
                      color: Colors.white,
                    ),
                  ),
                  feedback: const CircleAvatar(
                    backgroundColor: Colors.green,
                    radius: 40,
                    child: Icon(
                      Icons.add,
                      color: Colors.white,
                    ),
                  ),
                  childWhenDragging: Container(),
                ),
                Draggable<String>(
                  data: 'Negative',
                  child: const CircleAvatar(
                    backgroundColor: Colors.orange,
                    radius: 40,
                    child: Icon(
                      Icons.remove,
                      color: Colors.white,
                    ),
                  ),
                  feedback: const CircleAvatar(
                    backgroundColor: Colors.red,
                    radius: 40,
                    child: Icon(
                      Icons.remove,
                      color: Colors.white,
                    ),
                  ),
                  childWhenDragging: Container(),
                ),
              ],
            ),
            DragTarget<String>(
              onWillAccept: (data) {
                if (data == "Negative") {
                  return true;
                } else {
                  showSnackBar('Rejected', color: Colors.red);

                  return false;
                }
              },
              onAccept: (data) {
                if (data == 'Negative') {
                  showSnackBar('Accepted', color: Colors.green);
                }
                setState(() {
                  targetColor = Colors.lime;
                });
              },
              onLeave: (_) {
                setState(() {
                  targetColor = Colors.lime;
                });
              },
              onMove: (_) {
                setState(() {
                  targetColor = Colors.green;
                });
              },
              builder: (context, candidateData, rejectedData) {
                return Container(
                  color: targetColor,
                  height: 100,
                  width: 200,
                  child: const Icon(
                    Icons.add,
                    color: Colors.white,
                  ),
                );
              },
            ),
          ],
        ),
      ),
    );
  }
}

Hopefully, this article made the concept of Draggable and DragTarget widgets clear for you. Now, you can use this widgets to add different functionalities to your app.

Related article: Lottie Animations In Flutter