Flutter中Custom Painter内drawImage的点击事件实现问题
解决CustomPainter中绘制图片的点击事件问题
你的核心问题是全局坐标与CustomPaint本地坐标不匹配,以及直接判断坐标相等的逻辑不合理,以下是修复方案:
关键修改点
- 将全局坐标转换为CustomPaint的本地坐标:通过GlobalKey获取CustomPaint的RenderBox,把点击的全局坐标转成组件内的相对坐标。
- 判断点击是否在图片范围内:不要直接比较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
相关产品推荐
相关产品推荐

