Flutter迁移至2.8版本(启用空安全)后,CustomPainter结合InteractiveViewer在容器内绘图位置异常问题求助
我之前在做Flutter手绘功能时,也碰到过一模一样的问题——核心原因是你直接用了details.localFocalPoint,这个坐标是相对于InteractiveViewer组件的,而非你那个1700×1200的目标画布。当你缩放或平移时,这个坐标没有映射到真实的画布坐标系,自然会出现位置偏移。
下面是具体的修复方案:
1. 用TransformationController跟踪变换状态
首先需要通过TransformationController记录InteractiveViewer的缩放、平移参数,这样我们才能拿到当前的变换矩阵,实现坐标转换。
在你的Widget中声明控制器:
final TransformationController _transformationController = TransformationController();
然后把它绑定到InteractiveViewer上:
InteractiveViewer( transformationController: _transformationController, // 保留你原有的其他属性 )
2. 将触摸坐标转换为画布本地坐标
在onInteractionUpdate里,我们需要用变换矩阵的逆矩阵把触摸点从InteractiveViewer的坐标系,转换为你的1700×1200画布的坐标系。
修改onInteractionUpdate的代码:
onInteractionUpdate: (details) { if (currentShape == null) return; // 获取逆矩阵:把屏幕触摸坐标转换回画布本地坐标 final Matrix4 inverseMatrix = Matrix4.inverted(_transformationController.value); final Offset canvasOffset = inverseMatrix.transformPoint(details.localFocalPoint); currentShape!.pointList.add(canvasOffset); BlocProvider.of<DrawBloc>(context).add(AddShapeEvent(currentShape)); },
为什么用逆矩阵?因为
transformationController.value是把画布坐标转换为屏幕显示坐标的矩阵,逆矩阵刚好能完成反向转换,把触摸点映射回真实的画布位置。
3. 修正代码里的语法小问题
我注意到你代码里有几处多余的分号,比如boundaryMargin: EdgeInsets.all(1200 * 0.2);,和SizedBox结尾的分号,先把这些去掉避免编译错误。
4. 验证CustomPainter的绘制逻辑
现在你的DrawingPainter不需要做任何额外变换,因为我们已经把触摸点转换成了画布的本地坐标,和你设置的Size(width, height)完全匹配,直接绘制即可。
修改后的关键代码片段
// 声明变换控制器 final TransformationController _transformationController = TransformationController(); DrawnShape? currentShape; return InteractiveViewer( transformationController: _transformationController, onInteractionStart: (details) { currentShape = DrawnShape( pointList: [], paint: Paint() ..strokeCap = StrokeCap.round ..isAntiAlias = true ..color = Colors.black ..strokeWidth = 3.0 ..style = PaintingStyle.stroke, type: 1, id: Uuid(), ); }, onInteractionUpdate: (details) { if (currentShape == null) return; // 坐标转换核心逻辑 final Matrix4 inverseMatrix = Matrix4.inverted(_transformationController.value); final Offset canvasOffset = inverseMatrix.transformPoint(details.localFocalPoint); currentShape!.pointList.add(canvasOffset); BlocProvider.of<DrawBloc>(context).add(AddShapeEvent(currentShape)); }, onInteractionEnd: (details) { currentShape = null; }, constrained: false, boundaryMargin: EdgeInsets.all(1200 * 0.2), // 移除多余分号 maxScale: 5, minScale: 0.1, child: Stack( children: [ SizedBox( height: 1700, width: 1200, child: Container( decoration: BoxDecoration( color: Colors.red, border: Border.all( color: Colors.grey, width: 1.5, style: BorderStyle.solid), ), ), ), // 移除多余分号 IgnorePointer( ignoring: true, child: MyCanvas( height: 1700, width: 1200, ), ), ], ), );
这样修改后,不管你怎么缩放、平移InteractiveViewer,触摸位置都会精准对应到1700×1200的画布上,绘制的线条就能和手指完全同步了。
内容的提问来源于stack exchange,提问作者Daniel Roldán

