HarmonyOS APP实战-基于Image Kit的图像处理APP - 第2篇:图片选择与显示

📅 2026/7/13 15:18:52
HarmonyOS APP实战-基于Image Kit的图像处理APP - 第2篇:图片选择与显示
HarmonyOS APP实战-基于Image Kit的图像处理APP - 第2篇图片选择与预览1. 开篇在上一篇中我们完成了环境搭建与项目创建配置了DevEco Studio开发环境创建了名为ImageProcessor的HarmonyOS工程并在module.json5中预留了文件读写所需的权限声明。项目骨架已经搭好源代码中包含了基础的EntryAbility入口和空白的首页页面。从本篇开始我们将进入真正的功能开发。本篇要实现的是整个APP的入口操作——让用户从系统相册中选择图片并将选中的图片以缩略图网格的形式呈现在屏幕上。这不仅是视觉交互的第一步更是后续所有图片处理操作的数据来源基础。我们将封装三个核心模块PhotoPickerService选择图片服务、ImageSourceUtils图片解码工具和ImageGridPage图片网格页面它们将协同完成从选择到展示的完整闭环。2. 核心实现2.1 基础配置导入模块与权限声明在开始编码前我们需要确认项目已经添加了必要的依赖。在module.json5的requestPermissions中新增相册访问权限PhotoViewPicker在API 11无需单独权限但为了兼容旧设备和数据访问建议声明读取媒体文件权限。同时在代码中导入所需的系统模块。src/main/module.json5权限配置片段{module:{name:entry,type:entry,requestPermissions:[{name:ohos.permission.READ_MEDIA}]}}关键点说明尽管PhotoViewPicker自带用户授权弹窗但声明ohos.permission.READ_MEDIA可以确保后续需要直接读取文件时权限就绪。ohos.permission.READ_MEDIA属于user_grant类型需要动态弹窗申请。本APP仅在选择图片时触发我们会在选择前动态请求。导入依赖模块在需要使用的地方添加import{photoAccessHelper}fromkit.MediaLibraryKit;// 仅作参考实际使用PhotoViewPickerimport{photoViewPicker}fromkit.MediaLibraryKit;// PhotoViewPicker 所在模块import{image}fromkit.ImageKit;// Image Kit 核心模块import{fileIo}fromkit.CoreFileKit;// 文件操作import{common}fromkit.AbilityKit;实际使用的API路径基于官方文档PhotoViewPicker来自ohos.multimedia.pickerAPI10或kit.MediaLibraryKitAPI11。image.createImageSource来自ohos.multimedia.image或kit.ImageKit。fileIo.openSync来自ohos.file.fs。2.2 核心逻辑PhotoPickerService 与 ImageSourceUtils我们首先封装一个服务类负责调用系统选择器并返回选中文件的URI列表然后编写一个工具类将URI解码为像素图PixelMap缩略图。src/main/ets/services/PhotoPickerService.etsimport{photoViewPicker}fromkit.MediaLibraryKit;import{common}fromkit.AbilityKit;import{BusinessError}fromkit.BasicServicesKit;/** * 图片选择服务封装 PhotoViewPicker 调用 */exportclassPhotoPickerService{privatepicker:photoViewPicker.PhotoViewPicker;constructor(){this.pickernewphotoViewPicker.PhotoViewPicker();}/** * 打开系统相册选择图片返回选中文件的URI数组 * param context UIAbility上下文用于申请权限 * returns 返回选中的图片URI列表如果用户取消则返回空数组 */asyncpickPhotos(context:common.UIAbilityContext):Promisestring[]{try{// 构建选择选项限制最多选择50张只显示图片类型constoptions:photoViewPicker.PhotoSelectOptions{MIMEType:photoViewPicker.PhotoViewMIMETypes.IMAGE_TYPE,// 只选择图片maxSelectNumber:50,// 最大50张isOriginal:false// 不选择原始图提升速度};// 调用选择器返回结果constresult:photoViewPicker.PhotoSelectResultawaitthis.picker.select(options,context);// 取出选中的URI列表consturis:string[]result.photoUris;console.info(PhotoPickerService: selected uris count uris.length);returnuris;}catch(error){consterr:BusinessErrorerrorasBusinessError;console.error(PhotoPickerService: pick failed, code err.code, msg err.message);return[];}}}关键点说明photoViewPicker.PhotoViewPicker是系统提供的图片选择器API无需额外权限系统会弹出用户授权界面。PhotoSelectOptions中的MIMEType设置为PhotoViewMIMETypes.IMAGE_TYPE只显示图片文件。isOriginal: false表示返回的是压缩后的缩略图URI选择速度快适合预览场景。如需原始大图可设为true但后续解码时需注意内存。src/main/ets/utils/ImageSourceUtils.etsimport{image}fromkit.ImageKit;import{fileIo}fromkit.CoreFileKit;/** * 图片解码工具类提供URI转像素图缩略图功能 */exportclassImageSourceUtils{/** * 根据文件URI创建解码后的缩略图 PixelMap * param fileUri 文件URI例如从 PhotoPickerService 得到 * param width 期望的缩略图宽度px * param height 期望的缩略图高度px * returns 解码后的 PixelMap失败返回 undefined */staticasyncdecodeThumbnail(fileUri:string,width:number256,height:number256):Promiseimage.PixelMap|undefined{letfile:fileIo.File|undefined;try{// 打开文件获取文件描述符filefileIo.openSync(fileUri,fileIo.OpenMode.READ_ONLY);constfd:numberfile.fd;// 创建图片源对象constimageSource:image.ImageSourceimage.createImageSource(fd);// 设置解码参数输出缩略图尺寸constdecodingOptions:image.DecodingOptions{desiredSize:{width:width,height:height},// 指定期望的宽高editable:true// 允许后续编辑如裁剪};// 同步解码得到 PixelMapconstpixelMap:image.PixelMapimageSource.createPixelMapSync(decodingOptions);// 释放图片源资源imageSource.release();returnpixelMap;}catch(error){console.error(ImageSourceUtils decode failed: JSON.stringify(error));returnundefined;}finally{// 关闭文件描述符if(file){fileIo.closeSync(file);}}}}关键点说明使用fileIo.openSync将URI转换为文件描述符因为image.createImageSource接受文件描述符fd。fileIo.openSync的OpenMode.READ_ONLY表示只读打开。image.createImageSource返回ImageSource对象它是解码图片的入口。DecodingOptions中的desiredSize属性可以指定输出像素图的尺寸这相当于“缩略图采样”比解码再缩放更高效且节省内存。createPixelMapSync是同步接口不会阻塞主线程太久小图。也可以使用异步接口createPixelMap但示例采用同步更简洁。2.3 完整模块ImageGridPage 页面现在我们将选择服务与解码工具组合到页面中实现点击按钮选择图片并显示在Grid布局中。src/main/ets/pages/ImageGridPage.etsimport{PhotoPickerService}from../services/PhotoPickerService;import{ImageSourceUtils}from../utils/ImageSourceUtils;import{image}fromkit.ImageKit;import{common}fromkit.AbilityKit;Componentexportstruct ImageGridPage{// 声明图片列表状态每次选择后追加新图的PixelMapStateimageList:image.PixelMap[][];// 页面标题privatetitle:string图片预览;// 常量网格列数privatereadonlyGRID_COLUMNS:number3;build(){Column(){// 顶部操作栏Row(){Text(this.title).fontSize(24).fontWeight(FontWeight.Bold).padding({left:16});Blank();Button(选择图片).onClick(()this.pickAndAddImages()).margin({right:16})}.height(60).width(100%).backgroundColor(#F5F5F5)// 图片网格区域if(this.imageList.length0){// 没有图片时显示提示Column(){Text(点击上方按钮从相册选择图片).fontSize(16).fontColor(#999999)}.layoutWeight(1).justifyContent(FlexAlign.Center).alignItems(HorizontalAlign.Center).width(100%)}else{Grid(){ForEach(this.imageList,(item:image.PixelMap,index:number){GridItem(){Image(item).width(100%).aspectRatio(1)// 正方形显示.objectFit(ImageFit.Cover).borderRadius(8)}.margin(4)},(item:image.PixelMap,index:number)index.toString())}.columnsTemplate(1fr .repeat(this.GRID_COLUMNS)).rowsTemplate(1fr .repeat(this.GRID_COLUMNS)).columnsGap(4).rowsGap(4).padding(4).layoutWeight(1).width(100%)}}.width(100%).height(100%)}/** * 选择图片并添加到列表 */privateasyncpickAndAddImages(){// 获取UIAbility上下文在页面中通过getContext获取constcontext:common.UIAbilityContextgetContext(this)ascommon.UIAbilityContext;constservicenewPhotoPickerService();consturis:string[]awaitservice.pickPhotos(context);if(uris.length0){// 用户取消或选择失败return;}// 遍历URI解码缩略图并添加到列表for(consturiofuris){constthumb:image.PixelMap|undefinedawaitImageSourceUtils.decodeThumbnail(uri,256,256);if(thumb){// 追加到状态数组触发UI刷新this.imageList[...this.imageList,thumb];}}}}关键点说明网格使用Grid容器columnsTemplate设置为1fr 1fr 1fr3列用字符串重复生成。ForEach遍历imageList每个GridItem内使用Image(pixelMap)显示像素图。objectFit(ImageFit.Cover)确保图片填满正方形区域可能会被裁剪。每次选择后pickAndAddImages方法异步解码所有选定图片新生成的PixelMap追加到imageList中。为了简单这里使用...展开运算符创建新数组触发刷新。如果图片数量较多建议使用Observed配合LazyForEach实现懒加载。getContext(this)获取当前组件的上下文传递给PhotoPickerService。首页引用该页面修改EntryAbility.ets或Index.ets中的build()直接加载ImageGridPage组件例如EntryComponentstruct Index{build(){ImageGridPage()}}3. 运行验证完成上述代码后运行项目通过模拟器或真机。预期表现如下应用启动后显示一个空白页面顶部标题“图片预览”右侧有“选择图片”按钮。点击“选择图片”按钮系统弹出相册选择界面用户可以单选或多选图片。选择后返回APP选中的图片以3列网格的形式显示为小方块256x256像素缩略图。连续多次选择新图片会被追加到网格末尾之前的图片保留。如果出现图片无法显示请检查真机/模拟器是否已登录华为账号并授予相册权限测试图片是否损坏或格式不被支持建议使用JPEG、PNG格式控制台是否有文件打开或解码失败的日志4. 小结与预告本篇我们完成了APP的第一个核心功能模块通过PhotoViewPicker选择多张图片使用image.createImageSource配合DecodingOptions高效解码出缩略图并以Grid网格形式预览。我们封装的PhotoPickerService和ImageSourceUtils将成为后续所有编辑操作的基石——所有图片处理都将基于PixelMap进行。下一篇我们将深入图片解码环节学习如何获取图片的原始信息宽度、高度、方向、EXIF元数据等并在此基础上实现图片的基本信息展示面板。届时你的图片处理APP将拥有“看懂图片”的能力。