鸿蒙ArkTs实战之截图保存图片到相册,详细教程,不使用SaveButton的方法,附上源码和效果图

发布于:2025-05-13 ⋅ 阅读:(15) ⋅ 点赞:(0)

 1、适用场景

在鸿蒙应用开发过程中时常遇见需要截图保存到相册的场景,如果使用安全控件SaveButton保存图片到相册,无法给它自定义样式,有很多限制,和实际开发场景不是很适配,所以呢,使用api的方式去保存,这样可以直接自定义保存图片的时机,自定义保存图片按钮的样式,更加贴合实际开发场景,下面结合一个截图保存到相册的小案例来阐述如何截图并绕开SaveButton,自定义保存样式,实现截图并保存到相册的功能,附上完整代码,以及效果图。

2、截图

截图api官方文档:

@ohos.arkui.componentSnapshot (组件截图)-UI界面-ArkTS API-ArkUI(方舟UI框架)-应用框架 - 华为HarmonyOS开发者

参数名 类型 必填 说明
id string 目标组件的组件标识
callback AsyncCallback<image.PixelMap> 截图返回结果的回调。
options12+ SnapshotOptions 截图相关的自定义参数。

 这个api接收三个参数,传参更具需求去进行传参,这里只传必填参数

 截图代码

componentSnapshot.get这个方法接收两个必传参数

第一个参数:被截图组件的id 

第二个参数:一个获取到pixmap的回调,在回调中可以的截取到的图片进行赋值操作

到这里,恭喜你完成了截图的操作

  Column({ space: 20 }) {
        Image(this.pixmap)
          .width(200)
          .height(200)
          .border({ color: Color.Black, width: 2 })
          .margin(5)
          .borderRadius(10)
        Image($r('app.media.avatar'))
          .borderRadius(10)
          .autoResize(true)
          .width(200)
          .height(200)
          .margin(5)
          // 给被截图的组件加上id
          .id("avatar")
      }



    Button("点击截图")
         .linearGradient({
          direction: GradientDirection.Right, // 渐变方向
          repeating: true, // 渐变颜色是否重复
          colors: [['#E9AFAA', 0], [Color.Red, 1]]
           })
         .onClick(async () => {
          // 截图api
          componentSnapshot.get("avatar", (error: Error, pixmap: image.PixelMap) => {
            if (error) {
              console.log("error: " + JSON.stringify(error))
              return;
            }
            // 获取到截取的图片
            this.pixmap = pixmap
          })
        })

  截图效果

点击截图之后,被截取的组件,就会显示到上方的空白框中

3、保存图片

 申请保存权限,调用showAssetsCreationDialog

弹窗提示保存图片的官方文档:

@ohos.file.photoAccessHelper (相册管理模块)-ArkTS API-Media Library Kit(媒体文件管理服务)-媒体 - 华为HarmonyOS开发者

参数名 类型 必填 说明
srcFileUris Array<string>

需保存到媒体库中的图片/视频文件对应的媒体库uri

注意:

- 仅支持处理图片、视频uri。

- 不支持手动拼接的uri,需调用接口获取,获取方式参考媒体文件uri获取方式

photoCreationConfigs Array<PhotoCreationConfig> 保存图片/视频到媒体库的配置,包括保存的文件名等,与srcFileUris保持一一对应。

 这个api有两个必传参数

第一个参数:被保存图片的媒体库uri

第二个参数:保存到媒体库的配置

保存操作代码

保存操作封装了两个方法:

saveImageToAsset 拉起用户授权弹窗,获取到媒体库目标路径

createAssetByIo  将图片写入到媒体库(在saveImageToAsset中调用)

saveImageToAsset保存

接收两个参数:

第一个参数是截图获取到的pixmap

第二个参数是被保存的图片后缀名

showAssetsCreationDialog这个关键函数第一个参数接收的是媒体库的uri,讲人话就是相册路径,那么就需要对pixmap进行处理,这里呢本人十分推荐一个鸿蒙的第三方仓库工具去对这个pixmap进行转换,这个工具是鸿蒙第三方仓库工具中最火的一个工具,功能十分齐全 

工具地址:https://ohpm.openharmony.cn/#/cn/detail/@pura%2Fharmony-utils

安装方式;项目终端中运行以下命令,即可安装

ohpm i @pura/harmony-utils 

 saveImageToAsset函数中使用到 ImageUtil.savePixelMap()方法将pixmap保存到沙箱路径

    // 调用了第三方库,将这个图片保存到沙箱目录下,再保存到媒体库
      const imgPath =
        await ImageUtil.savePixelMap(pixmap, FileUtil.getFilesDirPath(""),util.generateRandomUUID() + ".png")

然后使用FileUtil.getUriFromPath()将沙箱路径转换为媒体库uri

   // 将图片保存到媒体库
      const imgUri = FileUtil.getUriFromPath(imgPath);
      let phAccessHelper = photoAccessHelper.getPhotoAccessHelper(getContext());

 配置showAssetsCreationDialog参数

第一个参数:将转换好的媒体库uri放到一个字符串数组

第二个参数:参照官方文档配置,若是有疑惑可直接复制以下代码

@ohos.file.photoAccessHelper (相册管理模块)-ArkTS API-Media Library Kit(媒体文件管理服务)-媒体 - 华为HarmonyOS开发者

名称 类型 必填 说明
title string

图片或者视频的标题,不传入时由系统生成。参数规格为:

- 不应包含扩展名。

- 文件名字符串长度为1~255(资产文件名为标题+扩展名)。

- 不允许出现非法字符,包括:. \ / : * ? " ' ` < > | { } [ ]

fileNameExtension string 文件扩展名,例如'jpg'。
photoType PhotoType 创建的文件类型PhotoType,IMAGE或者VIDEO。
subtype PhotoSubtype 图片或者视频的文件子类型PhotoSubtype,当前仅支持DEFAULT。
  // 获取需要保存到媒体库的位于应用沙箱的图片/视频uri
      let srcFileUris: Array<string> = [imgUri];
      // 	保存图片/视频到媒体库的配置,包括保存的文件名等,与srcFileUris保持一一对应。
      let photoCreationConfigs: Array<photoAccessHelper.PhotoCreationConfig> = [
        {
          title: util.generateRandomUUID(), // 文件名
          fileNameExtension: nameExtension, // 文件后缀
          photoType: photoAccessHelper.PhotoType.IMAGE, // 图片
          subtype: photoAccessHelper.PhotoSubtype.DEFAULT,
        }];
      // 拉起保存弹框,获取到保存后的uri

 接下来调用关键api  : 获取到返回值  目标文件文件路径

类型 说明
Promise<Array<string>>

Promise对象,返回给应用的媒体库文件uri列表。Uri已对应用授权,支持应用写入数据。如果生成uri异常,则返回批量创建错误码。

返回-3006表不允许出现非法字符;返回-2004表示图片类型和后缀不符;返回-203表示文件操作异常。

 phAccessHelper.showAssetsCreationDialog(srcFileUris, photoCreationConfigs)
 // 拉起保存弹框,获取到保存后的uri
      let desFileUris: Array<string> = await phAccessHelper.showAssetsCreationDialog(srcFileUris, photoCreationConfigs);
      if (desFileUris.length == 0) {
        // 用户拒绝保存
        promptAction.showToast({ message: "用户拒绝保存" })
        throw (new Error("用户拒绝保存"))

      }
      await this.createAssetByIo(imgUri, desFileUris[0]);
      return Promise.resolve();

 调用了 phAccessHelper.showAssetsCreationDialog(srcFileUris, photoCreationConfigs)之后会出现一个弹窗,向用户申请保存权限,点击允许,会调用createAssetByIo()方法

 createAssetByIo写入文件夹

createAssetByIo方法的作用是将被保存的文件写入到目标文件中,以下是两个函数完整代码,结合上面截图操作之后,就可以完成截图保存到相册的功能

  // 保存图片到媒体库
  async saveImageToAsset(pixmap: image.PixelMap, nameExtension: string): Promise<void> {
    try {
      // 调用了第三方库,将这个图片保存到沙箱目录下,再保存到媒体库
      const imgPath =
        await ImageUtil.savePixelMap(pixmap, FileUtil.getFilesDirPath(""), util.generateRandomUUID() + ".png")
      // 将图片保存到媒体库
      const imgUri = FileUtil.getUriFromPath(imgPath);
      let phAccessHelper = photoAccessHelper.getPhotoAccessHelper(getContext());
      // 获取需要保存到媒体库的位于应用沙箱的图片/视频uri
      let srcFileUris: Array<string> = [imgUri];
      // 	保存图片/视频到媒体库的配置,包括保存的文件名等,与srcFileUris保持一一对应。
      let photoCreationConfigs: Array<photoAccessHelper.PhotoCreationConfig> = [
        {
          title: util.generateRandomUUID(), // 文件名
          fileNameExtension: nameExtension, // 文件后缀
          photoType: photoAccessHelper.PhotoType.IMAGE, // 图片
          subtype: photoAccessHelper.PhotoSubtype.DEFAULT,
        }];
      // 拉起保存弹框,获取到保存后的uri
      let desFileUris: Array<string> = await phAccessHelper.showAssetsCreationDialog(srcFileUris, photoCreationConfigs);
      if (desFileUris.length == 0) {
        // 用户拒绝保存
        promptAction.showToast({ message: "用户拒绝保存" })
        throw (new Error("用户拒绝保存"))

      }
      await this.createAssetByIo(imgUri, desFileUris[0]);
      return Promise.resolve();
    } catch (err) {
      return Promise.reject(err);
    }
  }

  // 将源文件复制到目标文件的函数,使用同步方式读写文件
  async createAssetByIo(sourceFilePath: string, targetFilePath: string) {
    try {
      // 以只读模式打开源文件
      let srcFile: fileIo.File = fileIo.openSync(sourceFilePath, fileIo.OpenMode.READ_ONLY);

      // 以读写模式打开目标文件
      let targetFile: fileIo.File = await fileIo.open(targetFilePath, fileIo.OpenMode.READ_WRITE);

      const bufSize = 4096; // 定义缓冲区大小为4KB
      let readSize = 0; // 已读取字节数
      let buf = new ArrayBuffer(bufSize); // 创建缓冲区
      let readOptions: ReadOptions = { offset: readSize, length: bufSize }; // 设置读取选项

      // 从源文件读取数据
      let readLen = fileIo.readSync(srcFile.fd, buf, readOptions);

      // 循环读取直到文件末尾
      while (readLen > 0) {
        // 更新已读取字节数
        readSize += readLen;

        // 将读取到的数据写入目标文件
        fileIo.writeSync(targetFile.fd, buf, { length: readLen });

        // 更新偏移量并继续读取下一块数据
        readOptions.offset = readSize;
        readLen = fileIo.readSync(srcFile.fd, buf, readOptions);
      }

      // 关闭源文件和目标文件
      fileIo.closeSync(srcFile);
      fileIo.closeSync(targetFile);
    } catch (error) {
      // 捕获异常并打印错误信息
      console.error(` 错误信息: ${error} `);
    }
  }








// 调用
Button("保存图片")
  .linearGradient({
          direction: GradientDirection.Right, // 渐变方向
          repeating: true, // 渐变颜色是否重复
          colors: [['#E9AFAA', 0], [Color.Brown, 1]]
        })
  .onClick(() => {
          // 判断是否已经截图
          if (this.pixmap == undefined) {
            promptAction.showToast({ message: "请先截图" })
            return;
          }
          // 保存图片到媒体库
          this.saveImageToAsset(this.pixmap as image.PixelMap, "png")
        })

 保存图片的这一步操作,只需要调用saveImageToAsset方法传入PixelMap,以及图片后缀名,即可保存图片

4、完整代码 

代码逻辑是:

点击“点击截图”按钮将页面中图片截图,并显示到上方的空白框中,点击“保存图片”按钮会弹出用户授权框,用户点击保存就可以将截图保存到相册。

可以看到,这两个按钮都可以自定义样式,不会像SaveButton那样限制样式。

import { componentSnapshot, promptAction } from '@kit.ArkUI';
import { image } from '@kit.ImageKit';
import { fileIo, ReadOptions } from '@kit.CoreFileKit';
import { photoAccessHelper } from '@kit.MediaLibraryKit';
import { util } from '@kit.ArkTS';
import { FileUtil, ImageUtil } from '@pura/harmony-utils';


@Entry
@Component
struct SnapshotAndSave {
  @State pixmap: image.PixelMap | undefined = undefined

  // 保存图片到媒体库
  async saveImageToAsset(pixmap: image.PixelMap, nameExtension: string): Promise<void> {
    try {
      // 调用了第三方库,将这个图片保存到沙箱目录下,再保存到媒体库
      const imgPath =
        await ImageUtil.savePixelMap(pixmap, FileUtil.getFilesDirPath(""), util.generateRandomUUID() + ".png")
      // 将图片保存到媒体库
      const imgUri = FileUtil.getUriFromPath(imgPath);
      let phAccessHelper = photoAccessHelper.getPhotoAccessHelper(getContext());
      // 获取需要保存到媒体库的位于应用沙箱的图片/视频uri
      let srcFileUris: Array<string> = [imgUri];
      // 	保存图片/视频到媒体库的配置,包括保存的文件名等,与srcFileUris保持一一对应。
      let photoCreationConfigs: Array<photoAccessHelper.PhotoCreationConfig> = [
        {
          title: util.generateRandomUUID(), // 文件名
          fileNameExtension: nameExtension, // 文件后缀
          photoType: photoAccessHelper.PhotoType.IMAGE, // 图片
          subtype: photoAccessHelper.PhotoSubtype.DEFAULT,
        }];
      // 拉起保存弹框,获取到保存后的uri
      let desFileUris: Array<string> = await phAccessHelper.showAssetsCreationDialog(srcFileUris, photoCreationConfigs);
      if (desFileUris.length == 0) {
        // 用户拒绝保存
        promptAction.showToast({ message: "用户拒绝保存" })
        throw (new Error("用户拒绝保存"))

      }
      await this.createAssetByIo(imgUri, desFileUris[0]);
      return Promise.resolve();
    } catch (err) {
      return Promise.reject(err);
    }
  }

  // 将源文件复制到目标文件的函数,使用同步方式读写文件
  async createAssetByIo(sourceFilePath: string, targetFilePath: string) {
    try {
      // 以只读模式打开源文件
      let srcFile: fileIo.File = fileIo.openSync(sourceFilePath, fileIo.OpenMode.READ_ONLY);

      // 以读写模式打开目标文件
      let targetFile: fileIo.File = await fileIo.open(targetFilePath, fileIo.OpenMode.READ_WRITE);

      const bufSize = 4096; // 定义缓冲区大小为4KB
      let readSize = 0; // 已读取字节数
      let buf = new ArrayBuffer(bufSize); // 创建缓冲区
      let readOptions: ReadOptions = { offset: readSize, length: bufSize }; // 设置读取选项

      // 从源文件读取数据
      let readLen = fileIo.readSync(srcFile.fd, buf, readOptions);

      // 循环读取直到文件末尾
      while (readLen > 0) {
        // 更新已读取字节数
        readSize += readLen;

        // 将读取到的数据写入目标文件
        fileIo.writeSync(targetFile.fd, buf, { length: readLen });

        // 更新偏移量并继续读取下一块数据
        readOptions.offset = readSize;
        readLen = fileIo.readSync(srcFile.fd, buf, readOptions);
      }

      // 关闭源文件和目标文件
      fileIo.closeSync(srcFile);
      fileIo.closeSync(targetFile);
    } catch (error) {
      // 捕获异常并打印错误信息
      console.error(` 错误信息: ${error} `);
    }
  }

  build() {
    Column() {
      Column({ space: 20 }) {
        Image(this.pixmap)
          .width(200)
          .height(200)
          .border({ color: Color.Black, width: 2 })
          .margin(5)
          .borderRadius(10)
        Image($r('app.media.avatar'))
          .borderRadius(10)
          .autoResize(true)
          .width(200)
          .height(200)
          .margin(5)
          .id("avatar")
      }

      Button("点击截图")
        .linearGradient({
          direction: GradientDirection.Right, // 渐变方向
          repeating: true, // 渐变颜色是否重复
          colors: [['#E9AFAA', 0], [Color.Red, 1]]
        })
        .onClick(async () => {
          // 截图api
          componentSnapshot.get("avatar", (error: Error, pixmap: image.PixelMap) => {
            if (error) {
              console.log("error: " + JSON.stringify(error))
              return;
            }
            // 获取到截取的图片
            this.pixmap = pixmap
          })
        }).margin(10)
      Button("保存图片")
        .linearGradient({
          direction: GradientDirection.Right, // 渐变方向
          repeating: true, // 渐变颜色是否重复
          colors: [['#E9AFAA', 0], [Color.Brown, 1]]
        })
        .onClick(() => {
          // 判断是否已经截图
          if (this.pixmap == undefined) {
            promptAction.showToast({ message: "请先截图" })
            return;
          }
          // 保存图片到媒体库
          this.saveImageToAsset(this.pixmap as image.PixelMap, "png")
        }).margin(10)
    }
    .justifyContent(FlexAlign.Center)
    .linearGradient({
      direction: GradientDirection.Top, // 渐变方向
      colors: [['#6B58F4', 0], [Color.Pink, 1]]
    })
    .width('100%')
    .height('100%')
    .alignItems(HorizontalAlign.Center)
  }
}

5、效果示例

下图中可以看到,原本相册中没有图片,截图并保存之后,相册中就有了我保存的图片


网站公告

今日签到

点亮在社区的每一天
去签到