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

Flutter/Dart中如何围绕参考点实现旋转与缩放?

在Flutter的Canvas中围绕指定参考点旋转/缩放图片

问题描述

我使用CustomPaint组件,传入继承自CustomPainter的对象,在paint()方法中通过Canvas绘制图片。现在需要围绕非原点的参考点(比如Offset(100,200))旋转图片,但按照其他语言的思路(先将参考点移到原点,旋转后移回)实现后,旋转仍围绕图片左上角进行。后续还想实现围绕该参考点的缩放,同样无效。

我尝试过Transform组件可以实现需求,但想知道如何在paint()方法和Canvas中直接实现。

我的错误代码如下:

class SomeWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return CustomPaint(painter: SomePainter());
  }
}

class SomePainter extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {

    canvas.translate(-100, -200);
    // 逆时针旋转30°
    canvas.rotate(-30 / 180 * pi);
    canvas.translate(100, 200);  // 可能需要先对这个向量做sin/cos旋转?

    canvas.drawImage(image, Offset.zero, Paint());
  }
}

Transform的可行代码:

Transform(
  // 我的图片是800x400,因此100,200对应图片的12.5%/50%位置
  alignment: Alignment(0.125 * 2 - 1, 0.50 * 2 - 1),
  transform: Matrix4.rotationZ(-30 / 180 * pi),
  child: CustomPaint(painter: MyPainter()),
),

解决方案:正确的Canvas变换顺序

Flutter中Canvas的变换是矩阵叠加,执行顺序为从后往前作用(即最后调用的变换会先应用到绘制内容上)。围绕参考点旋转/缩放的正确步骤如下:

  1. 将画布原点平移到参考点位置
  2. 执行旋转/缩放操作
  3. 将画布原点平移回原位置的反向偏移
  4. 绘制图片

1. 围绕参考点旋转的实现

修正后的paint()方法代码:

class SomePainter extends CustomPainter {
  final Image image;
  final Offset pivot = const Offset(100, 200); // 参考点
  final double rotationAngle = -30 / 180 * pi; // 逆时针30°

  SomePainter(this.image);

  @override
  void paint(Canvas canvas, Size size) {
    // 保存画布当前状态(可选,如需后续还原变换)
    canvas.save();

    // 步骤1:将原点移到参考点
    canvas.translate(pivot.dx, pivot.dy);
    // 步骤2:执行旋转
    canvas.rotate(rotationAngle);
    // 步骤3:将原点移回原位置的反向
    canvas.translate(-pivot.dx, -pivot.dy);

    // 绘制图片
    canvas.drawImage(image, Offset.zero, Paint());

    // 还原画布状态(如果之前调用了save)
    canvas.restore();
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) =>
      oldDelegate is SomePainter && oldDelegate.image != image;
}

2. 围绕参考点缩放的实现

缩放逻辑和旋转完全一致,只需替换变换操作:

class SomePainter extends CustomPainter {
  final Image image;
  final Offset pivot = const Offset(100, 200); // 参考点
  final double scale = 1.5; // 放大1.5倍

  SomePainter(this.image);

  @override
  void paint(Canvas canvas, Size size) {
    canvas.save();

    // 平移到参考点
    canvas.translate(pivot.dx, pivot.dy);
    // 执行缩放
    canvas.scale(scale);
    // 平移回原位置
    canvas.translate(-pivot.dx, -pivot.dy);

    canvas.drawImage(image, Offset.zero, Paint());

    canvas.restore();
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) =>
      oldDelegate is SomePainter && oldDelegate.image != image;
}

3. 同时旋转+缩放

如需同时执行两种变换,按需求顺序调用即可:

canvas.translate(pivot.dx, pivot.dy);
canvas.scale(scale);
canvas.rotate(rotationAngle);
canvas.translate(-pivot.dx, -pivot.dy);

为什么之前的代码无效?

你之前的变换顺序是:

canvas.translate(-100, -200);
canvas.rotate(...);
canvas.translate(100, 200);

这个顺序的实际作用是:先将图片向右下平移(100,200),然后旋转,再向左上平移(100,200),最终旋转中心还是图片的左上角。而正确的顺序是先将原点移到参考点,旋转后再移回,这样所有后续绘制内容都会围绕参考点完成变换。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 07:47:39