鸿蒙原生开发手记:徒步迹 - 相册选择与图片裁剪

鸿蒙原生开发手记:徒步迹 - 相册选择与图片裁剪 鸿蒙原生开发手记徒步迹 - 相册选择与图片裁剪从相册选择图片并进行裁剪处理前言用户在设置头像、上传路线照片时需要从相册选取图片。HarmonyOS 提供了photoAccessHelper用于读取相册结合image模块进行图片裁剪处理。本文实现图片选择和裁剪功能。一、相册选择器import { photoAccessHelper } from kit.MediaLibraryKit; import { image } from kit.ImageKit; import { common } from kit.AbilityKit; import { BusinessError } from kit.BasicServicesKit; interface PhotoSelectOptions { maxCount: number; // 最大选择数量 mimeTypes: string[]; // 文件类型过滤 cropRatio?: number; // 裁剪比例如 1:1 cropWidth?: number; // 裁剪目标宽度 cropHeight?: number; // 裁剪目标高度 } interface PhotoResult { uri: string; pixelMap: image.PixelMap; width: number; height: number; mimeType: string; } class PhotoPicker { private context: common.UIAbilityContext; constructor(context: common.UIAbilityContext) { this.context context; } // 打开相册选择图片 async selectPhotos(options: PhotoSelectOptions): PromisePhotoResult[] { try { const helper photoAccessHelper.getPhotoAccessHelper(this.context); // 配置选择选项 const selectOptions new photoAccessHelper.PhotoSelectOptions(); selectOptions.maxSelectNumber options.maxCount; selectOptions.mimeType photoAccessHelper.PhotoViewMimeTypes.IMAGE_TYPE; // 打开相册选择界面 const results await helper.select(selectOptions); const photoResults: PhotoResult[] []; for (const photo of results.photoUris) { // 读取图片 const uri photo.uri; const fd fileIo.openSync(uri, fileIo.OpenMode.READ_ONLY); // 创建 PixelMap const imageSource image.createImageSource(fd.fd); const pixelMap await imageSource.createPixelMap({ desiredSize: { width: options.cropWidth || 1920, height: options.cropHeight || 1920, }, }); photoResults.push({ uri, pixelMap, width: pixelMap.getPixelMapInfoSync().imageSize.width, height: pixelMap.getPixelMapInfoSync().imageSize.height, mimeType: image/jpeg, }); imageSource.release(); fileIo.closeSync(fd); } return photoResults; } catch (e) { console.error(选择照片失败, (e as BusinessError).message); return []; } } // 单张图片选择快捷方法 async selectSingle(cropRatio?: number): PromisePhotoResult | null { const results await this.selectPhotos({ maxCount: 1, mimeTypes: [image/*], cropRatio, }); return results[0] || null; } }二、图片裁剪工具class ImageCropper { // 按比例裁剪图片 static async cropToRatio( pixelMap: image.PixelMap, ratioWidth: number, ratioHeight: number ): Promiseimage.PixelMap { const info pixelMap.getPixelMapInfoSync(); const srcWidth info.imageSize.width; const srcHeight info.imageSize.height; // 计算裁剪区域 const targetRatio ratioWidth / ratioHeight; const srcRatio srcWidth / srcHeight; let cropX: number, cropY: number, cropW: number, cropH: number; if (srcRatio targetRatio) { // 图片较宽裁剪左右两侧 cropH srcHeight; cropW srcHeight * targetRatio; cropX (srcWidth - cropW) / 2; cropY 0; } else { // 图片较高裁剪上下两侧 cropW srcWidth; cropH srcWidth / targetRatio; cropX 0; cropY (srcHeight - cropH) / 2; } // 执行裁剪 const cropRect: image.Region { x: Math.round(cropX), y: Math.round(cropY), width: Math.round(cropW), height: Math.round(cropH), }; const croppedPixelMap await pixelMap.crop(cropRect); return croppedPixelMap; } // 缩放到指定尺寸 static async resize( pixelMap: image.PixelMap, targetWidth: number, targetHeight: number ): Promiseimage.PixelMap { const scaledPixelMap await pixelMap.scale( targetWidth / pixelMap.getPixelMapInfoSync().imageSize.width, targetHeight / pixelMap.getPixelMapInfoSync().imageSize.height ); return scaledPixelMap; } // 生成圆形头像 static async circleAvatar( pixelMap: image.PixelMap, size: number ): Promiseimage.PixelMap { // 先裁剪为正方形 const squared await this.cropToRatio(pixelMap, 1, 1); // 缩放到目标尺寸 const resized await this.resize(squared, size, size); // 使用圆形遮罩需要在 Canvas 中处理 return resized; } // 图片压缩 static async compress( pixelMap: image.PixelMap, quality: number 80 ): PromiseArrayBuffer { const packer image.createImagePacker(); const arrayBuffer await packer.packing(pixelMap, { format: image/jpeg, quality: quality, }); packer.release(); return arrayBuffer; } // 获取图片尺寸 static getSize(pixelMap: image.PixelMap): { width: number; height: number } { const info pixelMap.getPixelMapInfoSync(); return { width: info.imageSize.width, height: info.imageSize.height, }; } }三、图片选择与裁剪页面Entry Component struct PhotoCropPage { State selectedImage: image.PixelMap | null null; State croppedImage: image.PixelMap | null null; State isCropping: boolean false; private photoPicker: PhotoPicker; private aspectRatio: number 1; // 1:1 正方形 build() { Column() { // 顶部导航 Row() { Text(选择图片).fontSize(18).fontWeight(FontWeight.Bold); } .width(100%).padding(16); if (this.selectedImage) { // 图片预览区域 Image(this.selectedImage) .width(100%) .layoutWeight(1) .objectFit(ImageFit.Contain) .backgroundColor(#1A1A1A); // 裁剪比例选择 Row() { this.RatioButton(1:1, 1); this.RatioButton(3:4, 3 / 4); this.RatioButton(4:3, 4 / 3); this.RatioButton(16:9, 16 / 9); } .width(100%) .padding(16) .justifyContent(FlexAlign.SpaceEvenly); // 操作按钮 Row() { Button(重新选择) .backgroundColor(#E0E0E0) .fontColor(#333) .borderRadius(20) .layoutWeight(1) .height(44) .margin({ right: 8 }) .onClick(() this.selectImage()); Button(确认裁剪) .backgroundColor(#4CAF50) .fontColor(Color.White) .borderRadius(20) .layoutWeight(1) .height(44) .margin({ left: 8 }) .onClick(() this.cropImage()); } .width(100%) .padding(16); } else { // 空状态 Column() { Image($r(app.media.icon_gallery)) .width(80).height(80); Text(从相册选择图片) .fontSize(16).fontColor(#666) .margin({ top: 16 }); Button(选择图片) .backgroundColor(#4CAF50) .fontColor(Color.White) .borderRadius(20) .margin({ top: 20 }) .onClick(() this.selectImage()); } .width(100%) .layoutWeight(1) .justifyContent(FlexAlign.Center) .alignItems(HorizontalAlign.Center); } } .width(100%).height(100%); } Builder RatioButton(label: string, ratio: number) { Text(label) .fontSize(13) .fontColor(this.aspectRatio ratio ? #4CAF50 : #666) .backgroundColor(this.aspectRatio ratio ? #E8F5E9 : #F5F5F5) .borderRadius(16) .padding({ left: 20, right: 20, top: 6, bottom: 6 }) .onClick(() { this.aspectRatio ratio; }); } async selectImage(): Promisevoid { const photo await this.photoPicker.selectSingle(); if (photo) { this.selectedImage photo.pixelMap; this.croppedImage null; } } async cropImage(): Promisevoid { if (!this.selectedImage) return; this.isCropping true; try { this.croppedImage await ImageCropper.cropToRatio( this.selectedImage, this.aspectRatio, 1 ); console.log(裁剪完成); } catch (e) { console.error(裁剪失败, e); } finally { this.isCropping false; } } }四、图片上传// 压缩并上传图片 async function uploadAvatar(pixelMap: image.PixelMap): Promisestring { // 压缩到 200x200 以内 const resized await ImageCropper.resize(pixelMap, 200, 200); const compressed await ImageCropper.compress(resized, 80); // 上传 const response await httpClient.post(/api/users/avatar, { image: compressed, }); return response.data.data.url; }五、总结相册选择结合图片裁剪功能让用户可以灵活选取和调整图片。使用 ImageCropper 工具类可以方便地实现比例裁剪、缩放和压缩满足头像上传、路线图片等多种场景需求。下一篇文章将读取运动健康数据。下一篇预告鸿蒙原生开发手记徒步迹 - 运动健康数据读取实现步骤详解步骤一环境准备确保已安装 DevEco Studio 最新版本并完成 HarmonyOS SDK 配置。# 验证开发环境 deveco --version ohpm --version步骤二核心代码实现按以下顺序实现功能模块创建基础页面结构定义 State 状态变量实现 build() 方法构建 UI 布局添加用户交互事件处理逻辑接入对应的 Kit 能力如 Location Kit、Camera Kit 等进行功能测试与性能优化步骤三测试验证测试要点单元测试使用 Hypium 框架编写测试用例UI 测试通过 uitest 自动化测试工具验证性能测试借助 Profiler 工具分析性能瓶颈兼容性测试在不同分辨率设备上验证// 测试示例代码 describe(HomePageTest, () { it(should render correctly, 0, () { // 测试逻辑 }); });模块化架构实践架构分层设计徒步迹应用采用分层架构设计将业务逻辑、UI 表现、数据访问清晰分离。组件化开发规范自定义组件开发遵循单一职责、高内聚低耦合、可复用性三大原则。测试与质量保证单元测试策略使用Hypium测试框架编写单元测试覆盖核心业务逻辑。UI 自动化测试通过uitest工具实现 UI 自动化测试包括页面跳转、交互响应、状态变更等场景。性能监控与优化关键性能指标指标类别具体指标优化目标启动性能冷启动时间 2 秒渲染性能滑动帧率≥ 60 FPS内存占用峰值内存 200 MB网络性能请求响应 500 ms表 5HarmonyOS 应用关键性能指标持续性能优化性能优化是持续迭代的过程建议通过Profiler工具定期分析识别瓶颈。扩展章节3.1 HarmonyOS 应用架构概览HarmonyOS 应用由Ability、UIAbility、ServiceExtensionAbility等核心组件构成。Stage 模型提供了更加现代化的应用开发范式支持多 Ability 组合、跨设备迁移、原子化服务等高级特性。3.2 ArkUI 声明式 UI 设计原则ArkUI 采用声明式 UI开发范式开发者只需描述界面应该是什么样子框架会自动处理状态变化与界面更新。核心原则包括单一数据源状态由 State 装饰器管理避免多源数据冲突单向数据流数据从父组件流向子组件事件反向传递不可变状态使用 Link、Prop 实现父子组件状态同步3.3 性能优化关键策略优化策略实现方式性能提升LazyForEach懒加载列表项内存减少 60%虚拟列表仅渲染可见项滚动流畅度 40%状态管理精准 State 范围重渲染减少 50%异步加载TaskPool 并发主线程释放 70%表 6HarmonyOS 应用性能优化策略对照表3.4 开发调试常用技巧调试 HarmonyOS 应用时常用工具与技巧包括hilog日志输出工具支持分级INFO/WARN/ERROR/FATALProfiler性能分析工具监控 CPU、内存、渲染DumpLayoutUI 布局树导出定位布局问题HiTrace分布式调用链追踪3.5 应用发布与分发流程HarmonyOS 应用发布流程主要分为打包签名、上架审核、用户分发三个阶段。开发者需通过 AppGallery Connect 完成应用上架。总结本文围绕“徒步迹“应用的实际开发场景系统讲解了相关技术的实现要点。通过代码实战原理剖析的方式帮助开发者快速掌握 HarmonyOS NEXT 的核心开发能力。总结要点理解 HarmonyOS NEXT 应用架构与 Ability 生命周期掌握 ArkUI 声明式 UI 的状态管理与组件化开发熟悉常用 Kit 能力Map Kit、Location Kit、Camera Kit 等的接入方式学会性能优化、内存管理、并发编程等进阶技巧具备从 0 到 1 构建完整 HarmonyOS 应用工程的能力核心特性回顾声明式 UIArkUI 提供简洁高效的声明式开发范式状态管理State、Prop、Link、Provide、Consume 等装饰器跨组件通信通过 Provide/Consume 实现跨层级数据传递原生能力通过 Kit 接入系统能力地图、定位、相机等性能优化LazyForEach、虚拟列表、Skeleton 骨架屏等学习建议技术学习重在实践建议结合项目源码同步动手操作遇到问题多查阅HarmonyOS 官方文档。下一篇预告鸿蒙原生开发手记徒步迹 - 持续更新中如果这篇文章对你有帮助欢迎点赞、收藏⭐、关注你的支持是我持续创作的动力相关资源开源鸿蒙跨平台社区https://openharmonycrossplatform.csdn.netHarmonyOS 官方文档https://developer.huawei.com/consumer/cn//OpenHarmony 开源项目https://www.openharmony.cn/ArkUI 组件参考https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-ui-development徒步迹项目源码GitHub - hiking-trail-harmonyosDevEco Studio 下载https://developer.huawei.com/consumer/cn/deveco-studio/ArkTS 语言指南https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-overview系列文章导航CSDN 博客 - 鸿蒙原生开发手记