如何为堆叠的自定义绘制形状实现精准手势检测
The issue you're facing is that GestureDetector uses the entire rectangular bounds of your CustomPaint widget for hit testing, instead of the actual custom path you've drawn. To fix this, we need to override the hit testing logic in your CustomPainter classes to only recognize taps within the bounds of your drawn paths.
Step 1: Modify Your CustomPainter Classes
For each of your button shape painters, we'll:
- Store the drawn path as a class member variable
- Override the
hitTestmethod to check if the tap position is inside the path
Updated SlideButtonShape
class SlideButtonShape extends CustomPainter { late Path _buttonPath; @override void paint(Canvas canvas, Size size) { Paint fillPaint = Paint(); fillPaint.color = const Color(0xFF830B2C); fillPaint.style = PaintingStyle.fill; // Initialize and store the path as a member variable _buttonPath = Path(); _buttonPath.moveTo(size.width*0.3750000,size.height*0.2000000); _buttonPath.cubicTo(size.width*0.7906500,size.height*0.1983400,size.width*0.7990625,size.height*0.2000000,size.width*0.9375000,size.height*0.1980000); _buttonPath.quadraticBezierTo(size.width*0.7800750,size.height*0.2997800,size.width*0.7625000,size.height*0.5000000); _buttonPath.lineTo(size.width*0.5500000,size.height*0.5000000); _buttonPath.quadraticBezierTo(size.width*0.5319500,size.height*0.3022000,size.width*0.3750000,size.height*0.2000000); _buttonPath.close(); canvas.drawPath(_buttonPath, fillPaint); // Text rendering remains unchanged TextPainter textPainter = TextPainter( text: const TextSpan( text: 'Slide', style: TextStyle( color: Colors.white, fontSize: 15, ), ), textDirection: TextDirection.ltr, textAlign: TextAlign.center ); textPainter.layout(); final offset = Offset(size.width / 2 + 32, size.height / 2 - 30); textPainter.paint(canvas,offset); } @override bool shouldRepaint(covariant CustomPainter oldDelegate) { return true; } // Override hit test to only recognize taps inside our path @override bool hitTest(Offset position) { return _buttonPath.contains(position); } }
Updated TiltButtonShape
class TiltButtonShape extends CustomPainter { late Path _buttonPath; @override void paint(Canvas canvas, Size size) { Paint fillPaint = Paint(); fillPaint.color = const Color(0xFF830B2C); fillPaint.style = PaintingStyle.fill; _buttonPath = Path(); _buttonPath.moveTo(size.width*0.2500000,size.height*0.6000000); _buttonPath.quadraticBezierTo(size.width*0.4066750,size.height*0.4998400,size.width*0.4250000,size.height*0.3000000); _buttonPath.lineTo(size.width*0.6375000,size.height*0.3000000); _buttonPath.quadraticBezierTo(size.width*0.6564000,size.height*0.4992800,size.width*0.8125000,size.height*0.6000000); _buttonPath.cubicTo(size.width*0.6718750,size.height*0.6000000,size.width*0.6718750,size.height*0.6000000,size.width*0.2500000,size.height*0.6000000); _buttonPath.close(); canvas.drawPath(_buttonPath, fillPaint); TextPainter textPainter = TextPainter( text: const TextSpan( text: 'Tilt', style: TextStyle( color: Colors.white, fontSize: 15, ), ), textDirection: TextDirection.ltr, textAlign: TextAlign.center ); textPainter.layout(); final offset = Offset(size.width / 2 - 3, size.height / 2 - 15); textPainter.paint(canvas,offset); } @override bool shouldRepaint(covariant CustomPainter oldDelegate) { return true; } @override bool hitTest(Offset position) { return _buttonPath.contains(position); } }
Updated PanButtonShape
class PanButtonShape extends CustomPainter { late Path _buttonPath; @override void paint(Canvas canvas, Size size) { Paint fillPaint = Paint(); fillPaint.color = const Color(0xFF830B2C); fillPaint.style = PaintingStyle.fill; _buttonPath = Path(); _buttonPath.moveTo(size.width*0.3750000,size.height*0.3000000); _buttonPath.cubicTo(size.width*0.5625000,size.height*0.3000000,size.width*0.5625000,size.height*0.3000000,size.width*0.6250000,size.height*0.3000000); _buttonPath.quadraticBezierTo(size.width*0.6124375,size.height*0.4002400,size.width*0.6250000,size.height*0.5000000); _buttonPath.lineTo(size.width*0.3750000,size.height*0.5000000); _buttonPath.quadraticBezierTo(size.width*0.3879500,size.height*0.4000000,size.width*0.3750000,size.height*0.3000000); _buttonPath.close(); canvas.drawPath(_buttonPath, fillPaint); TextPainter textPainter = TextPainter( text: const TextSpan( text: 'Pan', style: TextStyle( color: Colors.white, fontSize: 15, ), ), textDirection: TextDirection.ltr, textAlign: TextAlign.center ); textPainter.layout(); final offset = Offset(size.width / 2 - 12, size.height / 2 - 30); textPainter.paint(canvas,offset); } @override bool shouldRepaint(covariant CustomPainter oldDelegate) { return true; } @override bool hitTest(Offset position) { return _buttonPath.contains(position); } }
Step 2: Update Your State Class
Make sure you're using the correct painter classes in your CustomPaint widgets (replace RPSCustomPainter3 with your updated painters):
class _MotionControl3State extends State<MotionControl3> { @override Widget build(BuildContext context) { final PageController controller = PageController(); return Scaffold( backgroundColor: const Color(0xff2e2e2e), body: SafeArea( child: Column( children: [ Container( color: Colors.red, height: 250, width: MediaQuery.of(context).size.width, child: Padding( padding: const EdgeInsets.symmetric(horizontal: 5.0), child: Stack( alignment: Alignment.center, children: [ Positioned( top: 25, right: 72, child: GestureDetector( onTap: (){ print('pan'); }, child: CustomPaint( painter: PanButtonShape(), size: const Size(250, 200), ), ), ), Positioned( top: 46, child: GestureDetector( onTap: (){ print('slide'); }, child: CustomPaint( painter: SlideButtonShape(), size: const Size(250, 200), ), ), ), Positioned( top: 106, right: 35, child: GestureDetector( onTap: (){ print('tilt'); }, child: CustomPaint( painter: TiltButtonShape(), size: const Size(300, 150), ), ), ), const Positioned( right:210, child: Joystick(), ), const Positioned( left:210, child: Joystick(), ), ], ), ), ), ], ), ), ); } }
How This Works
By default, CustomPaint uses its entire rectangular size for hit testing. When we override the hitTest method in our CustomPainter, we tell Flutter to only consider taps that fall within the bounds of our custom path. The Path.contains(position) method checks if the tap coordinate (relative to the CustomPaint widget) lies inside the path we've drawn.
内容的提问来源于stack exchange,提问作者Zuher Abud Said

