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

Flutter中Custom Painter内drawImage的点击事件实现问题

解决CustomPainter中绘制图片的点击事件问题

你的核心问题是全局坐标与CustomPaint本地坐标不匹配,以及直接判断坐标相等的逻辑不合理,以下是修复方案:

关键修改点

  1. 将全局坐标转换为CustomPaint的本地坐标:通过GlobalKey获取CustomPaint的RenderBox,把点击的全局坐标转成组件内的相对坐标。
  2. 判断点击是否在图片范围内:不要直接比较Offset完全相等,而是检查点击坐标是否落在图片绘制的矩形区域内。

修改后的完整代码

import 'dart:typed_data';
import 'dart:ui' as ui;
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

class MyApp extends StatelessWidget {
  MyApp({super.key});

  final ValueNotifier<ui.Offset?> onTappedLocation = ValueNotifier(null);
  final GlobalKey _customPaintKey = GlobalKey(); // 新增GlobalKey

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      debugShowCheckedModeBanner: false,
      home: Container(
        color: Colors.indigo,
        child: GestureDetector(
          onTapDown: (details) {
            // 将全局坐标转换为CustomPaint的本地坐标
            final renderBox = _customPaintKey.currentContext?.findRenderObject() as RenderBox?;
            if (renderBox != null) {
              final localOffset = renderBox.globalToLocal(details.globalPosition);
              onTappedLocation.value = localOffset;
            }
          },
          child: Center(
              child: FutureBuilder<ui.Image>(
                  future: getImage(),
                  builder: (BuildContext context, AsyncSnapshot<ui.Image> snapshot) {
                    Widget centerChild;
                    if (snapshot.hasData) {
                      centerChild = CustomPaint(
                        key: _customPaintKey, // 绑定GlobalKey
                        painter: FootballPitchPainter(
                          snapshot.data!,
                          onTappedLocation,
                              () {
                            print("图片被点击了!");
                          },
                        ),
                        size: const Size(360, 480),
                      );
                    } else {
                      centerChild = const CircularProgressIndicator();
                    }
                    return centerChild;
                  })),
        ),
      ),
    );
  }
}

class FootballPitchPainter extends CustomPainter {
  final ui.Image image;
  final ValueNotifier<ui.Offset?> onTappedLocation;
  final VoidCallback onTap;
  // 存储图片的绘制位置和尺寸
  final Rect _imageRect = const Rect.fromLTWH(100, 100, 20, 20);

  FootballPitchPainter(this.image, this.onTappedLocation, this.onTap)
      : super(repaint: onTappedLocation);

  @override
  void paint(Canvas canvas, Size size) {
    final w = size.width;
    final h = size.height;

    // 绘制图片
    canvas.drawImage(image, _imageRect.topLeft, Paint());

    // 判断点击是否在图片范围内
    final tappedOffset = onTappedLocation.value;
    if (tappedOffset != null && _imageRect.contains(tappedOffset)) {
      onTap();
      // 触发后重置,避免重复触发
      onTappedLocation.value = null;
    }
  }

  @override
  bool shouldRepaint(covariant FootballPitchPainter oldDelegate) {
    return oldDelegate.image != image || oldDelegate.onTappedLocation.value != onTappedLocation.value;
  }
}

Future<ui.Image> getImage() async {
  ByteData bd = await rootBundle.load('assets/logo.png');
  final Uint8List bytes = Uint8List.view(bd.buffer);
  final ui.Codec codec = await ui.instantiateImageCodec(bytes, targetHeight: 20, targetWidth: 20);
  final ui.Image image = (await codec.getNextFrame()).image;
  return image;
}

额外说明

  • 用Rect.fromLTWH定义图片的绘制区域,包含位置和尺寸,方便判断点击范围。
  • 触发点击事件后重置onTappedLocation的值,避免因为CustomPainter重绘导致重复触发。
  • shouldRepaint优化为只在必要时重绘,提升性能。

内容的提问来源于stack exchange,提问作者khush pajwani

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.22 00:45:18