7

I'm very new to flutter and am trying to figure out how to get a gesture detected on a CustomPaint path. I can click on a host of other things but not on paths for some reason... How do I get this to work? My code thus far is below.

import 'package:flutter/material.dart';

void main() => runApp(MbiraShapes());

class MbiraShapes extends StatefulWidget {
    @override
  _MbiraShapesState createState() => _MbiraShapesState();
}

class _MbiraShapesState extends State<MbiraShapes>{

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        title: 'Mbira Shapes',
        home: PathExample());
  }
}

class PathExample extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return CustomPaint(
      painter: KeyPathPainter(),
    );
  }
}

class KeyPathPainter extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    Paint lineDrawer = Paint()
      ..color = Colors.blue
      ..strokeWidth = 8.0;

    Path path = Path()
// Moves to starting point
      ..moveTo(50, 50)
      //draw lines passing through xc, yc to end at x,y
      ..quadraticBezierTo(77, 370, 50, 750)
      ..quadraticBezierTo(100, 775, 150, 750)
      ..quadraticBezierTo(110, 440, 75, 50);
    //close shape from last point
    path.close();
    canvas.drawPath(path, lineDrawer);

    Path path2 = Path()
// Moves to starting point
      ..moveTo(250, 50)
      //draw lines passing through xc, yc to end at x,y
      ..quadraticBezierTo(280, 350, 270, 675)
      ..quadraticBezierTo(290, 750, 350, 750)
      ..quadraticBezierTo(365, 710, 345, 600)
      ..quadraticBezierTo(320, 450, 270, 50);
    //close shape from last point
    path2.close();
    canvas.drawPath(path2, lineDrawer);
  }

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

I tried the snippet below with GestureDetector but it does not work. I tried a Listener but get no response for onPointerDown, only for onPointerMove, but I need a tap action, not a move action.

@override
  Widget build(BuildContext context) {

    return Scaffold(
      appBar: AppBar(
  
        title: Text("widget.title)"),
      ),
      
      body: Center(
        child: GestureDetector(
          child: CustomPaint(
            painter: KeyPathPainter(),
            child: Container()),
          onTap: () {
            print("tapped the key");
          },
        ),
      ),
    );
  }

I just want to tap a key and get a response, it will be a sound in the end but for now, I'm just trying to figure out how to get the onTap to work.

graciax452
  • 71
  • 1
  • 4
  • 1
    I made a library for this purpose : https://pub.dev/packages/touchable which lets you add gesture callbacks to each individual objects you draw on canvas. – Natesh bhat Apr 27 '20 at 05:00

6 Answers6

12

You have to override the hitTest() method of your KeyPathPainter class:

@override
bool hitTest(Offset position) {
    // _path - is the same one we built in paint() method;
    return _path.contains(position);
}

That will allow GestureDetector to detect taps inside the path instead of the entire CustomPaint container. Though the _path must be closed since there's no way—at least that I know of—to test hits on curves.

Jeremy Caney
  • 7,102
  • 69
  • 48
  • 77
Michael Sun
  • 121
  • 1
  • 3
3

I came to this from googling an issue I had. My issue was I was just wrapping the Painter in a gesture detector like:

GestureDetector(
      child: CustomPaint(
        painter: CustomPainter(),
      ),

      onTapDown: _handleTapDown,
);

Where the solution was I needed to have a Container with a color, wrapping the Custom Painter, like so:

return GestureDetector(
      child: Container(
        width: 300,
        height: 300,
        color: Colors.white,
        child: CustomPaint(
          painter: CustomPainter(),
        ),
      ),

      onTapDown: _handleTapDown,
);
Mans
  • 2,953
  • 2
  • 23
  • 32
2

Wrapping your paint with a gesture detector should do the work.

 GestureDetector(
    onTapDown: (details) {
         print("${details.globalPosition.dx}");
         print("${details.globalPosition.dy}");
     },
  ),
Sergio Bernal
  • 2,126
  • 9
  • 20
  • Hi sorry I don't understand I thought that was what I had already done with the second snippet above, having the child KeyPainter inside CustomPaint and inside GestureDetector.. that does not work... Please explain to me where I am getting it wrong – graciax452 Jul 18 '19 at 19:39
  • I'm testing your code and it works, it detects the gesture – Sergio Bernal Jul 18 '19 at 19:59
2

You should have a child of CustomPaint

   @override
   Widget build(BuildContext context) {
     return CustomPaint(
       painter: YourAwesomePainter(),
       child: Container(), // this line will solve the tap problem
    );
   }
Andrew
  • 36,676
  • 11
  • 141
  • 113
0

You need to put GestureDetector as a child of CustomPainter. In your case :

class PathExample extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return CustomPaint(
      painter: KeyPathPainter(),
      // Add 5 following lines
      child: GestureDetector(
        onTap: () {
          // Handle your tap here
        },
      ),
    );
  }
}
Mathieu
  • 1,435
  • 3
  • 16
  • 35
0

Use LeafRenderObjectWidget instead of CustomPaint.

  1. LeafRenderObjectWidget override createRenderObject method.
  2. createRenderObject return RenderConstrainedBox.
  3. RenderConstrainedBox override handlerEvent/hitTestSelf/paint method.

You can implement your gesture recognizer in handleEvent(PointerEvent event, BoxHitTestEntry entry) method.

or

Extends your gesture recognizer base on system classes VerticalDragGestureRecognizer/HorizontalDragGestureRecognizer/PanGestureRecognizer/...