Drag and Drop in Flutter Canvas

Drag and Drop in Flutter Canvas

·

4 min read

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,
          ),
         ),
        )
      );
  }
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;

}

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;
  }

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,
            ),
          ),
        )

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

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;

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;
              });
            }
          },

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!