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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.03 06:57:28