Flutter基于容器位置的图片裁剪功能需求修改
需求修改说明
现有Flutter代码已实现固定容器加载图片、可拖拽移动的选框容器及裁剪按钮功能,点击裁剪按钮会跳转新页面展示裁剪区域的图片。现需调整为:点击裁剪按钮后,不在新页面展示裁剪图,而是在原可移动选框的相同位置显示裁剪后的图片,同时隐藏选框外的原图片,效果参考以下示例图:
原操作状态

目标效果

修改后的完整代码
import 'dart:typed_data'; import 'dart:ui' as ui; import 'package:flutter/material.dart'; void main() { runApp(MaterialApp( home: Scaffold(body: ImageCrop(image: const AssetImage("images/rose1.png"))))); } class ImageCrop extends StatefulWidget { const ImageCrop({Key? key, required this.image}) : super(key: key); final ImageProvider image; @override State<ImageCrop> createState() => _ImageCropState(); } class _ImageCropState extends State<ImageCrop> { late ImageStream _imageStream; ui.Image? _image; late double _left; late double _top; late double _width; late double _height; // 标记是否已完成裁剪 bool _isCropped = false; // 存储裁剪后的图片数据 Uint8List? _croppedImageData; @override void initState() { super.initState(); _left = 0.0; _top = 0.0; _width = 100.0; _height = 100.0; WidgetsBinding.instance?.addPostFrameCallback((_) { _getImage(); }); } void _getImage() { _imageStream = widget.image.resolve(createLocalImageConfiguration(context)); _imageStream.addListener(ImageStreamListener(_updateImage)); } void _updateImage(ImageInfo imageInfo, bool synchronousCall) { setState(() { _image = imageInfo.image; }); } @override void didUpdateWidget(ImageCrop oldWidget) { super.didUpdateWidget(oldWidget); if (widget.image != oldWidget.image) { _imageStream.removeListener(ImageStreamListener(_updateImage)); _imageStream = widget.image.resolve(createLocalImageConfiguration(context)); _imageStream.addListener(ImageStreamListener(_updateImage)); } } @override Widget build(BuildContext context) { return Center( child: Padding( padding: const EdgeInsets.only(top:50), child: Column( children: [ ElevatedButton( onPressed: _isCropped ? null // 裁剪完成后禁用按钮 : () async { final data = await _crop(); if (data != null) { setState(() { _croppedImageData = data.buffer.asUint8List(); _isCropped = true; }); } }, child: Text(_isCropped ? '已裁剪' : '裁剪'), ), SizedBox( width: 200, height: 200, child: Stack( children: [ if (!_isCropped) Container( width: 200, height: 200, decoration: BoxDecoration( border: Border.all(color: Colors.black) ), child: Image(image: widget.image, fit:BoxFit.contain,) ), if (!_isCropped) Positioned( left: _left, top: _top, child: GestureDetector( onPanUpdate: (details) { setState(() { // 限制选框不能超出原图片容器范围 _left = (_left + details.delta.dx).clamp(0.0, 200 - _width); _top = (_top + details.delta.dy).clamp(0.0, 200 - _height); }); }, child: Container( width: _width, height: _height, decoration: BoxDecoration(border: Border.all(color: Colors.black38)), ), ), ), if (_isCropped && _croppedImageData != null) Positioned( left: _left, top: _top, child: Container( width: _width, height: _height, decoration: BoxDecoration( border: Border.all(color: Colors.black) ), child: Image.memory( _croppedImageData!, fit: BoxFit.fill, ), ), ), ], ), ), ], ), ), ); } Future<ByteData?> _crop() async { if (_image == null) return null; // 基于原图片容器尺寸计算缩放比例,确保裁剪区域准确 final imageContainerSize = 200.0; final scale = _image!.width / imageContainerSize; final left = _left * scale; final top = _top * scale; final width = _width * scale; final height = _height * scale; final recorder = ui.PictureRecorder(); final src = Rect.fromLTWH(left, top, width, height); final dst = Offset.zero & src.size; final canvas = Canvas(recorder, dst); canvas.drawImageRect(_image!, src, dst, Paint()); final picture = recorder.endRecording(); final cropped = await picture.toImage(width.toInt(), height.toInt()); return await cropped.toByteData(format: ui.ImageByteFormat.png); } }
修改关键点说明
- 新增状态变量:
_isCropped标记裁剪完成状态,_croppedImageData存储裁剪后的图片二进制数据 - 裁剪按钮逻辑调整:移除页面跳转,将裁剪结果转为
Uint8List后更新状态,完成后禁用按钮 - 条件渲染布局:根据
_isCropped状态切换显示内容——未裁剪时显示原图片和可拖拽选框;裁剪完成后仅在原选框位置展示裁剪图 - 选框边界限制:添加
clamp方法限制选框不能超出原图片容器范围,避免操作异常 - 缩放逻辑修正:将原基于屏幕宽度的缩放计算改为基于图片容器固定尺寸,确保裁剪区域计算精准
内容的提问来源于stack exchange,提问作者Harsh Rajpara
相关产品推荐
相关产品推荐

