Flutter Canvas开发:如何判断触摸点是否位于绘制的直线上
Flutter CustomPainter 图形交互实现方案
现有实现基础
当前基于CustomPainter+GestureDetector搭建的绘制框架,已经完成基础图形绘制、矩形拖拽移动能力,核心逻辑是在手势起始阶段将全局坐标转换为画布局部坐标,通过矩形自带的contains方法判断命中,手势更新阶段同步更新图形坐标触发重绘,核心代码如下:
List<Rectangle>? _rects; List<Line>? _lines; GestureDetector( onScaleStart: (details) { RenderBox box = context.findRenderObject() as RenderBox; Offset point = box.globalToLocal(details.focalPoint); // 检查触摸点是否命中图形 final selectedRectIndex = _rects!.lastIndexWhere((rect) => rect.rect!.contains(point)); } ) // 画布绘制逻辑 canvas.drawOval(shape.rect!, shape.paint); canvas.drawLine(shape.p1!, shape.p2!, shape.paint);
直线图形触摸命中判断方法
Flutter 没有提供线段直接对应的contains类似方法,需要自行实现命中判断,考虑手指触摸精度,需要设置合理的命中容差,不能严格要求点完全落在数学意义的线段上,具体实现如下:
/// 判断触摸点是否命中线段 /// [p1] [p2] 线段两个端点 /// [touchPoint] 转换为画布坐标的触摸点 /// [tolerance] 命中容差,单位逻辑像素,默认取10适配手指触摸精度,粗线可叠加线宽/2的值 bool isPointOnLine(Offset p1, Offset p2, Offset touchPoint, {double tolerance = 10}) { // 快速过滤:先判断点是否在两个端点组成的外接矩形(叠加容差)范围内,不符合直接返回false final boundRect = Rect.fromPoints(p1, p2).inflate(tolerance); if (!boundRect.contains(touchPoint)) return false; final double lineLength = (p2 - p1).distance; // 处理两个端点重合的特殊线段 if (lineLength == 0) return (touchPoint - p1).distance <= tolerance; // 点到直线距离公式计算触摸点到线段的垂直距离 final double crossProduct = (touchPoint.dx - p1.dx) * (p2.dy - p1.dy) - (touchPoint.dy - p1.dy) * (p2.dx - p1.dx); final double distance = crossProduct.abs() / lineLength; return distance <= tolerance; }
使用时和矩形命中逻辑一致,在手势起始阶段遍历线段列表判断即可:
final selectedLineIndex = _lines!.lastIndexWhere( (line) => isPointOnLine(line.p1!, line.p2!, point) );
注意:遍历图形时要按照绘制顺序从后往前判断,后绘制的图形层级更高,应该优先响应命中。
完整拖拽移动逻辑梳理
- 维护统一的选中态字段,比如
Shape? selectedShape,建议将矩形、线段等所有图形抽成继承自同一基类的对象,统一管理点位、绘制、命中逻辑,避免分开维护多个列表的选中状态 onScaleStart阶段:坐标转换完成后,优先判断是否命中图形,命中后记录选中图形对象,同时记录触摸起始点、图形的初始点位基准,供后续位移计算使用;如果点击空白区域则清空选中态onScaleUpdate阶段:计算当前触摸点和起始点的偏移差值,将选中图形的所有点位统一叠加该偏移值,触发画布重绘即可实现拖拽效果;如果没有选中图形则走新图形绘制逻辑onScaleEnd阶段:清空拖拽过程的临时计算参数,保留选中态即可
选中控制点、旋转缩放功能实现建议
- 统一图形抽象层:给所有图形基类定义统一的
hitTest(命中判断)、translate(平移)、scale(缩放)、rotate(旋转)方法,图形自身维护顶点坐标、旋转中心、缩放比例、旋转角度属性,后续新增圆形、多边形等图形时不需要修改交互逻辑 - 控制点绘制规则:图形处于选中态时,在画布最顶层绘制控制点:
- 顶点/边角控制点:在图形的每个顶点、外接矩形边角位置绘制边长10-12逻辑像素的小正方形或圆形,保证手指可以准确点中
- 旋转控制点:一般设置在图形外接矩形顶部外侧,和图形旋转中心之间绘制一条辅助线做视觉提示
- 控制点交互逻辑:
onScaleStart阶段优先判断是否命中控制点,如果命中则记录当前操作的控制点类型、图形的初始状态参数- 拖拽顶点/边角控制点时,如果是顶点则更新对应顶点坐标实现形状调整,如果是边角控制点则以图形旋转中心为锚点,根据拖拽偏移计算缩放比例,同步更新所有点位坐标实现缩放
- 拖拽旋转控制点时,计算当前触摸点和旋转中心连线的角度,和初始角度的差值即为旋转增量,通过矩阵变换更新图形所有点位坐标即可
- 优化提示:旋转、缩放操作建议直接使用
Matrix4做坐标变换,不要手动计算每个点的坐标,减少计算误差,绘制时通过canvas.transform(matrix.storage)直接应用变换矩阵即可。
内容的提问来源于stack exchange,提问作者Kevin Yang
相关产品推荐
相关产品推荐

