Flutter中Transform.scale缩放后CustomPaint绘制偏移如何解决
问题根因
Transform.scale 默认以子组件中心为缩放锚点,原有坐标计算仅扣除了平移偏移,未做缩放逆映射、也未抵消缩放锚点带来的中心偏移,点位离画布中心越远坐标偏差越大。
同时原有代码将GestureDetector放在Transform子组件内部,而Transform的变换仅在绘制阶段生效,不会改变子组件布局坐标,此时调用globalToLocal拿到的坐标未经过平移、缩放计算,结果本身就不准确。
修复方案
1. 调整组件结构
将GestureDetector移动到所有Transform组件的最外层,保证获取的手势坐标是相对于显示区域的准确屏幕坐标。原buildCurrentPath方法仅需返回绘制用的CustomPaint即可,无需再承载手势识别逻辑。
2. 补全坐标反算逻辑
获取到屏幕坐标后,按照「扣除平移偏移→转换坐标系到缩放中心→除以缩放比例→还原画布原坐标系」的顺序计算,即可得到任意缩放下一致的画布真实坐标。
画布尺寸为1200*1200,默认缩放锚点为画布中心Offset(600,600),坐标计算代码如下:
// 手势回调内的坐标计算逻辑 RenderBox box = context.findRenderObject() as RenderBox; double scale = context.read<DrawProvider>().scale; Offset offset = context.read<DrawProvider>().offset; Offset sessionOffset = context.read<DrawProvider>().sessionOffset; Offset translateOffset = offset + sessionOffset; const canvasCenter = Offset(600, 600); Offset point = box.globalToLocal(details.focalPoint); // 扣除平移偏移 point = point - translateOffset; // 坐标原点移到缩放中心 point = point - canvasCenter; // 抵消缩放带来的坐标放大/缩小 point = Offset(point.dx / scale, point.dy / scale); // 还原到画布原始坐标系 point = point + canvasCenter; // 最终point即为可直接用于绘制的准确坐标
3. 修复后的完整结构参考
return GestureDetector( onScaleStart: (details) { RenderBox box = context.findRenderObject() as RenderBox; double scale = context.read<DrawProvider>().scale; Offset offset = context.read<DrawProvider>().offset; Offset sessionOffset = context.read<DrawProvider>().sessionOffset; Offset translateOffset = offset + sessionOffset; const canvasCenter = Offset(600, 600); if (_isZoom) { context.read<DrawProvider>().onScaleStart(details); } else if (_isUseTool && tool == Tool.pen) { Offset point = box.globalToLocal(details.focalPoint); point = point - translateOffset; point = point - canvasCenter; point = Offset(point.dx / scale, point.dy / scale); point = point + canvasCenter; // 传入point开始绘制路径 } }, onScaleUpdate: (details) { RenderBox box = context.findRenderObject() as RenderBox; double scale = context.read<DrawProvider>().scale; Offset offset = context.read<DrawProvider>().offset; Offset sessionOffset = context.read<DrawProvider>().sessionOffset; Offset translateOffset = offset + sessionOffset; const canvasCenter = Offset(600, 600); if (_isZoom) { context.read<DrawProvider>().onScaleUpdate(details); } else if (_isUseTool && tool == Tool.pen) { Offset point = box.globalToLocal(details.focalPoint); point = point - translateOffset; point = point - canvasCenter; point = Offset(point.dx / scale, point.dy / scale); point = point + canvasCenter; // 传入point更新当前绘制路径 } }, onScaleEnd: (details) { if (_isZoom) { context.read<DrawProvider>().onScaleEnd(details); } }, child: Transform.translate( offset: offset + sessionOffset, child: Transform.scale( scale: scale, child: Stack( children: [ buildAllPaths(allLines: allLines), buildCurrentPath(allLines: allLines, currentLine: currentLine), ], ), ), ), );
补充说明
如果后续调整了Transform.scale的alignment参数(即修改缩放锚点位置),只需要对应修改代码里的canvasCenter变量为实际锚点坐标即可,计算逻辑无需变动。

内容的提问来源于stack exchange,提问作者Kevin Yang
相关产品推荐
相关产品推荐

