news 2026/7/13 15:18:51

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
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.json5requestPermissions中新增相册访问权限(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}from'@kit.MediaLibraryKit';// 仅作参考,实际使用PhotoViewPickerimport{photoViewPicker}from'@kit.MediaLibraryKit';// PhotoViewPicker 所在模块import{image}from'@kit.ImageKit';// Image Kit 核心模块import{fileIo}from'@kit.CoreFileKit';// 文件操作import{common}from'@kit.AbilityKit';

实际使用的API路径(基于官方文档):

  • PhotoViewPicker来自@ohos.multimedia.picker(API10+)或@kit.MediaLibraryKit(API11+)。
  • image.createImageSource来自@ohos.multimedia.image@kit.ImageKit
  • fileIo.openSync来自@ohos.file.fs

2.2 核心逻辑:PhotoPickerService 与 ImageSourceUtils

我们首先封装一个服务类,负责调用系统选择器并返回选中文件的URI列表;然后编写一个工具类,将URI解码为像素图(PixelMap)缩略图。

src/main/ets/services/PhotoPickerService.ets

import{photoViewPicker}from'@kit.MediaLibraryKit';import{common}from'@kit.AbilityKit';import{BusinessError}from'@kit.BasicServicesKit';/** * 图片选择服务,封装 PhotoViewPicker 调用 */exportclassPhotoPickerService{privatepicker:photoViewPicker.PhotoViewPicker;constructor(){this.picker=newphotoViewPicker.PhotoViewPicker();}/** * 打开系统相册选择图片,返回选中文件的URI数组 * @param context UIAbility上下文,用于申请权限 * @returns 返回选中的图片URI列表,如果用户取消则返回空数组 */asyncpickPhotos(context:common.UIAbilityContext):Promise<string[]>{try{// 构建选择选项:限制最多选择50张,只显示图片类型constoptions:photoViewPicker.PhotoSelectOptions={MIMEType:photoViewPicker.PhotoViewMIMETypes.IMAGE_TYPE,// 只选择图片maxSelectNumber:50,// 最大50张isOriginal:false// 不选择原始图,提升速度};// 调用选择器,返回结果constresult:photoViewPicker.PhotoSelectResult=awaitthis.picker.select(options,context);// 取出选中的URI列表consturis:string[]=result.photoUris;console.info('PhotoPickerService: selected uris count = '+uris.length);returnuris;}catch(error){consterr:BusinessError=errorasBusinessError;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.ets

import{image}from'@kit.ImageKit';import{fileIo}from'@kit.CoreFileKit';/** * 图片解码工具类,提供URI转像素图(缩略图)功能 */exportclassImageSourceUtils{/** * 根据文件URI创建解码后的缩略图 PixelMap * @param fileUri 文件URI,例如从 PhotoPickerService 得到 * @param width 期望的缩略图宽度(px) * @param height 期望的缩略图高度(px) * @returns 解码后的 PixelMap,失败返回 undefined */staticasyncdecodeThumbnail(fileUri:string,width:number=256,height:number=256):Promise<image.PixelMap|undefined>{letfile:fileIo.File|undefined;try{// 打开文件获取文件描述符file=fileIo.openSync(fileUri,fileIo.OpenMode.READ_ONLY);constfd:number=file.fd;// 创建图片源对象constimageSource:image.ImageSource=image.createImageSource(fd);// 设置解码参数:输出缩略图尺寸constdecodingOptions:image.DecodingOptions={desiredSize:{width:width,height:height},// 指定期望的宽高editable:true// 允许后续编辑(如裁剪)};// 同步解码得到 PixelMapconstpixelMap:image.PixelMap=imageSource.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.openSyncOpenMode.READ_ONLY表示只读打开。
  • image.createImageSource返回ImageSource对象,它是解码图片的入口。
  • DecodingOptions中的desiredSize属性可以指定输出像素图的尺寸,这相当于“缩略图采样”,比解码再缩放更高效,且节省内存。
  • createPixelMapSync是同步接口,不会阻塞主线程太久(小图)。也可以使用异步接口createPixelMap,但示例采用同步更简洁。

2.3 完整模块:ImageGridPage 页面

现在我们将选择服务与解码工具组合到页面中,实现点击按钮选择图片,并显示在Grid布局中。

src/main/ets/pages/ImageGridPage.ets

import{PhotoPickerService}from'../services/PhotoPickerService';import{ImageSourceUtils}from'../utils/ImageSourceUtils';import{image}from'@kit.ImageKit';import{common}from'@kit.AbilityKit';@Componentexportstruct ImageGridPage{// 声明图片列表状态,每次选择后追加新图的PixelMap@StateimageList:image.PixelMap[]=[];// 页面标题privatetitle:string='图片预览';// 常量:网格列数privatereadonlyGRID_COLUMNS:number=3;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.length===0){// 没有图片时显示提示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.UIAbilityContext=getContext(this)ascommon.UIAbilityContext;constservice=newPhotoPickerService();consturis:string[]=awaitservice.pickPhotos(context);if(uris.length===0){// 用户取消或选择失败return;}// 遍历URI,解码缩略图并添加到列表for(consturiofuris){constthumb:image.PixelMap|undefined=awaitImageSourceUtils.decodeThumbnail(uri,256,256);if(thumb){// 追加到状态数组,触发UI刷新this.imageList=[...this.imageList,thumb];}}}}

关键点说明:

  • 网格使用Grid容器,columnsTemplate设置为'1fr 1fr 1fr'(3列),用字符串重复生成。
  • ForEach遍历imageList,每个GridItem内使用Image(pixelMap)显示像素图。objectFit(ImageFit.Cover)确保图片填满正方形区域,可能会被裁剪。
  • 每次选择后,pickAndAddImages方法异步解码所有选定图片,新生成的PixelMap追加到imageList中。为了简单,这里使用...展开运算符创建新数组触发刷新。如果图片数量较多,建议使用@Observed配合LazyForEach实现懒加载。
  • getContext(this)获取当前组件的上下文,传递给PhotoPickerService

首页引用该页面:
修改EntryAbility.etsIndex.ets中的build()直接加载ImageGridPage组件,例如:

@Entry@Componentstruct Index{build(){ImageGridPage()}}

3. 运行验证

完成上述代码后,运行项目(通过模拟器或真机)。预期表现如下:

  1. 应用启动后,显示一个空白页面,顶部标题“图片预览”,右侧有“选择图片”按钮。
  2. 点击“选择图片”按钮,系统弹出相册选择界面,用户可以单选或多选图片。
  3. 选择后返回APP,选中的图片以3列网格的形式显示为小方块(256x256像素缩略图)。
  4. 连续多次选择,新图片会被追加到网格末尾,之前的图片保留。

如果出现图片无法显示,请检查:

  • 真机/模拟器是否已登录华为账号并授予相册权限?
  • 测试图片是否损坏或格式不被支持(建议使用JPEG、PNG格式)?
  • 控制台是否有文件打开或解码失败的日志?

4. 小结与预告

本篇我们完成了APP的第一个核心功能模块:通过PhotoViewPicker选择多张图片,使用image.createImageSource配合DecodingOptions高效解码出缩略图,并以Grid网格形式预览。我们封装的PhotoPickerServiceImageSourceUtils将成为后续所有编辑操作的基石——所有图片处理都将基于PixelMap进行。

下一篇,我们将深入图片解码环节,学习如何获取图片的原始信息(宽度、高度、方向、EXIF元数据等),并在此基础上实现图片的基本信息展示面板。届时,你的图片处理APP将拥有“看懂图片”的能力。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/13 15:18:21

Unity Tilemap 2D游戏地图开发全流程:从素材切割到性能优化

1. 项目概述&#xff1a;为什么Tilemap是2D游戏开发的“基建狂魔”&#xff1f;如果你刚接触Unity2D&#xff0c;想做一款横版过关、俯视角RPG或者平台跳跃游戏&#xff0c;第一个拦路虎往往不是写代码&#xff0c;而是“地图怎么画”。一张张手动摆放Sprite&#xff1f;效率低…

作者头像 李华
网站建设 2026/7/13 15:16:10

从零构建高转化自适应APP下载页:源码解析与实战优化

1. 为什么需要高转化自适应APP下载页在移动互联网时代&#xff0c;APP下载页是用户首次接触产品的关键入口。一个设计精良的下载页能显著提升转化率&#xff0c;而一个糟糕的页面则可能导致潜在用户流失。根据行业数据&#xff0c;优化后的下载页可以将转化率提升30%以上。自适…

作者头像 李华
网站建设 2026/7/13 15:14:44

Java程序设计(第3版)第四章——JDK高版本接口语法补充

JDK高版本接口语法补充 1.JDK8.0开始,接口中可以定义默认方法和静态方法 (1)默认方法default 返回值类型方法名&#xff08;形参列表&#xff09;{//方法实现}注意:带有方法实现的非静态方法必须加default (2)静态方法public static返回值类型方法名&#xff08;形参列表){//…

作者头像 李华
网站建设 2026/7/13 15:14:37

3大优势全面解析:开源工具如何智能破解Cursor Pro限制

3大优势全面解析&#xff1a;开源工具如何智能破解Cursor Pro限制 【免费下载链接】cursor-free-vip [Support 0.45]&#xff08;Multi Language 多语言&#xff09;自动注册 Cursor Ai &#xff0c;自动重置机器ID &#xff0c; 免费升级使用Pro 功能: Youve reached your tri…

作者头像 李华
网站建设 2026/7/13 15:14:20

在线评测与离线评测的架构差异:线上环境永远比测试复杂

在线评测与离线评测的架构差异&#xff1a;线上环境永远比测试复杂 一、离线评测 90 分&#xff0c;上线后用户评价只有 50 分 这是大模型落地的典型场景&#xff1a;在离线评测集上&#xff0c;模型的各项指标都达标。准确率 92%&#xff0c;用户满意度预测 88%&#xff0c;安…

作者头像 李华
网站建设 2026/7/13 15:14:17

终极免费方案:如何用Wand-Enhancer解锁游戏修改器完整功能

终极免费方案&#xff1a;如何用Wand-Enhancer解锁游戏修改器完整功能 【免费下载链接】Wand-Enhancer Advanced UX and interoperability extension for Wand (WeMod) app 项目地址: https://gitcode.com/GitHub_Trending/we/Wand-Enhancer 还在为Wand&#xff08;原We…

作者头像 李华