You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.29 00:03:20