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

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的回调,在回调中可以的截取到的图片进行赋值操作

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

TypeScript 复制代码
  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保存到沙箱路径

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

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

TypeScript 复制代码
   // 将图片保存到媒体库
      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。
TypeScript 复制代码
  // 获取需要保存到媒体库的位于应用沙箱的图片/视频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)
TypeScript 复制代码
 // 拉起保存弹框,获取到保存后的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方法的作用是将被保存的文件写入到目标文件中,以下是两个函数完整代码,结合上面截图操作之后,就可以完成截图保存到相册的功能

TypeScript 复制代码
  // 保存图片到媒体库
  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那样限制样式。

TypeScript 复制代码
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、效果示例

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

相关推荐
HMS Core4 小时前
【FAQ】HarmonyOS SDK 闭源开放能力 — PDF Kit
华为·pdf·harmonyos
二蛋和他的大花5 小时前
HarmonyOS运动开发:如何集成百度地图SDK、运动跟随与运动公里数记录
华为·harmonyos
SuperHeroWu75 小时前
【HarmonyOS 5】鸿蒙页面和组件生命周期函数
华为·harmonyos·鸿蒙·自定义组件·页面·生命周期函数
HarmonyOS小助手6 小时前
Flutter适配HarmonyOS 5开发知识地图
harmonyos·鸿蒙·harmonyos next·鸿蒙flutter
__Benco8 小时前
OpenHarmony平台驱动开发(九),MIPI DSI
人工智能·驱动开发·harmonyos
深海的鲸同学 luvi9 小时前
【HarmonyOS 5】App Linking 应用间跳转详解
华为·harmonyos·applinking·应用间跳转
Bruce_Liuxiaowei10 小时前
HarmonyOS NEXT深度解析:自研框架ArkUI-X的技术革命与跨平台实践
华为·harmonyos
仓颉编程语言1 天前
南京大学OpenHarmony技术俱乐部正式揭牌 仓颉编程语言引领生态创新
harmonyos·鸿蒙·仓颉编程语言
yuanlaile1 天前
Flutter开发HarmonyOS实战-鸿蒙App商业项目
flutter·华为·harmonyos·flutter开发鸿蒙