Flutter Canvas圆形内drawImage图片位置错位解决方法
问题原因
图片和圆形无法精准对齐的核心原因有3个:
- 直接调用
drawImage时默认以图片左上角为绘制起点,不会自动对齐圆心 - 没有做圆形区域裁切,图片会超出圆形边界
- 没有对图片做等比缩放+坐标偏移计算,图片尺寸和圆形尺寸不匹配
修复方案
1. 导入依赖
在自定义绘制类的文件顶部引入数学计算库:
import 'dart:math';
2. 修改Circle组件,新增图片入参+预加载逻辑
提前加载网络图片,避免绘制时拿不到图片尺寸:
class Circle extends StatefulWidget { final Map<String, double> center; final double radius; final Color color; // 新增图片地址入参 final String? imageUrl; const Circle( {Key? key, required this.center, required this.radius, required this.color, this.imageUrl}) : super(key: key); @override _CircleState createState() => _CircleState(); } class _CircleState extends State<Circle> { ImageProvider? _imageProvider; ImageStream? _imageStream; ImageInfo? _imageInfo; bool _imageLoaded = false; @override void initState() { super.initState(); // 提前加载网络图片 if (widget.imageUrl != null) { _imageProvider = NetworkImage(widget.imageUrl!); _loadImage(); } } void _loadImage() { _imageStream = _imageProvider!.resolve(const ImageConfiguration()); _imageStream!.addListener(ImageStreamListener((info, sync) { setState(() { _imageInfo = info; _imageLoaded = true; }); })); } @override void dispose() { _imageStream?.removeListener(ImageStreamListener((info, sync) {})); super.dispose(); } @override Widget build(BuildContext context) { return CustomPaint( size: Size(MediaQuery.of(context).size.width / 5, MediaQuery.of(context).size.height), painter: DrawCircle( center: widget.center, radius: widget.radius, color: widget.color, // 修复原代码写死Colors.white的问题 imageInfo: _imageLoaded ? _imageInfo : null, )); } }
3. 修改DrawCircle绘制逻辑,实现圆心对齐+圆形裁切
核心逻辑:先裁切圆形绘制区域,再计算图片缩放比例和偏移量,保证图片中心和圆心完全重合:
class DrawCircle extends CustomPainter { final Map<String, double> center; final double radius; final Color color; final ImageInfo? imageInfo; DrawCircle({ Key? key, required this.center, required this.radius, required this.color, this.imageInfo, }); @override void paint(Canvas canvas, Size size) { final circleCenter = Offset(center["x"]!, center["y"]!); // 绘制圆形底色 Paint brush = Paint() ..color = color ..strokeCap = StrokeCap.round ..style = PaintingStyle.fill ..strokeWidth = 30; canvas.drawCircle(circleCenter, radius, brush); // 绘制圆形内图片 if (imageInfo != null) { final img = imageInfo!.image; // 保存画布状态,裁切后恢复 canvas.save(); // 裁切圆形区域,仅圆形范围内内容可见 canvas.clipPath(Path()..addOval(Rect.fromCircle(center: circleCenter, radius: radius))); // 计算图片等比缩放比例,保证图片覆盖整个圆形不变形 final double imgWidth = img.width.toDouble(); final double imgHeight = img.height.toDouble(); final double scale = max(radius * 2 / imgWidth, radius * 2 / imgHeight); // 计算绘制偏移量,让图片中心与圆心完全重合 final double drawX = circleCenter.dx - (imgWidth * scale) / 2; final double drawY = circleCenter.dy - (imgHeight * scale) / 2; // 绘制缩放后的图片 canvas.drawImageRect( img, Rect.fromLTWH(0, 0, imgWidth, imgHeight), Rect.fromLTWH(drawX, drawY, imgWidth * scale, imgHeight * scale), Paint() ); // 恢复画布状态 canvas.restore(); } } @override bool shouldRepaint(covariant DrawCircle oldDelegate) { return oldDelegate.imageInfo != imageInfo || oldDelegate.radius != radius; } }
4. 首页布局传入图片地址即可
SizedBox( height: Get.height / 2 + 20, width: MediaQuery.of(context).size.width, child: WidgetSlider(children: [ Circle( center: {"x": 40, "y": 100}, radius: 70, color: Colors.green, imageUrl: "对应圆形的图片地址"), Circle( center: {"x": -90, "y": 280}, radius: 65, color: Colors.grey, imageUrl: "对应圆形的图片地址"), // 其余Circle组件按相同规则传入imageUrl即可 ]), )
关键注意点
- 不要直接使用
drawImage,该方法不会自动缩放图片,会导致显示尺寸异常,优先用drawImageRect指定绘制区域 - 必须先做圆形裁切,否则图片会溢出圆形边界
- 计算绘制坐标时一定要减去图片缩放后宽高的一半,否则图片默认从左上角开始绘制,必然和圆心错位
内容的提问来源于stack exchange,提问作者Büşra Yamaner
相关产品推荐
相关产品推荐

