Flutter 相机、相册图片裁剪问题处理与封装使用(image_picker、image_cropper)

929 阅读2分钟

一、安装

  • 安装插件,注意:裁剪库 需要额外配置

    # 相机、相册库
    $ flutter pub add image_picker
    # 裁剪库
    $ flutter pub add image_cropper
    
  • 每次修改了配置,需要 cleanget 重新安装,要不然会使用编译好的缓存运行,所以避免问题,从根本解决问题:

    # 先清空
    $ flutter clean
    # 再安装
    $ flutter pub get
    # 运行
    $ flutter run
    
  • Android 权限配置,文件路径 android/app/src/main/AndroidManifest.xml

    <!-- 请求相机权限 -->
    <uses-permission android:name="android.permission.CAMERA"/>
    <!-- 请求读取存储权限(读取相册) -->
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
    <!-- 请求写入存储权限(选择和保存相册中的图片)-->
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    <!-- 如果使用了访问相册,且是 Android 10(API 29)及以上版本,还需要配置 -->
    <uses-permission android:name="android.permission.ACCESS_MEDIA_LOCATION" />
    

    image.png

  • iOS 权限配置,文件路径 ios/Runner/Info.plist

    <key>Localization native development region</key>
    <string>zh_CN</string>
    <key>NSCameraUsageDescription</key>
    <string>我们需要访问您的相机</string>
    <key>NSPhotoLibraryUsageDescription</key>
    <string>我们需要访问您的照片库</string>
    

    Localization native development region 会使相机、相册打开后显示中文。附:# Flutter iOS 调起相机、相册显示英文,需要改成中文

二、image_cropper 安卓问题处理

  • 不需要裁剪功能,就不用看这段了。

  • image_cropperiOS 上没问题,但是在安卓上需要将 compileSdk 修改为 35,文件路径 android/app/build.gradle

    未来最新的版本可能不是 35,可能更高,按着报错改就行。

    image.png

    不修改会报错:

    Your project is configured to compile against Android SDK 34, but the following plugin(s) require to be compiled against a higher Android SDK version:
    - image_cropper compiles against Android SDK 35
    Fix this issue by compiling against the highest Android SDK version (they are backward compatible).
    Add the following to /Users/dengzemiao/Desktop/Project/flutter/flutter-edu-app/android/app/build.gradle:
    
        android {
            compileSdk = 35
            ...
        }
    
    
    FAILURE: Build failed with an exception.
    
    * What went wrong:
    Execution failed for task ':app:processDebugResources'.
    > A failure occurred while executing com.android.build.gradle.internal.res.LinkApplicationAndroidResourcesTask$TaskAction
       > Android resource linking failed
         com.example.testbaseproject.app-mergeDebugResources-42:/values-v35/values-v35.xml:4: error: style attribute 'android:attr/windowOptOutEdgeToEdgeEnforcement' not found.
         error: failed linking references.
    
    
    * Try:
    > Run with --stacktrace option to get the stack trace.
    > Run with --info or --debug option to get more log output.
    > Run with --scan to get full insights.
    > Get more help at https://help.gradle.org.
    
    BUILD FAILED in 9s
    

    修改后,还不行,需要确定 Android Studio 安装好了 35sdk,注意安装包支持的 CPU 类型,安装好了就行了。

    image.png

  • nonono,再次运行,可以正常使用了,但是在拍好照片进行裁剪的还会报错,可能还会报错,一大串的,可以搜搜里面有没有 com.yalantis.ucrop.UCropActivity,就是缺少了 activity 的申明,所以还需要配置下,文件路径 android/app/src/main/AndroidManifest.xml

    application 里面再加一个 activity 申明:

    <activity android:name="com.yalantis.ucrop.UCropActivity"
              android:theme="@style/Theme.AppCompat.Light.NoActionBar"/>
    

    image.png

  • 到这里就真的 ok 了,可以放心使用裁剪了,如果不需要裁剪后面这些配置可以不用配置,只要配置权限即可。

三、裁剪封装

  • 使用

    // 导入
    import 'package:base_project/utils/camera_utils.dart';
    
    // 使用
    CustomBottomSheet.show(
      context: context,
      list: ['拍照', '打开相册'],
      onConfirm: (index) async {
        if (index == 0) {
          // 打开相机
          await camera.showCamera(
            cropping: true,
            onChanged: (value) => {
              print('图片路径: $value')
            }
          );
        } else {
          // 打开相册
          await camera.showAlbum(
            cropping: true,
            onChanged: (value) => {
              print('图片路径: $value')
            }
          );
        }
      },
    );
    
  • camera_utils.dart

    import 'package:flutter/material.dart';
    import 'package:image_picker/image_picker.dart';
    import 'package:image_cropper/image_cropper.dart';
    
    class CameraUtils {
    
      // 静态变量存储单例
      static final CameraUtils _instance = CameraUtils._internal();
      // 静态方法获取单例实例
      factory CameraUtils() => _instance;
      // 私有构造函数,确保只能通过工厂方法获取实例
      CameraUtils._internal();
    
      // 静态的 _picker 变量,确保只会初始化一次
      final ImagePicker _picker = ImagePicker();
    
      // 打开相机
      Future<void> showCamera({
        bool? cropping,
        ValueChanged<String>? onChanged
      }) async {
        try {
          final XFile? file = await _picker.pickImage(source: ImageSource.camera);
          if (file != null) {
            if (cropping == true) {
              await _cropImage(
                path: file.path,
                onChanged: onChanged
              );
            } else {
              if (onChanged != null) { onChanged(file.path); }
            }
          } else {
            if (onChanged != null) { onChanged(''); }
          }
        } catch (e) {
          if (onChanged != null) { onChanged(''); }
          // print('图片选择出错: $e');
        }
      }
    
      // 打开相册
      Future<void> showAlbum({
        bool? cropping,
        ValueChanged<String>? onChanged
      }) async {
        try {
          final XFile? file = await _picker.pickImage(source: ImageSource.gallery);
          if (file != null) {
            if (cropping == true) {
              await _cropImage(
                path: file.path,
                onChanged: onChanged
              );
            } else {
              if (onChanged != null) { onChanged(file.path); }
            }
          } else {
            if (onChanged != null) { onChanged(''); }
          }
        } catch (e) {
          // print('图片选择出错: $e');
          if (onChanged != null) { onChanged(''); }
        }
      }
    
      // 图片裁剪方法
      Future<void> _cropImage({
        required String path,
        ValueChanged<String>? onChanged
      }) async {
        try {
          // 使用 image_cropper 进行裁剪
          final file = await ImageCropper().cropImage(
            sourcePath: path,
            uiSettings: [
              AndroidUiSettings(
                toolbarTitle: '裁剪图片',
                toolbarColor: Colors.deepOrange,
                toolbarWidgetColor: Colors.white,
                initAspectRatio: CropAspectRatioPreset.square,
                lockAspectRatio: false,
              ),
              IOSUiSettings(
                minimumAspectRatio: 1.0,
                aspectRatioLockEnabled: false
              ),
            ]
          );
          // 如果裁剪成功
          if (file != null && onChanged != null) {
            // 图片裁剪成功,返回裁剪后的图片
            onChanged(file.path);
          }
        } catch (e) {
          // print('裁剪出错: $e');
          if (onChanged != null) { onChanged(''); }
        }
      }
    }