DEV Community

Cover image for Drag and Drop in Flutter Canvas
JH Jeong
JH Jeong

Posted on

Drag and Drop in Flutter Canvas

Flutter
Drag and Drop (hereinafter DnD) is often used for interaction when configuring UI. Flutter supports Drag and Drop with a widget called Draggable.
Flutter Dev. Draggable Widget
Flutter's official YouTube shows how to use Draggable Widget kindly.

DnD in Canvas

To DnD the Widget, Flutter supports a widget called Dragable, but I wanted to DnD one path on CustomPaint's Canvas, not a widget. What should I do now? As a result of searching StackOverFlow, it seemed that there was no widget or method for this only(except pub dev). So, I decided to implement it directly as a GestureDetector Widget.

Code

This is the result of implementation.
This is the result of implementation. I drew a ball in Canvas, and when I click the ball, it becomes DnD.

Skeleton Code

  dynamic _balls;
  double xPos = 100;
  double yPos = 100;

 @override
  Widget build(BuildContext context) {
    _balls=_paint(
        xPosition: xPos,
        yPosition: yPos,
        ballRad: 20
    );

    return Scaffold(
      appBar: AppBar(
        title: Text("Drag and Drop"),
      ),
      body: Center(
        child: Container(
          width: 300,
          height: 300,
          color: Colors.lightBlueAccent,
          child: CustomPaint(
            painter: _balls,
          ),
         ),
        )
      );
  }
Enter fullscreen mode Exit fullscreen mode
class _paint extends CustomPainter {
  final xPosition;
  final yPosition;
  final ballRad;

  _paint({
    required this.xPosition,
    required this.yPosition,
    required this.ballRad,
  });

  @override
  void paint(Canvas canvas, Size size) {
    Paint paint = Paint()
      ..color = Colors.indigoAccent
      ..style = PaintingStyle.stroke
      ..strokeWidth = 2;

    Path path = Path();

    for(double i=0; i<ballRad-1; i++){
      path.addOval(Rect.fromCircle(
          center: Offset(
            xPosition,
            yPosition
          ),
          radius: i
      ));
    }
    path.close();

    canvas.drawPath(path, paint);
  }

  @override
  bool shouldRepaint(CustomPainter oldDelegate) => true;

}
Enter fullscreen mode Exit fullscreen mode

Basically, we have to draw a ball. I create a _paint class that inherits CustomPaint and declares a dynamic type _paint object called _balls. In this case, the _paint designates xPosition, yPosition, and ballRadius as required.

A ball of radius 20 is drawn on the result screen of the above code. Now let's implement DnD.

Is Ball Region?

  bool isBallRegion(double checkX, double checkY){
    if((pow(xPosition-checkX, 2)+pow(yPosition-checkY, 2))<=pow(ballRad, 2)){
      return true;
    }
    return false;
  }
Enter fullscreen mode Exit fullscreen mode

Let's create an instance method that can simply determine if a particular coordinate in Canvas is empty. This can be easily made through the inequality of the circle in the high school math course.

GestureDetector

This is the widget that I will use to implement DnD. GestureDetector Widget is a widget that can recognize various types of gestures of a user. If you look at flutter docs, you can see a lot of methods.

GestureDetector(
          onHorizontalDragDown: (details) {

          },
          onHorizontalDragEnd: (details) {

          },
          onHorizontalDragUpdate: (details) {

          },

          child: Container(
            width: 300,
            height: 300,
            color: Colors.lightBlueAccent,
            child: CustomPaint(
              painter: _balls,
            ),
          ),
        )
Enter fullscreen mode Exit fullscreen mode

Wrap the Gesture Detector on the Custom Paint Widget above. In the GestureDetector, we will use onHorizontalDragDown, onHorizontalDragEnd, and onHorizontalDragUpdate.

onHorizontalDragDown is when you start Drag
onHorizontalDragEnd is used at the end of Drag(=Drop)
onHorizontalDragUpdate is executed when Drag is performed.

details.localPosition.dx
details.localPosition.dy
Enter fullscreen mode Exit fullscreen mode

In each function, the coordinate value of the mouse or touch can be known through the information in details. I'm going to use this information. First, let's declare a variable that can determine whether we are clicking (touching).

bool isClick = false;
Enter fullscreen mode Exit fullscreen mode

This variable becomes true when onHorizontalDragDown and false again when onHorizontalDragEnd to determine if it is being touched.

          onHorizontalDragDown: (details) {
            setState(() {
              if (_balls.isBallRegion(details.localPosition.dx, details.localPosition.dy)) {
                isClick=true;
              }
            });
          },
          onHorizontalDragEnd: (details) {
            setState(() {
              isClick=false;
            });
          },
          onHorizontalDragUpdate: (details) {
            if(isClick){
              setState(() {
                xPos=details.localPosition.dx;
                yPos=details.localPosition.dy;
              });
            }
          },
Enter fullscreen mode Exit fullscreen mode

If we drag a part other than the ball in onHorizontalDragDown, nothing should happen, so let's call the isBallRegion method that we created above in the if statement. As a parameter of the method, details.localPosition, that is, the touch coordinate value is passed over.
In addition, onHorizontalDragUpdate updates the new x-coordinate value and y-coordinate value every moment of drag.

Conclusion

Done. To implement this, I saw various Widgets related to touch such as draggable, FestureDetector, and Inkwell in flutter docs, and I think there will be many things to use in the future. And I'm excited that I can make more fun things through this!

Discussion (0)