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

Flutter中如何让自定义绘图画布尺寸与图片组件保持一致?

解决方案

要让绘图画布和上传图片的实际渲染尺寸一致,关键是获取Image组件在Widget树中的真实尺寸,而非屏幕尺寸。以下是具体修改方案:

修改步骤

  1. 添加GlobalKey获取Image尺寸:给Image.file组件绑定一个GlobalKey,用于后续获取它的渲染尺寸。
  2. 使用Hook存储图片尺寸:通过useState保存图片的实际大小,确保尺寸更新时能触发组件重建。
  3. 在组件渲染后获取尺寸:利用useEffect和addPostFrameCallback,在Image组件渲染完成后读取其真实尺寸。
  4. 基于图片尺寸构建绘图层:用获取到的图片尺寸替代原来的屏幕约束,设置绘图区域的宽高,并通过ClipRRect限制绘图范围。

修改后的完整代码

class DrawingCanvas extends HookWidget {
  final ValueNotifier<Color> selectedColor;
  final ValueNotifier<double> strokeSize;
  final ValueNotifier<double> eraserSize;
  final ValueNotifier<DrawingMode> drawingMode;
  final AnimationController sideBarController;
  final ValueNotifier<Sketch?> currentSketch;
  final ValueNotifier<List<Sketch>> allSketches;
  final GlobalKey canvasGlobalKey;
  final ValueNotifier<bool> filled;
  final String imagePath;
  final double? imageHeight;
  final double? imageWidth;

  const DrawingCanvas({
    Key? key,
    required this.selectedColor,
    required this.strokeSize,
    required this.eraserSize,
    required this.drawingMode,
    required this.sideBarController,
    required this.currentSketch,
    required this.allSketches,
    required this.canvasGlobalKey,
    required this.filled,
    required this.imagePath,
    this.imageWidth,
    this.imageHeight,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    // 用于获取Image组件的渲染尺寸
    final _imageKey = GlobalKey();
    // 存储图片的实际渲染尺寸
    final imageSize = useState<Size?>(null);

    // 在组件渲染完成后获取Image的尺寸
    useEffect(() {
      WidgetsBinding.instance.addPostFrameCallback((_) {
        final renderBox = _imageKey.currentContext?.findRenderObject() as RenderBox?;
        if (renderBox != null) {
          imageSize.value = renderBox.size;
        }
      });
      // 监听屏幕尺寸变化,适配旋转等场景
      return () {};
    }, [MediaQuery.of(context).size]);

    return Stack(
      children: [
        Image.file(
          File(imagePath),
          key: _imageKey,
          // 可根据需求设置fit属性,比如BoxFit.contain/cover等,不影响尺寸获取
          // fit: BoxFit.contain,
        ),
        // 只有获取到图片尺寸后才渲染绘图层
        if (imageSize.value != null)
          Stack(
            children: [
              ClipRRect(
                child: SizedBox(
                  width: imageSize.value!.width,
                  height: imageSize.value!.height,
                  child: buildAllPaths(),
                ),
              ),
              ClipRRect(
                child: SizedBox(
                  width: imageSize.value!.width,
                  height: imageSize.value!.height,
                  child: buildCurrentPath(context),
                ),
              ),
            ],
          ),
      ],
    );
  }

  Widget buildAllPaths() {
    return RepaintBoundary(
      child: CustomPaint(
        painter: SketchPainter(sketches: allSketches.value),
      ),
    );
  }

  Widget buildCurrentPath(BuildContext context) {
    return Listener(
      onPointerDown: (details) {
        final box = context.findRenderObject() as RenderBox;
        final offset = box.globalToLocal(details.position);

        currentSketch.value = Sketch.fromDrawingMode(
          Sketch(
            points: [offset],
            color: selectedColor.value,
            size: strokeSize.value,
          ),
          drawingMode.value,
          filled.value,
        );
      },
      onPointerMove: (details) {
        final box = context.findRenderObject() as RenderBox;
        final offset = box.globalToLocal(details.position);
        final points = List<Offset>.from(currentSketch.value?.points ?? [])
          ..add(offset);

        currentSketch.value = Sketch.fromDrawingMode(
            Sketch(
                points: points,
                color: selectedColor.value,
                size: strokeSize.value),
            drawingMode.value,
            filled.value);
      },
      onPointerUp: (details) {
        allSketches.value = List<Sketch>.from(allSketches.value)
          ..add(currentSketch.value!);
      },
      child: RepaintBoundary(
        child: SizedBox.expand(
          child: CustomPaint(
            painter: SketchPainter(
                sketches:
                    currentSketch.value == null ? [] : [currentSketch.value!]),
          ),
        ),
      ),
    );
  }
}

关键说明

  • GlobalKey的作用:通过它可以定位到Image组件的RenderBox,从而获取组件在屏幕上的实际宽高。
  • addPostFrameCallback:确保在当前帧渲染完成后再读取尺寸,避免组件还未渲染导致尺寸为null。
  • 适配屏幕旋转:将MediaQuery.of(context).size加入useEffect的依赖,当屏幕尺寸变化时重新获取图片尺寸,保证绘图层始终匹配图片大小。
  • ClipRRect的作用:严格限制绘图区域,即使出现坐标计算误差,也能确保绘制内容不会超出图片范围。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.08 11:47:14