当前位置: 首页 > news >正文

HarmonyOS(50) 截图保存功能实现

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方法中申请


struct Index {aboutToAppear() {// 使用UIExtensionAbility:将common.UIAbilityContext 替换为common.UIExtensionContextconst context: common.UIAbilityContext = getContext(this) as common.UIAbilityContext;reqPermissionsFromUser(permissions, context);}build() {// ...}
}

向用户申请权限的更多细节,参考官方文档

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);}

保存截图到应用沙箱

应用沙箱是一种以安全防护为目的的隔离机制,避免数据受到恶意路径穿越访问。在这种沙箱的保护机制下,应用可见的目录范围即为“应用沙箱目录”,详见官方文档

在这里插入图片描述

   context: 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);}

上述代码中其中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)。
应用沙箱

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 100 Exercises To Learn Rust 挑战!if・Panic・演练
  • 从零开始构建霸王餐返利APP的技术路线与挑战
  • “前缀和”专题篇二
  • “程序员的艺术转身:AI绘画副业,从代码到画布的变现之旅“
  • 【文件IO】文件内容操作
  • jmeter使用while控制器时防止死循环
  • 临床数据科学和金融数据科学,选择R语言吧!
  • Python操作MongoDB文档存储
  • workerman下的webman路由浏览器跨域的一种问题
  • Docker详解
  • sh脚本发送邮件到多个收件人如何高效实现?
  • #Datawhale AI夏令营第4期#AIGC方向 文生图 Task2
  • 前端面试题整理-Javascript
  • 凤凰端子音频矩阵应用领域
  • 【问题解决】git status中文文件名乱码
  • 「面试题」如何实现一个圣杯布局?
  • 【EOS】Cleos基础
  • codis proxy处理流程
  • JAVA 学习IO流
  • JS数组方法汇总
  • leetcode388. Longest Absolute File Path
  • NLPIR语义挖掘平台推动行业大数据应用服务
  • OSS Web直传 (文件图片)
  • Rancher-k8s加速安装文档
  • Spring Boot MyBatis配置多种数据库
  • Theano - 导数
  • TypeScript实现数据结构(一)栈,队列,链表
  • underscore源码剖析之整体架构
  • vue学习系列(二)vue-cli
  • 开年巨制!千人千面回放技术让你“看到”Flutter用户侧问题
  • 排序算法之--选择排序
  • 前端知识点整理(待续)
  • 如何合理的规划jvm性能调优
  • media数据库操作,可以进行增删改查,实现回收站,隐私照片功能 SharedPreferences存储地址:
  • 阿里云IoT边缘计算助力企业零改造实现远程运维 ...
  • 蚂蚁金服CTO程立:真正的技术革命才刚刚开始
  • # MySQL server 层和存储引擎层是怎么交互数据的?
  • (32位汇编 五)mov/add/sub/and/or/xor/not
  • (42)STM32——LCD显示屏实验笔记
  • (6)设计一个TimeMap
  • (ctrl.obj) : error LNK2038: 检测到“RuntimeLibrary”的不匹配项: 值“MDd_DynamicDebug”不匹配值“
  • (ISPRS,2023)深度语义-视觉对齐用于zero-shot遥感图像场景分类
  • (附源码)spring boot智能服药提醒app 毕业设计 102151
  • (牛客腾讯思维编程题)编码编码分组打印下标(java 版本+ C版本)
  • (四) 虚拟摄像头vivi体验
  • (四十一)大数据实战——spark的yarn模式生产环境部署
  • (五十)第 7 章 图(有向图的十字链表存储)
  • (一) 初入MySQL 【认识和部署】
  • (转)jQuery 基础
  • (转)负载均衡,回话保持,cookie
  • (总结)Linux下的暴力密码在线破解工具Hydra详解
  • ... fatal error LINK1120:1个无法解析的外部命令 的解决办法
  • .desktop 桌面快捷_Linux桌面环境那么多,这几款优秀的任你选
  • .net core 依赖注入的基本用发
  • .NET DevOps 接入指南 | 1. GitLab 安装