Flutter中使用InteractiveViewer时获取CustomPaint的正确localPosition
解决InteractiveViewer缩放平移后CustomPaint点击位置判断错误的问题
问题根源是InteractiveViewer会对其子Widget应用缩放和平移变换,直接使用localPosition获取的是经过变换后的位置,无法对应到CustomPaint的原始坐标系。需要利用TransformationController跟踪变换矩阵,将点击位置逆变换回CustomPaint的本地坐标系。
修改步骤:
- 添加
TransformationController控制InteractiveViewer的变换,同时给CustomPaint添加GlobalKey用于获取其RenderBox。 - 在点击事件中,将全局点击位置转换为CustomPaint的本地坐标,再通过变换矩阵的逆运算得到原始坐标系下的位置。
- 基于转换后的位置判断是否位于目标区域。
修改后的完整代码:
import 'package:flutter/material.dart'; void main() { runApp(const ZoomAndPanWidget()); } class ZoomAndPanWidget extends StatefulWidget { const ZoomAndPanWidget({Key? key}) : super(key: key); @override _ZoomAndPanWidgetState createState() => _ZoomAndPanWidgetState(); } class _ZoomAndPanWidgetState extends State<ZoomAndPanWidget> { double yp = 15; final TransformationController _transformationController = TransformationController(); final GlobalKey _customPaintKey = GlobalKey(); @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( body: GestureDetector( onTapDown: (details) { // 获取CustomPaint的RenderBox final RenderBox renderBox = _customPaintKey.currentContext!.findRenderObject() as RenderBox; // 将全局点击位置转换为CustomPaint的本地坐标(未考虑InteractiveViewer变换) final Offset localPos = renderBox.globalToLocal(details.globalPosition); // 通过变换矩阵的逆运算,得到原始坐标系下的位置 final Offset transformedPos = _transformationController.value.inverted.transform(localPos); // CustomPaint的中心位置(原始坐标系) final Size customPaintSize = renderBox.size; final Offset center = Offset(customPaintSize.width / 2, customPaintSize.height / 2); // 计算点击位置与中心的偏移 final Offset distance = transformedPos - center; print('转换后的位置:$transformedPos,中心位置:$center'); // 判断是否在40*40的中心区域内 if (distance.dx.abs() <= 20 && distance.dy.abs() <= 20) { setState(() { yp = (yp == 15) ? 0 : 15; }); } }, child: InteractiveViewer( controller: _transformationController, maxScale: 3, minScale: 0.5, boundaryMargin: EdgeInsets.all(double.infinity), constrained: false, child: Container( alignment: Alignment.center, width: MediaQuery.of(context).size.width, height: MediaQuery.of(context).size.height, child: CustomPaint( key: _customPaintKey, size: Size(MediaQuery.of(context).size.width, MediaQuery.of(context).size.height), painter: Drawpath(yp), ), ), ), ), ), ); } } class Drawpath extends CustomPainter { final double yp; Drawpath(this.yp); @override void paint(Canvas canvas, Size size) { double x = size.width / 2; double y = size.height / 2; final paint = Paint() ..color = Colors.blue ..style = PaintingStyle.stroke ..strokeWidth = 4; final paintr = Paint()..color = Colors.red; final path = Path() ..moveTo(x - 40, y) ..lineTo(x - 10, y); final path2 = Path() ..moveTo(x - 10, y) ..lineTo(x + 10, y - yp); final path3 = Path() ..moveTo(x + 10, y) ..lineTo(x + 40, y); canvas.drawRect( Rect.fromCenter(center: Offset(x, y), width: 40, height: 40), paintr); canvas.drawPath(path, paint); canvas.drawPath(path2, paint); canvas.drawPath(path3, paint); canvas.drawRect( Rect.fromCenter(center: Offset(x, y), width: 2 * x, height: 2 * y), paint); } @override bool shouldRepaint(Drawpath oldDelegate) => true; }
关键说明:
- TransformationController:用于跟踪InteractiveViewer的缩放、平移变换矩阵,通过
value.inverted获取逆矩阵,将屏幕坐标转换回CustomPaint的原始坐标系。 - GlobalKey:获取CustomPaint的RenderBox,用于将全局点击位置转换为该Widget的本地坐标。
- 坐标转换流程:全局位置 → CustomPaint本地位置(未变换)→ 逆变换得到原始坐标系位置,此时再与CustomPaint的中心位置对比,就能准确判断点击区域。
内容的提问来源于stack exchange,提问作者Ahm2025
相关产品推荐
相关产品推荐

