可可图片编辑 HarmonyOS(2) 选择图片和保存到图库 
前言 
HarmonyOS 上架应用 可可图片编辑 APP中,大量使用到了读取相册图片和保存图片到图库的功能。这篇文章主要围绕这两个核心功能继续讲解,目前HarmonyOS 应用开发中 主要推荐使用Picker读取媒体库的图片与视频。使用保存控件/授权弹窗保存媒体库的图片与视频。

picker 选择图片 

Picker 可以实现直接选择图库图片或者拍照的方式获取图片,需要注意的是 使用 Picker 读取图片时,返回的该图片的uri信息。Picker读取图片。
1. 导入模块 photoAccessHelper 模块 
import { photoAccessHelper } from '@kit.MediaLibraryKit';
import { BusinessError } from '@kit.BasicServicesKit';photoAccessHelper 来自于 MediaLibraryKit,MediaLibraryKit(媒体文件管理服务)提供了管理相册和媒体文件的能力,包括图片和视频,帮助应用快速构建图片和视频的展示与播放功能。
2. 设置选择图片的参数 
PhotoSelectOptions继承自BaseSelectOptions。
BaseSelectOptions提供的配置主要有:
| 名称 | 类型 | 必填 | 说明 | 
|---|---|---|---|
| MIMEType | PhotoViewMIMETypes | 否 | 可选择的媒体文件类型,若无此参数,则默认为图片和视频类型。元服务API: 从API version 11开始,该接口支持在元服务中使用。 | 
| maxSelectNumber | number | 否 | 选择媒体文件数量的最大值(最大可设置的值为500,若不设置则默认为50)。元服务API: 从API version 11开始,该接口支持在元服务中使用。 | 
| isPhotoTakingSupported | boolean | 否 | 是否支持拍照,true表示支持,false表示不支持,默认为true。元服务API: 从API version 11开始,该接口支持在元服务中使用。 | 
| isSearchSupported | boolean | 否 | 是否支持搜索,true表示支持,false表示不支持,默认为true。元服务API: 从API version 11开始,该接口支持在元服务中使用。 | 
| recommendationOptions | RecommendationOptions | 否 | 图片推荐相关配置参数。元服务API: 从API version 11开始,该接口支持在元服务中使用。 | 
| preselectedUris | Array<string> | 否 | 预选择图片的uri数据。元服务API: 从API version 11开始,该接口支持在元服务中使用。 | 
| isPreviewForSingleSelectionSupported | boolean | 否 | 单选模式下是否需要进大图预览,true表示需要,false表示不需要,默认为true。元服务API: 从API version 12开始,该接口支持在元服务中使用。 | 
| singleSelectionMode | SingleSelectionMode | 否 | 单选模式类型。默认为大图预览模式(SingleSelectionMode.BROWSER_MODE)。元服务API: 从API version 18开始,该接口支持在元服务中使用。 | 
| mimeTypeFilter | MimeTypeFilter | 否 | 文件类型的过滤配置,支持指定多个类型过滤。当配置mimeTypeFilter参数时,MIMEType的配置自动失效。配置该参数时,仅显示配置过滤类型对应的媒体文件,建议提示用户仅支持选择指定类型的图片/视频。元服务API: 从API version 19开始,该接口支持在元服务中使用。 | 
| fileSizeFilter | FileSizeFilter | 否 | 可选择媒体文件大小的过滤配置。配置该参数时,仅显示配置文件大小范围的媒体文件,建议提示用户仅支持选择指定大小的图片/视频。元服务API: 从API version 19开始,该接口支持在元服务中使用。 | 
| videoDurationFilter | VideoDurationFilter | 否 | 可选择媒体文件视频时长的过滤配置。配置该参数时,仅显示配置视频时长范围的媒体文件,建议提示用户仅支持选择指定时长视频。元服务API: 从API version 19开始,该接口支持在元服务中使用。 | 
| combinedMediaTypeFilter | Array<string> | 否 | 将过滤条件配置为字符串数组,支持多种类型组合。字符串格式如下:photoType | photoSubType1,photoSubType2, … | mimeType1,mimeType2, …。- 第1段指定1个photoType,固定为image(图片)或video(视频)。- 第2段指定1~N个photoSubType,多个photoSubType之间使用逗号隔开,之间为"或(OR)"的逻辑取并集;N目前支持最大为1;可选的PhotoSubType包括movingPhoto或"*"(忽略)。- 第3段指定1N个mimeType,多个mimeType之间使用逗号隔开,之间为"或(OR)"的逻辑取并集;N最大为10,格式类似于MimeTypeFilter。三段过滤的组合取交集处理。支持"非"的逻辑。对于需要排除的类型,进行加括号的方式进行标识;一个string最多可使用1个括号。当应用配置的过滤条件string不满足上述规格时,过滤结果为空。配置该参数时,仅取数组前三个参数进行处理,MIMEType、mimeTypeFilter参数自动失效。元服务API: 从API version 20开始支持在元服务中使用。 | 
| photoViewMimeTypeFileSizeFilters | Array<PhotoViewMimeTypeFileSizeFilter> | 否 | 指定媒体文件类型和文件大小进行过滤。配置该参数时,仅取数组前三个参数进行处理,MIMETypes和fileSizeFilter自动失效。元服务API: 从API version 20开始,该接口支持在元服务中使用。 | 
而 PhotoSelectOptions 提供的配置有:
| 名称 | 类型 | 必填 | 说明 | 
|---|---|---|---|
| isEditSupported | boolean | 否 | 是否支持编辑照片,true表示支持,false表示不支持,默认为true。元服务API: 从API version 11开始,该接口支持在元服务中使用。 | 
| isOriginalSupported | boolean | 否 | 是否显示选择原图按钮,true表示显示,false表示不显示,默认为true。元服务API: 从API version 12开始,该接口支持在元服务中使用。 | 
| subWindowName | string | 否 | 子窗口名称。元服务API: 从API version 12开始,该接口支持在元服务中使用。 | 
| completeButtonText | CompleteButtonText | 否 | 完成按钮显示的内容。完成按钮指在界面右下方,用户点击表示图片选择已完成的按钮。元服务API: 从API version 14开始,该接口支持在元服务中使用。 | 
以下示例代码中主要使用了 MIMEType 和 maxSelectNumber
const photoSelectOptions = new photoAccessHelper.PhotoSelectOptions();
photoSelectOptions.MIMEType = photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE;
photoSelectOptions.maxSelectNumber = 5;3. 创建图片选择器 
// 3 创建图片选择器
const photoViewPicker = new photoAccessHelper.PhotoViewPicker();4. 开始选择图片 
  // 4 开始选择图片
  photoViewPicker.select(photoSelectOptions).then((photoSelectResult: photoAccessHelper.PhotoSelectResult) => {
    uris = photoSelectResult.photoUris;
    console.info('photoViewPicker.select to file succeed and uris are:' + uris);
  }).catch((err: BusinessError) => {
    console.error(`Invoke photoViewPicker.select failed, code is ${err.code}, message is ${err.message}`);
  })5. 打印输出 
photoViewPicker.select to file succeed and uris are:file://media/Photo/1/IMG_1756079725_000/screenshot_20250825_075345.jpg完整代码 
// 1 导入模块 photoAccessHelper 模块
import { photoAccessHelper } from '@kit.MediaLibraryKit';
import { BusinessError } from '@kit.BasicServicesKit';
@ComponentV2
@Entry
struct Index {
  build() {
    Column({ space: 10 }) {
      Button("选择图片")
        .onClick(() => {
          // 2 设置选择图片的参数
          const photoSelectOptions = new photoAccessHelper.PhotoSelectOptions();
          photoSelectOptions.MIMEType = photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE; // 过滤选择媒体文件类型为IMAGE。
          photoSelectOptions.maxSelectNumber = 5; // 选择媒体文件的最大数目。
          let uris: Array<string> = [];
          // 3 创建图片选择器
          const photoViewPicker = new photoAccessHelper.PhotoViewPicker();
          // 4 开始选择图片
          photoViewPicker.select(photoSelectOptions).then((photoSelectResult: photoAccessHelper.PhotoSelectResult) => {
            uris = photoSelectResult.photoUris;
            console.info('photoViewPicker.select to file succeed and uris are:' + uris);
          }).catch((err: BusinessError) => {
            console.error(`Invoke photoViewPicker.select failed, code is ${err.code}, message is ${err.message}`);
          })
        })
    }
    .width("100%")
    .height("100%")
    .justifyContent(FlexAlign.Center)
  }
}SaveButton 安全控件 保存图片 
应用可以通过安全控件或授权弹窗的方式,将用户指定的媒体资源保存到图库中。授权弹窗的方式需要另外设置权限和向用户申请权限,如果安全控件可以满足我们的需求,建议直接使用安全控件的方式。
使用安全控件的主要流程如下:

1. 设置安全控件的基本样式 
如果安全控件的基本样式不清晰、明了,那么系统就会拒绝授权给你保存图片,这个务必要注意。
安全控件的保存控件。用户点击保存控件,应用可以临时获取存储权限,而不需要权限弹框授权确认。
为避免控件样式不合法导致授权失败,请开发者先了解安全控件样式的约束与限制。
可能会导致授权失败的问题(包括但不限于):
- 字体、图标尺寸过小。
- 安全控件整体尺寸过大。
- 字体、图标、背景按钮的颜色透明度过高。
- 字体或图标与背景按钮颜色过于相似。
- 安全控件超出屏幕、超出窗口等,导致显示不全。
- 安全控件被其他组件或窗口遮挡。
- 安全控件的父组件有类似变形模糊等可能导致安全控件显示不完整的属性。
     SaveButton({
        text: SaveDescription.SAVE_IMAGE,
        icon: SaveIconStyle.FULL_FILLED
      })
2. 使用 phAccessHelper得到存图库中的路径 
createAsset:指定文件类型、后缀和创建选项,创建图片或视频资源
      SaveButton({
        text: SaveDescription.SAVE_IMAGE,
        icon: SaveIconStyle.FULL_FILLED
      })
        .onClick(async (event, result: SaveButtonOnClickResult) => {
          if (result == SaveButtonOnClickResult.SUCCESS) {
            try { // 获取相册访问助手
              const phAccessHelper = photoAccessHelper.getPhotoAccessHelper(getContext());
              // 创建图片资源
              const createOptions: photoAccessHelper.CreateOptions = {
                subtype: photoAccessHelper.PhotoSubtype.DEFAULT
              };
              const uri =
                await phAccessHelper.createAsset(photoAccessHelper.PhotoType.IMAGE, this.fileUrl.split('.').pop(),
                  createOptions)
              
            } catch (e) {
              console.log("保存失败", e.message)
            }
          }
        })3. 读取要保存图片的源数据 
这里需要传入Picker选择的图片的具体路径 this.fileUri
          // 4 开始选择图片
          photoViewPicker.select(photoSelectOptions).then((photoSelectResult: photoAccessHelper.PhotoSelectResult) => {
            uris = photoSelectResult.photoUris;
            this.fileUri = uris[0] // 存储路径
            console.info('photoViewPicker.select to file succeed and uris are:' + uris);
          }).catch((err: BusinessError) => {
            console.error(`Invoke photoViewPicker.select failed, code is ${err.code}, message is ${err.message}`);
          })使用 fileIo kit读取图片数据
import { fileIo as fs } from '@kit.CoreFileKit';
  // 读取源文件数据
  const sourceFile = fs.openSync(this.fileUri, fs.OpenMode.READ_ONLY);
  const stat = fs.statSync(sourceFile.fd);
  const imageData = new ArrayBuffer(stat.size);
  fs.readSync(sourceFile.fd, imageData);
  fs.closeSync(sourceFile);4. 写入到相册中 
最后写入到相册中
  // 写入到相册
  const destFile = fs.openSync(uri!, fs.OpenMode.WRITE_ONLY);
  fs.writeSync(destFile.fd, imageData);
  fs.closeSync(destFile);案例完整代码 
// 1 导入模块 photoAccessHelper 模块
import { photoAccessHelper } from '@kit.MediaLibraryKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { fileIo as fs } from '@kit.CoreFileKit';
@ComponentV2
@Entry
struct Index {
  fileUri: string = ""
  build() {
    Column({ space: 10 }) {
      Button("选择图片")
        .onClick(() => {
          // 2 设置选择图片的参数
          const photoSelectOptions = new photoAccessHelper.PhotoSelectOptions();
          photoSelectOptions.MIMEType = photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE; // 过滤选择媒体文件类型为IMAGE。
          photoSelectOptions.maxSelectNumber = 5; // 选择媒体文件的最大数目。
          let uris: Array<string> = [];
          // 3 创建图片选择器
          const photoViewPicker = new photoAccessHelper.PhotoViewPicker();
          // 4 开始选择图片
          photoViewPicker.select(photoSelectOptions).then((photoSelectResult: photoAccessHelper.PhotoSelectResult) => {
            uris = photoSelectResult.photoUris;
            this.fileUri = uris[0]
            console.info('photoViewPicker.select to file succeed and uris are:' + uris);
          }).catch((err: BusinessError) => {
            console.error(`Invoke photoViewPicker.select failed, code is ${err.code}, message is ${err.message}`);
          })
        })
      SaveButton({
        text: SaveDescription.SAVE_IMAGE,
        icon: SaveIconStyle.FULL_FILLED
      })
        .onClick(async (event, result: SaveButtonOnClickResult) => {
          if (result == SaveButtonOnClickResult.SUCCESS) {
            try { // 获取相册访问助手
              const phAccessHelper = photoAccessHelper.getPhotoAccessHelper(getContext());
              // 创建图片资源
              const createOptions: photoAccessHelper.CreateOptions = {
                subtype: photoAccessHelper.PhotoSubtype.DEFAULT
              };
              // const filename = Date.now().toString() + "." + this.fileUrl.split('.').pop()
              const uri =
                await phAccessHelper.createAsset(photoAccessHelper.PhotoType.IMAGE, this.fileUri.split('.').pop(),
                  createOptions)
              // 读取源文件数据
              const sourceFile = fs.openSync(this.fileUri, fs.OpenMode.READ_ONLY);
              const stat = fs.statSync(sourceFile.fd);
              const imageData = new ArrayBuffer(stat.size);
              fs.readSync(sourceFile.fd, imageData);
              fs.closeSync(sourceFile);
              // 写入到相册
              const destFile = fs.openSync(uri!, fs.OpenMode.WRITE_ONLY);
              fs.writeSync(destFile.fd, imageData);
              fs.closeSync(destFile);
              console.log('图片已保存到相册:', uri);
            } catch (e) {
              console.log("保存失败", e.message)
            }
          }
        })
    }
    .width("100%")
    .height("100%")
    .justifyContent(FlexAlign.Center)
  }
}总结 
本文详细介绍了HarmonyOS应用开发中两个重要功能的实现方法:
1. 图片选择功能(Picker)
- 使用photoAccessHelper模块实现图片选择
- 支持从相册选择图片或拍照获取图片
- 通过配置PhotoSelectOptions参数控制选择行为
- 返回图片的URI信息供后续处理
2. 图片保存功能(SaveButton)
- 使用安全控件SaveButton实现图片保存到图库
- 无需复杂的权限申请流程
- 通过createAsset创建图库资源路径
- 使用fileIo模块读取和写入图片数据
开发要点:
- 安全控件样式需要清晰明了,避免授权失败
- 正确处理图片URI和文件操作
- 合理配置选择参数以满足应用需求
这两个功能是图片编辑类应用的核心基础,掌握它们可以为用户提供流畅的图片处理体验。
以往文章 
- 可可图片编辑 HarmonyOS 上架应用分享
- 懂你的HarmonyOS知识库更新了,已经来到10970个文档了
- 鸿蒙开发者狂喜!5000 元 / 个应用的羊毛快来薅
- AI编程神器!Trae+Claude4.0 简单配置 让 HarmonyOS 开发效率飙升
近期活动 
最近想要想要考取 HarmonyOS 基础或者高级证书,或者快要获取的同学都可以点击这个链接,加入我的班级,考取成功有机会获得鸿蒙礼盒一份。

联系我 
可以加我微信,带你了解更多HarmonyOS相关的资讯。
