Flutter如何计算触摸Offset到绘制线段的距离实现选中检测?
Flutter CustomPaint 线段触摸命中判定实现
要实现线条的选中、移动、删除交互,核心是完成触摸点到线段的命中检测,整个判定只需要两步校验,全部通过即判定为命中对应线段。
判定规则
- 校验触摸点在线段所在直线上的投影,是否落在两个端点构成的线段区间内,排除投影在线段延长线上的误判
- 计算触摸点到线段的垂直距离,当距离小于预设的命中阈值时,判定为命中。阈值根据交互场景调整:手写笔场景建议设为3-5逻辑像素,手指触摸场景建议设为8-12逻辑像素,适配不同操作的精度要求
可复用判定工具方法
直接在项目中引入以下工具方法即可使用,计算过程全程避免不必要的开根号操作,性能满足大量线条的遍历判定需求:
import 'dart:math'; import 'package:flutter/material.dart'; /// 判定触摸点[point]是否命中由[p1]、[p2]两个端点构成的线段 /// [hitTolerance] 为命中允许的最大距离,单位为逻辑像素 bool isPointHitLine(Offset p1, Offset p2, Offset point, {double hitTolerance = 10}) { // 线段方向向量 final double lineX = p2.dx - p1.dx; final double lineY = p2.dy - p1.dy; // 触摸点到线段起点p1的向量 final double pointX = point.dx - p1.dx; final double pointY = point.dy - p1.dy; // 线段长度平方,处理两个端点重合的特殊线段(即点) final double lineLengthSq = lineX * lineX + lineY * lineY; if (lineLengthSq == 0) { return (point - p1).distance <= hitTolerance; } // 计算投影占线段长度的比例t,t值范围在[0,1]时投影才落在线段有效范围内 final double t = (pointX * lineX + pointY * lineY) / lineLengthSq; final double validT = max(0, min(1, t)); // 计算线段上离触摸点最近的投影点坐标 final Offset closestPoint = Offset( p1.dx + validT * lineX, p1.dy + validT * lineY, ); // 比较触摸点到最近点的距离和命中阈值 return (point - closestPoint).distance <= hitTolerance; }
业务层接入方式
注意:命中判定逻辑不需要写在CustomPainter的绘制流程中,只需要在手势回调(如GestureDetector的onTapDown、onPanDown)中执行即可,不会额外增加绘制性能损耗。
在拿到触摸事件的局部坐标后,遍历所有绘制的线条,逐个调用判定方法即可找到当前命中的线条:
// 从手势详情中拿到当前触摸的局部坐标 final Offset touchPosition = event.localPosition; Line? selectedLine; for (final shape in shapes) { if (shape is Line) { if (isPointHitLine(shape.p1!, shape.p2!, touchPosition)) { selectedLine = shape; break; } } } // 命中线条后即可执行对应的选中、移动、删除业务逻辑 if (selectedLine != null) { // 例:标记选中状态、绑定移动偏移更新、从shapes列表移除等 }
如果你需要支持旋转、缩放后的画布命中判定,只需要在传入触摸点和线段端点前,先根据画布的变换矩阵做一次坐标反变换,将触摸点坐标转换到画布的原始坐标系下,再调用上述判定方法即可。
内容的提问来源于stack exchange,提问作者Kevin Yang
相关产品推荐
相关产品推荐

