37 相册读取与图片选择:PhotoViewPicker 实战
37 相册读取与图片选择:PhotoViewPicker 实战
前言
图:37 相册读取与图片选择:PhotoViewPicker 实战 运行效果截图(HarmonyOS NEXT)
除了直接拍照,从相册选择已有图片也是常见的图片获取方式。鸿蒙提供了PhotoViewPicker——一个系统级的图片选择器,让用户可以从相册中选择一张或多张图片。
本文以"鹿鹿·笔迹心理分析"项目中 [CapturePage.ets](file:///Users/fiona/Downloads/bijixinli/harmony-app/entry/src/main/ets/pages/CapturePage.ets) 的pickFromAlbum()方法为例,深入解析 PhotoViewPicker 的使用方法和相册选择流程。
鸿蒙官方·PhotoViewPicker 文档:developer.huawei.com
项目源码仓库:harmony-app GitHub
图:PhotoViewPicker 相册选择流程——创建选择器 → 配置选项 → 获取 URI
一、PhotoViewPicker 基础
1.1 模块导入
import{photoAccessHelper}from'@kit.MediaLibraryKit'1.2 基本使用
privateasyncpickFromAlbum(){try{// ① 创建选择器constphotoPicker=newphotoAccessHelper.PhotoViewPicker()// ② 配置选项constpickerOptions:photoAccessHelper.PhotoSelectOptions={maxSelectNumber:1// 最多选择 1 张}// ③ 打开选择器constresult:photoAccessHelper.PhotoSelectResult=awaitphotoPicker.select(pickerOptions)// ④ 处理结果if(result&&result.photoUris&&result.photoUris.length>0){this.previewPath=result.photoUris[0]this.hasPreview=truethis.source='gallery'}}catch(error){hilog.error(0x0000,TAG,'相册选择失败: %s',JSON.stringify(error))}}二、PhotoSelectOptions 配置
2.1 选项详解
interfacePhotoSelectOptions{maxSelectNumber:number// 最大选择数量MIME?:string[]// 文件类型过滤isEdit?:boolean// 是否允许编辑}2.2 实际配置
| 参数 | 本项目值 | 说明 |
|---|---|---|
maxSelectNumber | 1 | 一次分析一张笔迹图片 |
MIME | 未指定 | 默认所有图片类型 |
isEdit | 未指定 | 选择后不编辑 |
2.3 扩展配置示例
// 多选 + MIME 过滤constpickerOptions:photoAccessHelper.PhotoSelectOptions={maxSelectNumber:9,MIME:['image/jpeg','image/png'],}三、PhotoSelectResult 结果处理
3.1 返回结构
interfacePhotoSelectResult{photoUris:string[]// 所选图片的 URI 列表}3.2 URI 的使用
result.photoUris[0]// 返回类似:content://media/external/images/media/12345// 或:/data/storage/el2/base/files/...// 这个 URI 可以直接用于:// 1. image.createImageSource(uri) — 解码图像// 2. Image(uri) — ArkUI 图片组件展示// 3. fs.openSync(uri) — 文件操作四、拍照/相册双模式切换
4.1 模式切换策略
CapturePage 支持两种来源——拍照和相册,通过source参数控制:
aboutToAppear(){// 从路由参数读取来源constparams=this.getUIContext().getRouter().getParams()asRecord<string,Object>if(params&&typeofparams['source']==='string'){this.source=params['source']asstring}if(this.source==='gallery'){this.pickFromAlbum()// → 相册模式}else{this.initCamera()// → 相机模式}}4.2 触发来源
// 跳转到 CapturePage — 拍照模式this.getUIContext().getRouter().pushUrl({url:'pages/CapturePage',params:{source:'camera'}})// 跳转到 CapturePage — 相册模式this.getUIContext().getRouter().pushUrl({url:'pages/CapturePage',params:{source:'gallery'}})4.3 UI 中的相册入口
在拍照页面底部,用户也可以点击相册按钮随时切换到相册选择:
// 拍照模式底栏中的相册按钮Text('🖼').fontSize(22).fontColor('rgba(255,255,255,0.8)').onClick(()=>this.pickFromAlbum())五、MediaLibraryKit 的其他能力
5.1 获取相册列表
asyncfunctiongetAlbums():Promise<photoAccessHelper.Album[]>{consthelper=photoAccessHelper.getPhotoAccessHelper(getContext(this))constalbums=awaithelper.getAlbums(photoAccessHelper.AlbumType.USER,photoAccessHelper.AlbumSubtype.USER_GENERIC)returnalbums}5.2 保存图片到相册
import{photoAccessHelper}from'@kit.MediaLibraryKit'asyncfunctionsaveToAlbum(uri:string):Promise<void>{consthelper=photoAccessHelper.getPhotoAccessHelper(getContext(this))// 将文件添加到系统相册awaithelper.addAsset(uri)}六、PhotoViewPicker vs 自定义相册
| 对比 | PhotoViewPicker(项目使用) | 自定义相册 |
|---|---|---|
| 实现复杂度 | 低(3 行代码) | 高(权限 + 查询 + UI) |
| UI 一致性 | 系统原生相册 | 自定义 UI |
| 多选支持 | ✅ 内置 | 需要自行实现 |
| 权限要求 | 自动处理 | 需要 MANAGE_MEDIA |
| 推荐场景 | 快速集成 | 需定制 UI 的场景 |
七、路径与 URI 的处理
7.1 路径统一
拍照和相册选择的图片路径格式不同,但后续处理方式一致:
// 拍照:文件系统路径this.previewPath=`/data/storage/el2/base/cache/capture_${Date.now()}.jpg`// 相册:content:// URIthis.previewPath=result.photoUris[0]// content://media/...// 两种路径传入 AnalyzingPage 后的使用constimagePath=AppStorage.get<string>('capture_image_path')??''awaitHandwritingDao.create({image_path:imagePath,// 原样保存路径// ...})7.2 路径格式统一化
privatenormalizePath(uri:string):string{if(uri.startsWith('content://')){// content:// URI → 复制到应用缓存目录// 这需要读取文件内容并写入临时目录returnuri// 项目当前直接使用 URI}returnuri// 已经是文件路径}八、权限相关
8.1 相册读取权限
使用 PhotoViewPicker 不需要申请ohos.permission.READ_MEDIA权限——系统选择器自动处理了授权。但如果使用photoAccessHelper的其他 API(如遍历相册),则需要声明权限:
{ "requestPermissions": [ { "name": "ohos.permission.READ_MEDIA", "reason": "$string:media_permission_reason", "usedScene": { "abilities": ["EntryAbility"], "when": "inuse" } } ] }8.2 权限对比
| 场景 | 需要权限 | 权限声明 |
|---|---|---|
| PhotoViewPicker.select() | ❌ 不需要 | 系统选择器自带授权 |
| photoAccessHelper.getAlbums() | ✅ 需要 | ohos.permission.READ_MEDIA |
| photoAccessHelper.addAsset() | ✅ 需要 | ohos.permission.WRITE_MEDIA |
总结
本文从 CapturePage 的pickFromAlbum()方法出发,完整解析了鸿蒙 PhotoViewPicker 的使用:
- 基本使用:
new PhotoViewPicker()→select(options)→result.photoUris - 选项配置:
maxSelectNumber控制选择数量 - 双模式切换:通过
source参数在拍照/相册间切换 - URI 处理:拍照返回文件路径,相册返回 content:// URI
- 权限优势:PhotoViewPicker 不需要额外权限声明
下一篇文章将介绍图片压缩与本地存储——在保存图片时的质量和大小优化。
如果这篇文章对你有帮助,欢迎点赞👍、收藏⭐、关注🔔,你的支持是我持续创作的动力!
参考资源:
- PhotoViewPicker API
- [CapturPage 相册选择源码](file:///Users/fiona/Downloads/bijixinli/harmony-app/entry/src/main/ets/pages/CapturePage.ets)
- photoAccessHelper 开发指南
- @kit.MediaLibraryKit 模块
- media 权限声明
- HarmonyOS 开发文档
九、PhotoViewPicker 完整功能扩展
9.1 多选图片场景
asyncfunctionpickMultipleImages():Promise<string[]>{constphotoPicker=newphotoAccessHelper.PhotoViewPicker()constoptions:photoAccessHelper.PhotoSelectOptions={maxSelectNumber:9,// 最多选 9 张MIME:['image/jpeg','image/png','image/heif']// 仅图片格式}constresult=awaitphotoPicker.select(options)returnresult.photoUris??[]}9.2 DocumentViewPicker:选择文档
除了图片,鸿蒙还提供了 DocumentViewPicker 用于选择文档文件:
import{picker}from'@kit.CoreFileKit'asyncfunctionpickDocument():Promise<string>{constdocumentPicker=newpicker.DocumentViewPicker()constoptions:picker.DocumentSelectOptions={maxSelectNumber:1,fileSuffixFilters:['.pdf','.txt','.docx']// 只显示指定后缀的文件}constresult=awaitdocumentPicker.select(options)returnresult[0]??''}9.3 URI 转文件路径
相册 URI(content://)需要通过fs.open获取文件描述符才能读取:
importfsfrom'@ohos.file.fs'asyncfunctionreadImageFromUri(uri:string):Promise<ArrayBuffer>{// 通过 URI 打开文件constfile=fs.openSync(uri,fs.OpenMode.READ_ONLY)// 获取文件大小conststat=fs.statSync(file.fd)// 读取文件内容constbuffer=newArrayBuffer(stat.size)fs.readSync(file.fd,buffer)fs.closeSync(file)returnbuffer}9.4 相册访问能力对比
| 功能 | PhotoViewPicker | photoAccessHelper | DocumentViewPicker |
|---|---|---|---|
| 选择图片 | ✅ | ❌(需权限查询) | ❌ |
| 选择文档 | ❌ | ❌ | ✅ |
| 遍历相册 | ❌ | ✅(需 READ_MEDIA) | ❌ |
| 保存到相册 | ❌ | ✅(需 WRITE_MEDIA) | ❌ |
| 权限要求 | 无需额外权限 | 需要 READ_MEDIA | 无需额外权限 |
| 推荐场景 | 用户主动选择 | 程序化批量访问 | 用户选择文档 |
9.5 相册操作完整代码模板
// 完整的相册选择 + 图片处理模板@Componentstruct ImagePickerDemo{@StateselectedImageUri:string=''@StateisLoading:boolean=falseprivateasyncpickAndProcess():Promise<void>{this.isLoading=truetry{// ① 选择图片constpicker=newphotoAccessHelper.PhotoViewPicker()constresult=awaitpicker.select({maxSelectNumber:1})if(!result.photoUris||result.photoUris.length===0)returnconsturi=result.photoUris[0]// ② 解码 PixelMapconstsource=image.createImageSource(uri)constpixelMap=awaitsource.createPixelMap({desiredSize:{width:1024,height:1024}})// ③ 压缩保存到缓存constoutputPath=`${getContext(this).cacheDir}/selected_${Date.now()}.jpg`constpacker=image.createImagePacker()constbuffer=awaitpacker.packing(pixelMap,{format:'image/jpeg',quality:85})constfile=fs.openSync(outputPath,fs.OpenMode.CREATE|fs.OpenMode.WRITE_ONLY)fs.writeSync(file.fd,buffer)fs.closeSync(file)// ④ 释放内存pixelMap.release()this.selectedImageUri=outputPath}catch(err){hilog.error(0x0000,'ImagePicker','选图失败: %s',JSON.stringify(err))}finally{this.isLoading=false}}build(){Column(){if(this.selectedImageUri){Image(this.selectedImageUri).width(200).height(200).objectFit(ImageFit.Cover)}Button('从相册选择').onClick(()=>this.pickAndProcess())}}}如果这篇文章对你有帮助,欢迎点赞👍、收藏⭐、关注🔔,你的支持是我持续创作的动力!
