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、效果示例
下图中可以看到,原本相册中没有图片,截图并保存之后,相册中就有了我保存的图片
