Flutter中如何让自定义绘图画布尺寸与图片组件保持一致?
解决方案
要让绘图画布和上传图片的实际渲染尺寸一致,关键是获取Image组件在Widget树中的真实尺寸,而非屏幕尺寸。以下是具体修改方案:
修改步骤
- 添加GlobalKey获取Image尺寸:给
Image.file组件绑定一个GlobalKey,用于后续获取它的渲染尺寸。 - 使用Hook存储图片尺寸:通过
useState保存图片的实际大小,确保尺寸更新时能触发组件重建。 - 在组件渲染后获取尺寸:利用
useEffect和addPostFrameCallback,在Image组件渲染完成后读取其真实尺寸。 - 基于图片尺寸构建绘图层:用获取到的图片尺寸替代原来的屏幕约束,设置绘图区域的宽高,并通过
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
相关产品推荐
相关产品推荐

