Flutter中如何用CustomPainter绘制定位图标替代绿色点?
解决方法:将绿色点替换为定位图标
要把点击画布添加的绿色点换成定位图标,你需要修改MyCustomPainter里的绘制逻辑,替换原来的drawPoints方法。下面提供两种可行方案:
方案一:纯Canvas绘制定位图标(无需图片资源)
直接用Canvas绘制定位图标的形状(圆形+三角形),无需依赖外部图片,性能更优:
修改MyCustomPainter类
class MyCustomPainter extends CustomPainter { final List<Offset> marks; const MyCustomPainter(this.marks); @override void paint(Canvas canvas, Size size) { // 定义图标尺寸 final double iconSize = 30; // 填充色(绿色) final Paint fillPaint = Paint()..color = Colors.green; // 描边色(白色边框) final Paint strokePaint = Paint() ..color = Colors.white ..strokeWidth = 2 ..style = PaintingStyle.stroke; // 遍历所有标记点,逐个绘制定位图标 for (final offset in marks) { // 绘制圆形主体 canvas.drawCircle(offset, iconSize / 2, fillPaint); canvas.drawCircle(offset, iconSize / 2, strokePaint); // 绘制下方的三角形 final Path trianglePath = Path() ..moveTo(offset.dx, offset.dy + iconSize / 2) ..lineTo(offset.dx - iconSize / 2, offset.dy + iconSize) ..lineTo(offset.dx + iconSize / 2, offset.dy + iconSize) ..close(); canvas.drawPath(trianglePath, fillPaint); canvas.drawPath(trianglePath, strokePaint); } } @override bool shouldRepaint(MyCustomPainter oldDelegate) { // 只有当标记列表变化时才重绘 return !listEquals(oldDelegate.marks, marks); } }
注意:原代码中
paint方法的async关键字要去掉,CustomPainter的paint方法不支持异步操作。
方案二:使用图片资源(系统图标/本地Asset)
如果你想用现成的定位图标图片(比如Flutter内置的Icons.location_on或自定义Asset图片),可以通过加载图片并绘制到Canvas实现:
步骤1:准备图片资源
- 若使用本地Asset图片:在
pubspec.yaml中配置资源路径,比如:assets: - assets/location_icon.png
步骤2:修改MyCustomPainter类
class MyCustomPainter extends CustomPainter { final List<Offset> marks; final ImageProvider imageProvider; Image? _iconImage; bool _isImageLoaded = false; MyCustomPainter(this.marks, this.imageProvider) { // 预加载图片,加载完成后触发重绘 imageProvider.resolve(const ImageConfiguration()).addListener( ImageStreamListener((info, _) { _iconImage = info.image; _isImageLoaded = true; markNeedsPaint(); }), ); } @override void paint(Canvas canvas, Size size) { if (!_isImageLoaded || _iconImage == null) return; final double iconSize = 30; for (final offset in marks) { // 以点击位置为中心绘制图片 final Rect destRect = Rect.fromCenter( center: offset, width: iconSize, height: iconSize, ); // 绘制图片到Canvas canvas.drawImageRect( _iconImage!, Rect.fromLTWH(0, 0, _iconImage!.width.toDouble(), _iconImage!.height.toDouble()), destRect, Paint(), ); } } @override bool shouldRepaint(MyCustomPainter oldDelegate) { return !listEquals(oldDelegate.marks, marks) || oldDelegate.imageProvider != imageProvider || oldDelegate._isImageLoaded != _isImageLoaded; } }
步骤3:更新HomePage中的CustomPaint调用
// ... 其他代码不变 ... child: CustomPaint( // 传入AssetImage作为图标资源 painter: MyCustomPainter(mark, const AssetImage('assets/location_icon.png')), ), // ... 其他代码不变 ...
完整修改后的代码(方案一示例)
import 'dart:ui'; import 'package:flutter/material.dart'; import 'package:flutter/foundation.dart'; // 引入listEquals class HomePage extends StatefulWidget { const HomePage({super.key}); @override State<HomePage> createState() => _HomePageState(); } class _HomePageState extends State<HomePage> { List<Offset> mark = []; void updatePosition(TapDownDetails details) { setState(() { mark.add(details.localPosition); print("mark:$mark"); }); } @override Widget build(BuildContext context) { return Scaffold( body: Center( child: GestureDetector( onTapDown: updatePosition, child: RepaintBoundary( child: Container( margin: const EdgeInsets.only(left: 8, right: 8), height: 300, width: double.infinity, decoration: BoxDecoration( border: Border.all(color: Colors.blue), image: const DecorationImage( image: AssetImage( 'assets/dark.jpg', ), fit: BoxFit.cover)), child: CustomPaint( painter: MyCustomPainter(mark), ), )), ), ), ); } } class MyCustomPainter extends CustomPainter { final List<Offset> marks; const MyCustomPainter(this.marks); @override void paint(Canvas canvas, Size size) { final double iconSize = 30; final Paint fillPaint = Paint()..color = Colors.green; final Paint strokePaint = Paint() ..color = Colors.white ..strokeWidth = 2 ..style = PaintingStyle.stroke; for (final offset in marks) { canvas.drawCircle(offset, iconSize / 2, fillPaint); canvas.drawCircle(offset, iconSize / 2, strokePaint); final Path trianglePath = Path() ..moveTo(offset.dx, offset.dy + iconSize / 2) ..lineTo(offset.dx - iconSize / 2, offset.dy + iconSize) ..lineTo(offset.dx + iconSize / 2, offset.dy + iconSize) ..close(); canvas.drawPath(trianglePath, fillPaint); canvas.drawPath(trianglePath, strokePaint); } } @override bool shouldRepaint(MyCustomPainter oldDelegate) { return !listEquals(oldDelegate.marks, marks); } }
内容的提问来源于stack exchange,提问作者Yuva
相关产品推荐
相关产品推荐

