Flutter 三方库 adaptive_image_picker 的鸿蒙化适配指南:零权限选图、纯 Dart 裁剪与目标大小压缩
欢迎加入 Flutter 鸿蒙化社区(oh-flutter 组织):鸿蒙系统Flutter开源库社区 - 开源代码托管,代码协作 - AtomGit 本文所用适配版插件的仓库地址:adaptive_image_picker:Flutter adaptive_image_picker 的 HarmonyOS/OpenHarmony 适配版:支持系统相册选择、相机拍照、裁剪与压缩,保留上游 API 和 Git 历史。 - AtomGit
一、应用背景:为什么鸿蒙上需要这个库
做跨平台业务的同学对"选头像"这个需求一定不陌生:从相册选一张图,按圆形裁一下,压到 100 KB 以内再上传。在 Flutter 生态里,这条链路过去通常要拼三个库——选图一个、裁剪一个、压缩一个。这不仅让依赖变重,还有几个长期痛点:
- 权限问题。传统选图方案依赖系统相册权限,应用市场审核经常因为权限声明与实际用途不匹配被驳回,隐私合规成本高。
- 裁剪依赖原生库。常见的 Flutter 裁剪库底层是原生实现,每引入一个就要多维护一份平台侧代码;鸿蒙(HarmonyOS / OpenHarmony)作为新平台,这类原生库往往"没有对应版本",直接导致整条链路在鸿蒙上断掉。
- 压缩不保证结果。多数压缩接口只给一个 quality 参数,压出来是 300 KB 还是 3 MB 全凭运气,而头像上传这类业务需要的是"必须小于某个字节数"的硬承诺。
adaptive_image_picker 正是针对这些痛点设计的 Flutter 媒体选择库:相册选择走系统 Photo Picker(零权限)、裁剪引擎完全用 Dart 实现(不依赖任何原生裁剪库)、压缩采用二分搜索逼近目标字节数(结果严格不超过上限)。它把选图、拍照、裁剪、压缩、URL 导入收进一个统一的 Dart API。
问题在于,这样一个"为跨平台而生"的库,官方只覆盖了 Android、iOS、Web 和桌面端。我们把它适配到了 HarmonyOS / OpenHarmony,并把适配成果开源在 AtomGit 上。本文就带你把这个库用在鸿蒙工程里,覆盖它的全部对外接口,并附上鸿蒙设备上的实测效果。
本文的验证环境:
- 设备:HarmonyOS 7.0.0(API 26)手机模拟器;
nova 15真机(ROM 6.1+)用于相机拍照验证 - IDE 与工具链:DevEco Studio 26.0.0 Release、Flutter OHOS 版本工具链
- 插件版本:
adaptive_image_picker1.0.2 + 鸿蒙适配提交
二、这个库有哪些功能
adaptive_image_picker 的能力可以分为"平台相关"和"平台无关"两层,这个分层也决定了鸿蒙化的工作量分布。
平台无关层(纯 Dart,天然跨平台,无需适配即可在鸿蒙运行):
- 交互式手势裁剪引擎:双指缩放、拖拽平移、九宫格参考线、圆形头像蒙版(抗锯齿透明通道)、90° 旋转、水平/垂直翻转,预设比例覆盖 1:1、16:9、4:3、3:2、9:16、自由与自定义;
- 目标大小压缩引擎:以二分搜索逼近
maxBytes,保证输出严格不超过目标字节数,支持转 WebP / JPEG / PNG,自动烘焙 EXIF 方向; - 自适应弹窗 UI:底部动作面板(相机 / 相册 / URL 三源选择),支持完整的主题定制与完全自定义构建;
- AdaptiveFile 统一模型:文件名、大小、宽高、MIME 类型、懒加载字节流、落盘与删除等文件操作。
平台相关层(需要鸿蒙原生实现,共 3 个通道方法):
- 相册选择:鸿蒙侧基于系统
PhotoViewPicker实现,单选、多选(最多 500 项)、图片/视频/混合媒体过滤均支持,且不申请任何媒体库权限; - 相机拍照:基于系统
cameraPicker实现,支持前置/后置切换,同样零权限; - 平台版本诊断:
getPlatformVersion返回形如HarmonyOS x.x.x的版本串,用于联调排查。
适配遵循了原库的取消语义:相册取消返回空列表,拍照取消返回 null,与 Android / iOS 行为完全一致,业务代码不需要写平台分支。
三、环境搭建
本文不展开环境安装步骤,请直接按照官方文档完成 Flutter 鸿蒙化环境搭建:
完成后再执行 flutter doctor 确认输出中不缺少鸿蒙构建所需项,即可继续下文。
四、在应用中引入三方库(AtomGit 仓库方式)
鸿蒙化版本托管在 AtomGit 的 oh-flutter 组织下,通过 Git 依赖直接引入。在应用工程的 pubspec.yaml 中添加:
dependencies:
adaptive_image_picker:
git:
url: https://atomgit.com/oh-flutter/adaptive_image_picker.git
如果想锁定到某个确定提交(推荐在团队协作中固定),可以追加 ref:
dependencies:
adaptive_image_picker:
git:
url: https://atomgit.com/oh-flutter/adaptive_image_picker.git
ref: main
然后拉取依赖:
flutter pub get
不需要任何额外的鸿蒙工程改动。 适配版本的 pubspec.yaml 已经注册了 ohos 平台的 pluginClass: AdaptiveImagePickerPlugin,ArkTS 侧实现随包携带,flutter pub get 之后构建鸿蒙 HAP 时插件会自动完成注册。如果你的工程是第一次跑鸿蒙目标,可以用下面命令构建并安装示例:
flutter build hap --debug
之后用 DevEco Studio(或 devecocli run)把生成的 HAP 装到模拟器或真机上即可。
五、调用接口实现功能(附鸿蒙设备实测效果)
代码里统一这样导入:
import 'package:adaptive_image_picker/adaptive_image_picker.dart';
下面按"选图 → 拍照 → 多选 → 裁剪 → 压缩 → 弹窗 → URL 导入"的完整业务链路,把库的全部对外接口过一遍。所有截图均为鸿蒙设备实拍。
5.1 平台版本诊断:getPlatformVersion
联调时先确认插件在鸿蒙侧注册成功,最简单的办法就是调一次版本接口:
final String? version = await AdaptiveImagePicker.getPlatformVersion();
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('当前平台:$version')),
);
在鸿蒙设备上返回 HarmonyOS 7.0.0(模拟器与真机的具体版本串以 deviceInfo.displayVersion 为准)。如果这里能正常弹出,说明 MethodChannel 已通,后面的接口都有保障。
5.2 相册单图选择:pickImage(source: ImageSource.gallery)
最基础的能力,一行代码拉起系统相册:
final AdaptiveFile? file = await AdaptiveImagePicker.pickImage(
source: ImageSource.gallery,
);
if (file != null) {
debugPrint('选中:${file.name},${file.formattedSize},${file.width}x${file.height}');
}
鸿蒙侧拉起的是系统 Photo Picker 界面,应用全程不需要申请媒体库权限。选中图片后,插件会把 Picker 返回的临时 URI 复制到应用缓存目录,再以普通文件路径返回给 Dart,name / size / mimeType / lastModified 等元数据一应俱全。用户在 Picker 里点返回取消时,得到的是 null,不会抛异常。

值得注意的是 Picker 界面上的"安全访问图库"提示:应用仅可访问用户选定的图片和视频,无法读取图库的其余内容——这正是"零权限"特性的直接来源,也是它与传统需要存储权限的选图方案的本质区别。

选择完成后返回示例应用,底部 Gallery 标签的徽标更新为 1,表示已收集到 1 个媒体文件。此时用 hdc 检查应用缓存目录,能看到生成的 adaptive_picker_*.jpg(实测 24,225 字节)——"Picker 临时 URI 复制到应用缓存、再以普通文件路径返回 Dart"这条链路完整闭环。

5.3 相机拍照:pickImage(source: ImageSource.camera)
final AdaptiveFile? photo = await AdaptiveImagePicker.pickImage(
source: ImageSource.camera,
preferredCameraDevice: CameraDevice.rear, // 前置用 CameraDevice.front
);
鸿蒙侧基于系统 cameraPicker 实现,拍照同样零权限。preferredCameraDevice 控制拉起时的前后摄像头;用户取消拍照时返回 null,与 Android / iOS 契约一致。拍照结果会写入应用缓存并以 AdaptiveFile 返回。拍照链路已在 nova 15 真机上验证通过(模拟器没有可用摄像头)。
5.4 多图与混合媒体选择:pickMultiple
final List<AdaptiveFile> files = await AdaptiveImagePicker.pickMultiple(
maxCount: 9,
mediaType: MediaType.image, // 视频用 MediaType.video,混合用 MediaType.all
);
for (final f in files) {
debugPrint('${f.name}: ${f.formattedSize}');
}
maxCount 会透传给系统 Picker 的最大可选数(鸿蒙侧系统上限为 500,插件会做收敛);mediaType 控制图片 / 视频 / 混合三种过滤模式。取消时返回空列表。配合 compressionOptions 可以对选中的每一张批量压缩:
final List<AdaptiveFile> files = await AdaptiveImagePicker.pickMultiple(
maxCount: 9,
compressionOptions: const CompressionOptions(
format: OutputFormat.webp,
maxWidth: 1920,
maxHeight: 1080,
),
);
5.5 交互式裁剪:cropImage 与 CropOptions
裁剪引擎是纯 Dart 实现,在鸿蒙上与在其他平台表现完全一致。既可以在选图时通过 cropOptions 直接串联,也可以对已有文件独立调用:
// 方式一:选图后直接进入裁剪(pickImage 的 cropOptions 参数)
final AdaptiveFile? avatar = await AdaptiveImagePicker.pickImage(
source: ImageSource.gallery,
context: context,
cropOptions: CropOptions.circle(title: '设置头像'), // 圆形头像蒙版
);
// 方式二:对已有文件独立打开裁剪器
final AdaptiveFile? cropped = await AdaptiveImagePicker.cropImage(
file: existingFile,
context: context,
options: const CropOptions(
showGrid: true, // 九宫格参考线
allowRotation: true, // 90° 旋转按钮
allowFlipping: true, // 水平/垂直翻转
aspectRatioPreset: CropAspectRatio.ratio16x9,
lockAspectRatio: true,
),
);
在鸿蒙设备上,双指缩放、拖拽、圆形蒙版、旋转翻转全部可用,手势跟手性与其他平台一致。
5.6 目标大小压缩:compressImage
这是这个库最有辨识度的能力——保证输出严格不超过目标字节数:
final AdaptiveFile compressed = await AdaptiveImagePicker.compressImage(
file: largeFile,
options: CompressionOptions.targetSize(
150 * 1024, // 输出保证 ≤ 150 KB
format: OutputFormat.webp, // 同时转成 WebP
),
);
debugPrint('${largeFile.formattedSize} -> ${compressed.formattedSize}');
内部实现是二分搜索质量参数,配合必要的降采样,因此对任意输入都能收敛到上限以内。此外还有几个开箱即用的预设:CompressionOptions.avatar()(头像)、CompressionOptions.webOptimized()(Web 上传)、CompressionOptions.thumbnail()(缩略图)。
下图为示例应用中目标大小压缩的配置界面:Strict Max File Size Target 拖到 250 KB,输出格式选 WebP (Recommended - Ultra Efficient)。

5.7 自适应来源弹窗:showPickerModal
想让用户自己挑"拍照还是相册"?showPickerModal 弹出三源选择面板,选中后自动进入对应流程,还可以把裁剪、压缩参数一次配齐:
final AdaptiveFile? file = await AdaptiveImagePicker.showPickerModal(
context: context,
options: const PickerOptions(
modalTitle: '选择图片来源',
sources: [ImageSource.camera, ImageSource.gallery, ImageSource.url],
),
cropOptions: CropOptions.circle(title: '调整头像'),
compressionOptions: CompressionOptions.avatar(maxBytes: 100 * 1024),
);
PickerOptions 还支持 theme 定制配色与圆角,甚至可以用 customModalBuilder 完全接管弹窗 UI(比如做成品牌化的自绘面板)。
5.8 URL 图片导入:fromUrl
从网络图直接进入处理管线(下载 → 可选裁剪 → 可选压缩):
final AdaptiveFile file = await AdaptiveImagePicker.fromUrl(
'https://picsum.photos/800/600',
context: context,
cropOptions: const CropOptions(
shape: CropShape.circle,
aspectRatioPreset: CropAspectRatio.ratio1x1,
),
compressionOptions: const CompressionOptions(maxBytes: 200 * 1024),
);
唯一需要配置的场景来了:URL 导入要访问网络,需要在鸿蒙工程的 entry/src/main/module.json5 中声明网络权限(相册选择和拍照都不需要任何权限声明):
// entry/src/main/module.json5
{
"module": {
// ...
"requestPermissions": [
{ "name": "ohos.permission.INTERNET" }
]
}
}
漏掉这一声明是 URL 导入失败最常见的原因,见文末 FAQ。
5.9 AdaptiveFile:统一的结果模型
所有接口的返回值都是 AdaptiveFile,常用属性和方法如下:
file.name; // 文件名(含扩展名)
file.formattedSize; // 可读大小,如 "148.2 KB"
file.width; file.height; // 像素宽高
file.aspectRatio; // 宽高比
file.mimeType; // MIME 类型
file.path; // 本地路径(鸿蒙上是应用缓存目录内路径)
final bytes = await file.readAsBytes(); // 懒加载字节流
final saved = await file.saveToDirectory(dirPath); // 按原文件名保存到目录
await file.delete(); // 删除本地文件
需要注意:鸿蒙上相册/拍照结果落在应用缓存目录(cacheDir),适合做即时上传、预览;如果业务要长期保留,请用 saveToDirectory 转存到应用文档目录,不要依赖缓存路径长期有效。
六、FAQ:适配与使用过程中的常见问题
Q1:URL 导入一直失败,报网络错误?
九成是没在 module.json5 里声明 ohos.permission.INTERNET(配置见 5.8 节)。声明后重新构建安装即可。相册与拍照不需要额外权限;如果你发现相册选择也在申请什么权限,那说明用的不是本适配版本。
Q2:运行时报 MissingPluginException 怎么办?
先确认依赖确实解析到了 AtomGit 版本(看 pubspec.lock 中 adaptive_image_picker 的来源是否为 git 描述),然后重新执行 flutter pub get 让鸿蒙工程重新生成插件注册文件。如果是在自己改造插件代码时出现,检查 Dart 侧插件类的导出是否完整——鸿蒙生成的注册链路按包入口解析插件类,仅在某一个分平台实现文件里声明是找不到的。
Q3:选到的图片路径可以长期持有吗?
不可以。系统 Picker 返回的是临时 URI,插件已在返回前把它复制进应用缓存并校验非空,但缓存目录随时可能被系统回收。需要长期保留就 saveToDirectory 转存。这也是插件的设计契约:返回给你的一定是可直接读取的普通文件路径,而不是要求你自己处理 URI。
Q4:Windows 上构建鸿蒙工程时报奇怪的路径错误?
Windows 下 Hvigor 构建链路对路径长度敏感,工程路径接近 260 字符时可能失败。把工程放到 C:\Dev\<项目名> 这类短 ASCII 路径下再构建。我们自己适配时就踩过这个坑:换到短路径后一次通过,源码本身没有任何问题。
Q5:库本身有问题,如何提交 issue?
在插件的 AtomGit 仓库提 issue:issues - 鸿蒙系统Flutter开源库社区/adaptive_image_picker - AtomGit
为了保证可定位,请附上:
flutter doctor -v完整输出;- 设备型号与系统版本(模拟器注明 API 版本);
- 最小复现代码片段与完整调用参数;
- 预期行为与实际行为,以及
devecocli log中相关日志; - 能截图的尽量附截图。
Q6:我能修复它,如何提交 PR?
欢迎直接参与共建,流程如下:
- Fork adaptive_image_picker:Flutter adaptive_image_picker 的 HarmonyOS/OpenHarmony 适配版:支持系统相册选择、相机拍照、裁剪与压缩,保留上游 API 和 Git 历史。 - AtomGit 到个人账号,克隆到短路径目录;
- 从
main拉出功能分支(如fix/cache-copy-validation); - 修改并保证本地验证全部通过:
flutter analyze零问题、flutter test全绿、flutter build hap --debug构建成功,涉及设备行为的改动必须在模拟器或真机实测; - 提交并推送到你的 Fork,在 AtomGit 上对源仓库
main发起 Pull Request; - PR 描述中写清问题、改动点与验证证据(分析/测试/构建/设备截图),便于维护者复核。
七、总结
本文以 adaptive_image_picker 为例,完整走了一遍"鸿蒙化三方库在业务中的落地":从 AtomGit 仓库引入依赖,到相册、拍照、多选、裁剪、压缩、弹窗、URL 导入的全部接口实测。对业务方来说,它用零权限的系统 Picker 省掉了隐私合规的心智负担,用纯 Dart 的裁剪引擎绕开了"新平台没有原生裁剪库"的困境,用二分压缩给出了"不超过目标大小"的硬承诺;对想做鸿蒙共建的同学来说,这类纯 Dart 为主、少量平台通道的库,恰好是入手鸿蒙化适配的理想对象——平台相关面越小,验证成本越低。
如果你在适配或使用中踩到新的坑,欢迎到仓库提 issue / PR,也欢迎加入社区一起把 Flutter 鸿蒙生态补齐。
欢迎加入 Flutter 鸿蒙化社区(oh-flutter 组织):鸿蒙系统Flutter开源库社区 - 开源代码托管,代码协作 - AtomGit
更多推荐


所有评论(0)