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

Flutter中使用InteractiveViewer时获取CustomPaint的正确localPosition

解决InteractiveViewer缩放平移后CustomPaint点击位置判断错误的问题

问题根源是InteractiveViewer会对其子Widget应用缩放和平移变换,直接使用localPosition获取的是经过变换后的位置,无法对应到CustomPaint的原始坐标系。需要利用TransformationController跟踪变换矩阵,将点击位置逆变换回CustomPaint的本地坐标系。

修改步骤:

  1. 添加TransformationController控制InteractiveViewer的变换,同时给CustomPaint添加GlobalKey用于获取其RenderBox。
  2. 在点击事件中,将全局点击位置转换为CustomPaint的本地坐标,再通过变换矩阵的逆运算得到原始坐标系下的位置。
  3. 基于转换后的位置判断是否位于目标区域。

修改后的完整代码:

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.11 07:12:03