Flutter中CustomPainter路径与Tap点击位置不匹配问题求助
问题:点击CustomPainter绘制的Path无法触发命中检测
尝试点击CustomPainter生成的Path元素时,onTapDown返回的位置始终不在Path的范围内。已查看路径边界(Rect.fromLTRB(167.3, 142.5, 213.5, 177.4))和点击位置(如256.3333282470703, 527.0),尝试使用全局位置也无法解决问题。
原代码如下:
import 'package:flutter/material.dart'; void main() { runApp(MyApp()); } class MyApp extends StatelessWidget { Offset _tapPosition = Offset.zero; @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( appBar: AppBar( title: Text('Custom Paint Example'), ), body: GestureDetector( onTapDown: (TapDownDetails details) { print('Tap location: ${details.localPosition}'); print('Tap location: ${details.globalPosition}'); final RenderBox renderBox = context.findRenderObject() as RenderBox; final Offset localOffset = renderBox.localToGlobal(details.globalPosition); Offset customPaintPosition = renderBox.localToGlobal(Offset.zero); print(Offset.zero); if (isTapInsidePath(LinePainter.path_1, details.localPosition)) { print('Tapped inside path_1 at ${details.localPosition}'); } }, child: Container( decoration: BoxDecoration( border: Border.all( color: Colors.black, width: 10.0, ), ), child: CustomPaintExample(), ), ), ), ); } bool isTapInsidePath(Path path, Offset tapPosition) { print (path.getBounds()); print (path.computeMetrics()); return path.contains(tapPosition); } } class CustomPaintExample extends StatelessWidget { @override Widget build(BuildContext context) { return Center( child: CustomPaint( painter: LinePainter(), size: Size(300, 300), ), ); } } class LinePainter extends CustomPainter { static final Path path_1 = Path() ..moveTo(167.309,177.39) ..cubicTo(180.907,163.368,200.541,152.36499999999998,213.487,149.762) ..cubicTo(170.764,142.488,175.079,155.056,167.309,177.39) ..close(); @override void paint(Canvas canvas, Size size) { Paint paint = Paint() ..color = Colors.blue ..strokeCap = StrokeCap.round ..strokeWidth = 5.0; List<Offset> points = [ Offset(50, 50), Offset(150, 100), Offset(100, 200), Offset(200, 250), ]; for (int i = 0; i < points.length - 1; i++) { canvas.drawLine(points[i], points[i + 1], paint); } Paint paint_1_fill = Paint()..style=PaintingStyle.fill; paint_1_fill.color = Color(0xff000000).withOpacity(1.0); canvas.drawPath(path_1,paint_1_fill); } @override bool shouldRepaint(CustomPainter oldDelegate) { return false; } }
解决方案
问题核心在于坐标上下文不匹配:
- 原GestureDetector的context属于MyApp,获取的RenderBox不是CustomPaint所在的容器,导致坐标转换错误
- CustomPaint被Center包裹,其本地坐标与GestureDetector的坐标存在偏移
修改步骤:
- 将GestureDetector移至
CustomPaintExample内部,确保捕获的点击坐标对应CustomPaint的父容器 - 转换点击坐标到CustomPaint的本地空间,与Path的坐标体系对齐
修改后的完整代码:
import 'package:flutter/material.dart'; void main() { runApp(MyApp()); } class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( appBar: AppBar(title: const Text('Custom Paint Example')), body: Container( decoration: BoxDecoration( border: Border.all(color: Colors.black, width: 10.0), ), child: const CustomPaintExample(), ), ), ); } } class CustomPaintExample extends StatelessWidget { const CustomPaintExample({super.key}); bool isTapInsidePath(Path path, Offset tapPosition) { print('Path bounds: ${path.getBounds()}'); return path.contains(tapPosition); } @override Widget build(BuildContext context) { return Center( child: GestureDetector( onTapDown: (TapDownDetails details) { final RenderBox customPaintRenderBox = context.findRenderObject() as RenderBox; // 将全局点击坐标转换为CustomPaint的本地坐标 final Offset customPaintLocalPos = customPaintRenderBox.globalToLocal(details.globalPosition); print('CustomPaint local position: $customPaintLocalPos'); if (isTapInsidePath(LinePainter.path_1, customPaintLocalPos)) { print('Tapped inside path_1 at $customPaintLocalPos'); } }, child: const CustomPaint( painter: LinePainter(), size: Size(300, 300), ), ), ); } } class LinePainter extends CustomPainter { static final Path path_1 = Path() ..moveTo(167.309, 177.39) ..cubicTo(180.907, 163.368, 200.541, 152.365, 213.487, 149.762) ..cubicTo(170.764, 142.488, 175.079, 155.056, 167.309, 177.39) ..close(); @override void paint(Canvas canvas, Size size) { // 绘制蓝色线条 final Paint linePaint = Paint() ..color = Colors.blue ..strokeCap = StrokeCap.round ..strokeWidth = 5.0; final List<Offset> points = [ const Offset(50, 50), const Offset(150, 100), const Offset(100, 200), const Offset(200, 250), ]; for (int i = 0; i < points.length - 1; i++) { canvas.drawLine(points[i], points[i + 1], linePaint); } // 绘制黑色填充路径 final pathPaint = Paint() ..style = PaintingStyle.fill ..color = Colors.black.withOpacity(1.0); canvas.drawPath(path_1, pathPaint); } @override bool shouldRepaint(CustomPainter oldDelegate) => false; }
关键修改说明:
- 将GestureDetector直接包裹CustomPaint,确保点击坐标的上下文与CustomPaint一致
- 使用
globalToLocal将全局坐标转换为CustomPaint的本地坐标,解决Center导致的偏移问题 - 移除原MyApp中错误的坐标转换逻辑,避免上下文不匹配
内容的提问来源于stack exchange,提问作者Sidd
相关产品推荐
相关产品推荐

