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

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);
  }
}

修改关键点说明

  1. 新增状态变量:_isCropped标记裁剪完成状态,_croppedImageData存储裁剪后的图片二进制数据
  2. 裁剪按钮逻辑调整:移除页面跳转,将裁剪结果转为Uint8List后更新状态,完成后禁用按钮
  3. 条件渲染布局:根据_isCropped状态切换显示内容——未裁剪时显示原图片和可拖拽选框;裁剪完成后仅在原选框位置展示裁剪图
  4. 选框边界限制:添加clamp方法限制选框不能超出原图片容器范围,避免操作异常
  5. 缩放逻辑修正:将原基于屏幕宽度的缩放计算改为基于图片容器固定尺寸,确保裁剪区域计算精准

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.26 13:58:34