componentSnapshot实现截图
- 前言
- 权限配置和申请
- 权限配置
- 权限申请
- componentSnapshot截图实现
- 将PixelMap转换成图片格式
- 保存截图到系统相册
- 保存截图到应用沙箱
- 全部源码
- 参考资料
前言
HarmonyOS提供了componentSnapshot实现组件截图功能,可以将UI截图成为image.PixelMap
,然后可以将PixlMap保存到本地,运行效果如下图:
通过这篇博文你可以了解到:
- HarmonyOS的权限申请方法
- 截图功能的具体实现实现方法
- 截图PixelMap转换成图片格式的方法
- 异步和并发的简单使用
- 将截图保存到系统相册的实现方法
- 将截图保存到应用沙箱的实现方法
权限配置和申请
截图保存的功能,截图需要使用componentSnapshot,保存到系统相册需要在module.json5配置权限限“ohos.permission.WRITE_IMAGEVIDEO”
权限配置
"requestPermissions": [
{
"name": "ohos.permission.WRITE_IMAGEVIDEO",
"reason": "$string:reason",
"usedScene": {
"abilities": [
"EntryAbility"
],
"when": "inuse"
}
}
]
配置权限有个when属性,其有两个值:inuse(使用时)、always(始终),其他相关参数详见官方文档
权限申请
const permissions: Array<Permissions> = ['ohos.permission.WRITE_IMAGEVIDEO'];
function reqPermissionsFromUser(permissions: Array<Permissions>, context: common.UIAbilityContext): void {
let atManager: abilityAccessCtrl.AtManager =
abilityAccessCtrl.createAtManager();
// requestPermissionsFromUser会判断权限的授权状态来决定是否唤起弹窗
atManager.requestPermissionsFromUser(context, permissions).then((data) => {
let grantStatus: Array<number> = data.authResults;
let length: number = grantStatus.length;
for (let i = 0; i < length; i++) {
if (grantStatus[i] === 0) {
// 用户授权,可以继续访问目标操作
} else {
// 用户拒绝授权,提示用户必须授权才能访问当前页面的功能,并引导用户到系统设置中打开相应的权限
return;
}
}
// 授权成功
}).catch((err: BusinessError) => {
console.error(`Failed to request permissions from user. Code is ${err.code}, message is ${err.message}`);
})
}
上述方法可以在两处地方调用:在UIAbility和在UI中向用户申请授权。
- 在UIAbility申请权限,在
windowStage.loadContent
完成后申请
// 使用UIExtensionAbility:将 UIAbility 替换为UIExtensionAbility
export default class EntryAbility extends UIAbility {
onWindowStageCreate(windowStage: window.WindowStage): void {
// ...
windowStage.loadContent('pages/Index', (err, data) => {
reqPermissionsFromUser(permissions, this.context);
// ...
});
}
// ...
}
- 在UI中申请权限,可以在
aboutToApper
方法中申请
Index {
aboutToAppear() {
// 使用UIExtensionAbility:将common.UIAbilityContext 替换为common.UIExtensionContext
const context: common.UIAbilityContext = getContext(this) as common.UIAbilityContext;
reqPermissionsFromUser(permissions, context);
}
build() {
// ...
}
}
struct
向用户申请权限的更多细节,参考官方文档
componentSnapshot截图实现
该组件使用起来很简单,比如给组件设置一个id,将id传给componentSnapshot.get
即可实现,代码如下,实现效果见文章开头gif图片。
详细的截图功能,参考官方文档。
将PixelMap转换成图片格式
将PixelMap转成图片格式,因为是耗时任务,所以使用异步操作,使用Promise保存返回值,Promise是一种用于处理异步操作的对象,可以将异步操作转换为类似于同步操作的风格,以方便代码编写和维护。Promise提供了一个状态机制来管理异步操作的不同阶段,并提供了一些方法来注册回调函数以处理异步操作的成功或失败的结果,详见异步并发概述 (Promise和async/await)。
// 将pixelMap转成图片格式
transferPixelMap2Buffer(pixelMap: image.PixelMap): Promise<ArrayBuffer> {
return new Promise((resolve, reject) => {
/**
* 设置打包参数
* format:图片打包格式,只支持 jpg 和 webp
* quality:JPEG 编码输出图片质量 0-100
* bufferSize:图片大小,默认 10M
*/
let packOpts: image.PackingOption = { format: 'image/jpeg', quality: 98 };
// 创建ImagePacker实例
const imagePackerApi = image.createImagePacker();
imagePackerApi.packing(pixelMap, packOpts).then((buffer: ArrayBuffer) => {
resolve(buffer);
}).catch((err: BusinessError) => {
reject();
})
})
}
保存截图到系统相册
注意使用async修饰,设置为异步执行。async/await是一种用于处理异步操作的Promise语法糖,通过使用async关键字声明一个函数为异步函数,并使用await关键字等待Promise的解析(完成或拒绝),以同步的方式编写异步操作的代码。async函数是一个返回Promise对象的函数,用于表示一个异步操作。在async函数内部,可以使用await关键字等待一个Promise对象的解析,并返回其解析值,注意下面代码最后一行 fileIo.close(file.fd)
返回的是Promise<void>
// 保存到系统相册
async savePixmap2SysHelper() {
if (!this.pixmap) {
return;
}
const imgBuffer = await this.transferPixelMap2Buffer(this.pixmap);
// 获取相册管理模块的实例,用于访问和修改相册中的媒体文件。
let helper = photoAccessHelper.getPhotoAccessHelper(getContext(this));
// 指定待创建的文件类型和后缀,创建图片或视频资源,使用callback方式返回结果。
const uri = await helper.createAsset(photoAccessHelper.PhotoType.IMAGE, 'png');
const file = await fileIo.open(uri, fileIo.OpenMode.READ_WRITE | fileIo.OpenMode.CREATE);
await fileIo.write(file.fd, imgBuffer);
//注意declare function close(file: number | File): Promise<void>
await fileIo.close(file.fd);
}
保存截图到应用沙箱
应用沙箱是一种以安全防护为目的的隔离机制,避免数据受到恶意路径穿越访问。在这种沙箱的保护机制下,应用可见的目录范围即为“应用沙箱目录”,详见官方文档
: common.UIAbilityContext = getContext(this) as common.UIAbilityContext;
//获取文件目录
filesDir: string = this.context.filesDir;
// 保存到应用沙箱
async savePixmap2SystemFileManager() {
if (!this.pixmap) {
return;
}
const imgBuffer = await this.transferPixelMap2Buffer(this.pixmap);
const file = fileIo.openSync(this.filesDir + `/${DateUtil.getTimeStamp()}.png`,
fileIo.OpenMode.READ_WRITE | fileIo.OpenMode.CREATE);
await fileIo.write(file.fd, imgBuffer);
await fileIo.close(file.fd);
}
context
上述代码中其中filesDir获取的是应用通用文件路径,随应用卸载而清理。可以用于保存应用的任何私有数据,主要包括用户持久性文件、图片、媒体文件以及日志文件等。此路径下存储这些数据,使得数据保持私有、安全且持久有效
全部源码
import { componentSnapshot } from '@kit.ArkUI';
import { image } from '@kit.ImageKit';
import { photoAccessHelper } from '@kit.MediaLibraryKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { abilityAccessCtrl, common, Permissions } from '@kit.AbilityKit';
import { intl } from '@kit.LocalizationKit';
import { fileIo } from '@kit.CoreFileKit';
const permissions: Array<Permissions> = ['ohos.permission.WRITE_IMAGEVIDEO'];
function reqPermissionsFromUser(permissions: Array<Permissions>, context: common.UIAbilityContext): void {
let atManager: abilityAccessCtrl.AtManager =
abilityAccessCtrl.createAtManager();
// requestPermissionsFromUser会判断权限的授权状态来决定是否唤起弹窗
atManager.requestPermissionsFromUser(context, permissions).then((data) => {
let grantStatus: Array<number> = data.authResults;
let length: number = grantStatus.length;
for (let i = 0; i < length; i++) {
if (grantStatus[i] === 0) {
// 用户授权,可以继续访问目标操作
} else {
// 用户拒绝授权,提示用户必须授权才能访问当前页面的功能,并引导用户到系统设置中打开相应的权限
return;
}
}
// 授权成功
}).catch((err: BusinessError) => {
console.error(`Failed to request permissions from user. Code is ${err.code}, message is ${err.message}`);
})
}
export struct ComponentScreenshot {
message: string = 'Hello World';
timerSecond: number = 20;
pixmap: image.PixelMap | null = null;
isAutoPlay: boolean = true;
showControls: boolean = false;
curRate: number = 1;
context: common.UIAbilityContext = getContext(this) as common.UIAbilityContext;
filesDir: string = this.context.filesDir;
// controller: VideoController = new VideoController();
aboutToAppear(): void {
reqPermissionsFromUser(permissions, this.context);
}
// 组件截图
clickToComponentSnapshot() {
componentSnapshot.get('root', (error: Error, pixmap: image.PixelMap) => {
if (error) {
console.log('error: ' + JSON.stringify(error));
return;
}
console.log('截图成功');
this.pixmap = pixmap;
})
}
// 保存到系统相册
async savePixmap2SysHelper() {
if (!this.pixmap) {
return;
}
const imgBuffer = await this.transferPixelMap2Buffer(this.pixmap);
// 获取相册管理模块的实例,用于访问和修改相册中的媒体文件。
let helper = photoAccessHelper.getPhotoAccessHelper(getContext(this));
// 指定待创建的文件类型和后缀,创建图片或视频资源,使用callback方式返回结果。
const uri = await helper.createAsset(photoAccessHelper.PhotoType.IMAGE, 'png');
const file = await fileIo.open(uri, fileIo.OpenMode.READ_WRITE | fileIo.OpenMode.CREATE);
await fileIo.write(file.fd, imgBuffer);
await fileIo.close(file.fd);
}
// 保存到应用沙箱
async savePixmap2SystemFileManager() {
if (!this.pixmap) {
return;
}
const imgBuffer = await this.transferPixelMap2Buffer(this.pixmap);
console.log("fileDir=="+this.filesDir);
const file = fileIo.openSync(this.filesDir + `/${DateUtil.getTimeStamp()}.png`,
fileIo.OpenMode.READ_WRITE | fileIo.OpenMode.CREATE);
await fileIo.write(file.fd, imgBuffer);
await fileIo.close(file.fd);
}
// 将pixelMap转成图片格式
transferPixelMap2Buffer(pixelMap: image.PixelMap): Promise<ArrayBuffer> {
return new Promise((resolve, reject) => {
/**
* 设置打包参数
* format:图片打包格式,只支持 jpg 和 webp
* quality:JPEG 编码输出图片质量
* bufferSize:图片大小,默认 10M
*/
let packOpts: image.PackingOption = { format: 'image/jpeg', quality: 98 };
// 创建ImagePacker实例
const imagePackerApi = image.createImagePacker();
imagePackerApi.packing(pixelMap, packOpts).then((buffer: ArrayBuffer) => {
resolve(buffer);
}).catch((err: BusinessError) => {
reject();
})
})
}
build() {
Column() {
Row()
.height(40)
.padding(5)
.position({ x: 0, y: 0 })
.width('100%')
.zIndex(1)
.justifyContent(FlexAlign.End)
.expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM])
Text(this.message)
.fontColor(Color.White)
.fontSize(20)
.margin(50)
Button('点击进行组件截图')
.fontSize(20)
.margin(10)
.width('80%')
.position({
x: 20,
y: 500
})
.onClick(() => {
this.clickToComponentSnapshot();
})
if (this.pixmap) {
Button('保存到系统相册')
.fontSize(20)
.margin(10)
.width('80%')
.position({
x: 20,
y: 550
})
.onClick(() => {
this.savePixmap2SysHelper();
})
Button('保存到应用沙箱')
.fontSize(20)
.margin(10)
.width('80%')
.position({
x: 20,
y: 600
})
.onClick(() => {
this.savePixmap2SystemFileManager();
})
}
}
.id('root')
.height('100%')
.width('100%')
.backgroundColor('#b5b5b5')
.expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM])
}
}
class DateUtil {
static getFormatTimeStr(timestamp: number) {
const date = new Date(timestamp);
let dateFormat3 = new intl.DateTimeFormat('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
});
let formattedDate3 = dateFormat3.format(date);
return formattedDate3;
}
/* 获取当前时间戳 */
static getTimeStamp(): number {
const timeStamp = (new Date()).getTime();
return timeStamp;
}
}
参考资料
@ohos.screenshot (屏幕截图)
组件截图怎么将pixelMap存储到系统相册或应用沙箱
@ohos.arkui.componentSnapshot (组件截图)
权限配置相关字段说明
向用户申请授权
异步并发概述 (Promise和async/await)。
应用沙箱