HarmonyOS PhotoViewPicker(图片选择器)使用指南
效果
一、概述
在HarmonyOS应用开发中,经常需要从用户相册中选择图片或视频。PhotoViewPicker是photoAccessHelper模块提供的系统级图片选择器,具有以下优势:
- 无需申请权限:PhotoViewPicker采用安全控件模式,不需要声明
READ_IMAGEVIDEO权限。 - 系统级UI:调用后弹出系统相册选择界面,用户体验统一且流畅。
- 灵活配置:支持选择图片、视频或混合类型,可设置最大选择数量。
- 安全隔离:返回的URI仅对当前应用有效,不会暴露相册的其他内容。
官方文档参考:PhotoViewPicker - HarmonyOS开发者文档
二、模块导入
import{photoAccessHelper}from'@kit.MediaLibraryKit';三、API详解
3.1 PhotoViewPicker类
classPhotoViewPicker{select(photoSelectOptions:PhotoSelectOptions):Promise<PhotoSelectResult>}PhotoSelectOptions 配置项:
| 属性 | 类型 | 说明 |
|---|---|---|
MIMEType | PhotoViewMIMETypes | 媒体文件类型过滤 |
maxSelectNumber | number | 最大可选择数量 |
photoPickerInfos | PhotoPickerInfo[] | 预选中的资源信息 |
preselectedMode | PreselectedMode | 预选模式 |
PhotoViewMIMETypes 枚举值:
| 枚举值 | 说明 |
|---|---|
PhotoViewMIMETypes.IMAGE_TYPE | 仅显示图片 |
PhotoViewMIMETypes.VIDEO_TYPE | 仅显示视频 |
PhotoViewMIMETypes.IMAGE_VIDEO_TYPE | 显示图片和视频 |
PhotoSelectResult 返回值:
| 属性 | 类型 | 说明 |
|---|---|---|
photoUris | string[] | 选中资源的URI数组 |
isMaxNumberReached | boolean | 是否达到最大选择数量 |
3.2 创建实例
letpicker=newphotoAccessHelper.PhotoViewPicker();四、基础使用示例
4.1 选择单张图片
import{photoAccessHelper}from'@kit.MediaLibraryKit';import{image}from'@kit.ImageKit';import{fileIo}from'@kit.CoreFileKit';@Entry@Componentstruct PickSingleImageDemo{@StateselectedImage:image.PixelMap|undefined=undefined;asyncpickImage():Promise<void>{try{// 1. 创建选择器实例letpicker=newphotoAccessHelper.PhotoViewPicker();// 2. 配置选择选项letoptions=newphotoAccessHelper.PhotoSelectOptions();options.MIMEType=photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE;options.maxSelectNumber=1;// 3. 调起系统选择器letresult=awaitpicker.select(options);// 4. 获取选中的图片URIif(result.photoUris.length>0){leturi=result.photoUris[0];// 5. 读取图片数据并创建PixelMapletfile=fileIo.openSync(uri);letstat=fileIo.statSync(file.fd);letbuffer=newArrayBuffer(stat.size);fileIo.readSync(file.fd,buffer);fileIo.closeSync(file.fd);letimageSource=image.createImageSource(buffer);this.selectedImage=awaitimageSource.createPixelMap();}}catch(err){console.error('选择图片失败: '+(errasError).message);}}build(){Column({space:20}){if(this.selectedImage){Image(this.selectedImage).width(280).height(280).objectFit(ImageFit.Cover).borderRadius(12)}else{Column().width(280).height(280).borderRadius(12).border({width:2,color:'#BDBDBD',style:BorderStyle.Dashed}).justifyContent(FlexAlign.Center).children([Text('+').fontSize(48).fontColor('#BDBDBD')])}Button('从相册选择').width(200).height(44).fontSize(16).fontColor(Color.White).backgroundColor('#0A59F7').borderRadius(22).onClick(()=>this.pickImage())}.width('100%').height('100%').justifyContent(FlexAlign.Center)}}4.2 选择多张图片
@Entry@Componentstruct PickMultipleImagesDemo{@StateselectedImages:image.PixelMap[]=[];asyncpickMultipleImages():Promise<void>{try{letpicker=newphotoAccessHelper.PhotoViewPicker();letoptions=newphotoAccessHelper.PhotoSelectOptions();options.MIMEType=photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE;options.maxSelectNumber=9;// 最多选择9张letresult=awaitpicker.select(options);// 遍历所有选中的图片letpixelMaps:image.PixelMap[]=[];for(leturiofresult.photoUris){letfile=fileIo.openSync(uri);letstat=fileIo.statSync(file.fd);letbuffer=newArrayBuffer(stat.size);fileIo.readSync(file.fd,buffer);fileIo.closeSync(file.fd);letimageSource=image.createImageSource(buffer);letpixelMap=awaitimageSource.createPixelMap();pixelMaps.push(pixelMap);}this.selectedImages=pixelMaps;}catch(err){console.error('选择图片失败: '+(errasError).message);}}build(){Column({space:16}){// 网格展示选中的图片Grid(){ForEach(this.selectedImages,(item:image.PixelMap,index:number)=>{GridItem(){Image(item).width('100%').height('100%').objectFit(ImageFit.Cover).borderRadius(8)}},(item:image.PixelMap,index:number)=>index.toString())}.columnsTemplate('1fr 1fr 1fr').rowsGap(8).columnsGap(8).width('90%').height(320)Button(`选择图片 (${this.selectedImages.length}/9)`).onClick(()=>this.pickMultipleImages())}.width('100%').height('100%').padding({top:40})}}五、进阶用法
5.1 读取图片为PixelMap的工具函数
在实际项目中,建议将图片读取逻辑封装为工具函数:
import{image}from'@kit.ImageKit';import{fileIo}from'@kit.CoreFileKit';import{photoAccessHelper}from'@kit.MediaLibraryKit';/** * 通过URI读取图片并创建PixelMap * @param uri 图片文件URI * @returns PixelMap对象 */exportasyncfunctioncreatePixelMapFromUri(uri:string):Promise<image.PixelMap>{letfile=fileIo.openSync(uri);letstat=fileIo.statSync(file.fd);letbuffer=newArrayBuffer(stat.size);fileIo.readSync(file.fd,buffer);fileIo.closeSync(file.fd);letimageSource=image.createImageSource(buffer);letpixelMap=awaitimageSource.createPixelMap({editable:true// 设置为可编辑,便于后续处理});returnpixelMap;}/** * 使用PhotoViewPicker从相册选择图片 * @param maxCount 最大选择数量 * @returns 选中的PixelMap数组 */exportasyncfunctionpickImagesFromGallery(maxCount:number=1):Promise<image.PixelMap[]>{letpicker=newphotoAccessHelper.PhotoViewPicker();letoptions=newphotoAccessHelper.PhotoSelectOptions();options.MIMEType=photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE;options.maxSelectNumber=maxCount;letresult=awaitpicker.select(options);letpixelMaps:image.PixelMap[]=[];for(leturiofresult.photoUris){letpixelMap=awaitcreatePixelMapFromUri(uri);pixelMaps.push(pixelMap);}returnpixelMaps;}5.2 结合Image组件直接展示
如果不需要PixelMap,可以直接使用URI展示图片(性能更好):
@Entry@Componentstruct DirectURIDisplayDemo{@StateimageUri:string='';build(){Column({space:20}){if(this.imageUri){// 直接使用URI展示图片,无需创建PixelMapImage(this.imageUri).width(280).height(280).objectFit(ImageFit.Cover).borderRadius(12)}Button('选择图片').onClick(async()=>{letpicker=newphotoAccessHelper.PhotoViewPicker();letoptions=newphotoAccessHelper.PhotoSelectOptions();options.MIMEType=photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE;options.maxSelectNumber=1;letresult=awaitpicker.select(options);if(result.photoUris.length>0){this.imageUri=result.photoUris[0];}})}.width('100%').height('100%').justifyContent(FlexAlign.Center)}}5.3 在图片编辑器中替换背景图
@Entry@Componentstruct ReplaceBackgroundDemo{@StatebgImage:image.PixelMap|undefined=undefined;asyncreplaceBackground():Promise<void>{try{letpicker=newphotoAccessHelper.PhotoViewPicker();letoptions=newphotoAccessHelper.PhotoSelectOptions();options.MIMEType=photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE;options.maxSelectNumber=1;letresult=awaitpicker.select(options);if(result.photoUris.length>0){letfile=fileIo.openSync(result.photoUris[0]);letstat=fileIo.statSync(file.fd);letbuffer=newArrayBuffer(stat.size);fileIo.readSync(file.fd,buffer);fileIo.closeSync(file.fd);letimageSource=image.createImageSource(buffer);// 释放旧的PixelMapif(this.bgImage){this.bgImage.release();}this.bgImage=awaitimageSource.createPixelMap({editable:true});}}catch(err){console.error('替换背景失败: '+(errasError).message);}}build(){Stack(){// 背景图Image(this.bgImage??$r('app.media.background')).width(300).height(300).objectFit(ImageFit.Cover).borderRadius(12)// 替换按钮Button('更换背景').position({x:'35%',y:'85%'}).onClick(()=>this.replaceBackground())}.width('100%').height('100%').justifyContent(FlexAlign.Center)}}六、注意事项
- 无需权限声明:PhotoViewPicker不需要在
module.json5中声明READ_IMAGEVIDEO权限。 - URI有效期:返回的URI仅对当前应用有效,且有一定的生命周期,建议尽快读取。
- 内存管理:使用完毕的PixelMap应调用
release()方法释放,避免内存泄漏。 - 大图片处理:对于大尺寸图片,建议通过
ImageSource的decodingOptions设置采样率。 - 用户取消处理:用户可能在选择器中点击取消,此时
photoUris为空数组,需要判断处理。 - 数量限制:
maxSelectNumber最大值受系统限制,建议不超过200。
七、PhotoViewPicker vs PhotoAccessPicker
HarmonyOS中存在两种图片选择方式:
| 特性 | PhotoViewPicker | fileIo + photoAccessHelper |
|---|---|---|
| 权限声明 | 不需要 | 需要READ_IMAGEVIDEO |
| UI体验 | 系统级选择器 | 需自行实现 |
| 安全等级 | 高(临时授权) | 需用户授权 |
| 适用场景 | 普通图片选择 | 深度相册管理 |
推荐:对于简单的图片选择需求,优先使用PhotoViewPicker。
八、完整工具类封装
以下是一个完整的图片选择工具类,可直接在项目中使用:
import{image}from'@kit.ImageKit';import{fileIo}from'@kit.CoreFileKit';import{photoAccessHelper}from'@kit.MediaLibraryKit';exportclassPhotoPickerUtil{/** * 从相册选择单张图片 */staticasyncpickSingleImage():Promise<image.PixelMap|undefined>{letpicker=newphotoAccessHelper.PhotoViewPicker();letoptions=newphotoAccessHelper.PhotoSelectOptions();options.MIMEType=photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE;options.maxSelectNumber=1;letresult=awaitpicker.select(options);if(result.photoUris.length===0){returnundefined;}returnPhotoPickerUtil.uriToPixelMap(result.photoUris[0]);}/** * 从相册选择多张图片 */staticasyncpickMultipleImages(maxCount:number):Promise<image.PixelMap[]>{letpicker=newphotoAccessHelper.PhotoViewPicker();letoptions=newphotoAccessHelper.PhotoSelectOptions();options.MIMEType=photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE;options.maxSelectNumber=maxCount;letresult=awaitpicker.select(options);letpixelMaps:image.PixelMap[]=[];for(leturiofresult.photoUris){letpm=awaitPhotoPickerUtil.uriToPixelMap(uri);pixelMaps.push(pm);}returnpixelMaps;}/** * 将URI转换为PixelMap */staticasyncuriToPixelMap(uri:string):Promise<image.PixelMap>{letfile=fileIo.openSync(uri);letstat=fileIo.statSync(file.fd);letbuffer=newArrayBuffer(stat.size);fileIo.readSync(file.fd,buffer);fileIo.closeSync(file.fd);letimageSource=image.createImageSource(buffer);returnawaitimageSource.createPixelMap({editable:true});}/** * 将URI转换为ArrayBuffer */staticuriToBuffer(uri:string):ArrayBuffer{letfile=fileIo.openSync(uri);letstat=fileIo.statSync(file.fd);letbuffer=newArrayBuffer(stat.size);fileIo.readSync(file.fd,buffer);fileIo.closeSync(file.fd);returnbuffer;}}九、总结
PhotoViewPicker是HarmonyOS中从相册选择图片的最佳方案,核心要点:
- 无需声明权限,通过系统级选择器实现安全选图。
- 通过
PhotoSelectOptions配置选择类型和数量限制。 - 返回的
photoUris数组包含选中图片的URI,可进一步转为PixelMap。 - 注意及时释放PixelMap资源,防止内存泄漏。
- 建议封装工具类,统一图片选择和处理逻辑。
在图片编辑类应用中,PhotoViewPicker通常用于"选择背景图"功能,与Stack贴纸叠加、componentSnapshot截图、SaveButton保存形成完整的工作流。