鸿蒙中把组件截图写入到沙箱在变更到相册(资源变更)

102 阅读5分钟

效果图:

​编辑

先看一下整体的结构:

@CustomDialog

struct QuestionShareDialog {

controller: CustomDialogController // 控制器

build() {

Stack({ alignContent: Alignment.BottomEnd }) {

Column({ space: 20 }) {

Text('学IT,当牛马')

// 生成二维码 通过扫码这个二维码可以拿到这个字符串

QRCode('当阳桥头胆气生,吓退曹操百万兵')

.width(160)

.height(160)

.alignSelf(ItemAlign.Center)

}

.id('share') // 设置组件id

.padding(20)

.alignItems(HorizontalAlign.Start)

.width(300)

.height(300)

.backgroundColor($r('app.color.white'))

// 按钮

Row() {

Button({

icon: SaveIconStyle.FULL_FILLED,

text: SaveDescription.SAVE_IMAGE,

buttonType: ButtonType.Normal

})

.fontColor($r('app.color.white'))

.fontSize(14)

.padding(12)

.backgroundColor(Color.Orange)

}

.borderRadius({ topLeft: 8 })

.clip(true)

}

.borderRadius(8)

.clip(true)

}

}

@Entry

@Component

struct Index {

// 自定义弹窗控制器

dialog= new CustomDialogController({

builder: QuestionShareDialog(), // CustomDialog弹窗内容

customStyle: true, // 使用自定义样式

alignment: DialogAlignment.Center // 居中

})

build() {

Column({ space: 20 }) {

Button('查看二维码')

.onClick(() => {

this.dialog.open()

})

}

.width('100%')

.height('100%')

.justifyContent(FlexAlign.Center)

}

}

使用CustomDialog写的弹窗、QRCode生成的二维码

现在要实现点击按钮保存图片:

  1. 把二维码那个组件进行截图,并获取。(用componentSnapshot.get('id')截图组件,记得绑定羁绊)

async saveImage(){

// 进行截图 -> 组件生成的内容搞成一个图片

const pixelMap = await componentSnapshot.get('share') // share对应组件的id 来绑定羁绊

}

  1. 把图片写入到沙箱中。 

async saveImage () {

// 进行截图 -> 把一个组件生成的内容搞成一个图片

const pixelMap = await componentSnapshot.get('share') // share对应组件的id 来绑定羁绊

// 图片数据

const imagePacker = image.createImagePacker() // 创建处理图片的实例

// packToData image实例的这个方法用来把图片数据打包成arrayBuffer数据流

const arrayBuffer = await imagePacker.packToData(pixelMap, { format: "image/jpeg", quality: 98 })

// 存储图片

const ctx = getContext(this) // 获取当前环境上下文

const imagePath = ctx.cacheDir + '/' + Date.now() + '.jpeg' // 根据上下文拿到沙箱路径然后往沙箱后面拼接路径

// 打开文件 创建或读写模式 (没有这个文件就创建有就读写)

const file = fileIo.openSync(imagePath, fileIo.OpenMode.CREATE | fileIo.OpenMode.READ_WRITE)

// 同步写入图片数据

fileIo.writeSync(file.fd, arrayBuffer)

// 关闭文件

fileIo.closeSync(file.fd)

}

    沙箱中不能直接存储图片格式,要把图片转化为buffer数据流进行存储

  3. 在把图片资产从私有变更为公有(把沙箱中的图片写入相册)。

async saveImage () {

// ...

// 把沙箱中的图片写入相册

// 获取资源文件的url地址

const uri = fileUri.getUriFromPath(imagePath)

// 进行图片资产变更(私有 -> 公有)就相当于封装一个从沙箱传到相册的请求,然后通过相册实例调用

const assetChangeRequest = photoAccessHelper.MediaAssetChangeRequest.createImageAssetRequest(ctx, uri)

// 获取相册管理模块的实例

const phAccessHelper = photoAccessHelper.getPhotoAccessHelper(ctx)

// 调用变更方法

await phAccessHelper.applyChanges(assetChangeRequest)

this.controller.close() // 关闭弹窗

promptAction.showToast({ message: '保存成功' }) // 提示保存成功

}

  4. 发现并不能写入到相册,因为我们没有调用写入相册的方法的权限,需要用户开启,可以使用SaveButton组件向用户申请临时开启写入相册的权限

// 临时获取权限

SaveButton({

icon: SaveIconStyle.FULL_FILLED, // 设置保存按钮的图标风格

text: SaveDescription.SAVE_IMAGE, // 设置保存按钮的文本描述 和对应的权限 不可自定义

buttonType: ButtonType.Normal // 设置保存按钮的背景样式

})

.fontColor($r('app.color.white'))

.fontSize(14)

.padding(12)

.backgroundColor(Color.Orange)

// 点击事件 -> 点击时向用户申请临时权限

.onClick((_event, result) => {

// result 用来判断用户是否同意开启临时权限

if (result === SaveButtonOnClickResult.SUCCESS) {

this.saveImage() // 开启权限我就执行上面那一系列操作

}

}

完整代码:

import { componentSnapshot, promptAction } from '@kit.ArkUI'

import { image } from '@kit.ImageKit'

import { fileIo, fileUri } from '@kit.CoreFileKit'

import { photoAccessHelper } from '@kit.MediaLibraryKit'

@CustomDialog

struct QuestionShareDialog {

async saveImage () {

// 进行截图 -> 把一个组件生成的内容搞成一个图片

const pixelMap = await componentSnapshot.get('share') // share对应组件的id 来绑定羁绊

// 图片数据

const imagePacker = image.createImagePacker() // 创建处理图片的实例

// packToData image实例的这个方法用来把图片数据打包成arrayBuffer数据流

const arrayBuffer = await imagePacker.packToData(pixelMap, { format: "image/jpeg", quality: 98 })

// 存储图片

const ctx = getContext(this) // 获取当前环境上下文

const imagePath = ctx.cacheDir + '/' + Date.now() + '.jpeg' // 根据上下文拿到沙箱路径然后往沙箱后面拼接路径

// 打开文件 创建或读写模式 (没有这个文件就创建有就读写)

const file = fileIo.openSync(imagePath, fileIo.OpenMode.CREATE | fileIo.OpenMode.READ_WRITE)

// 同步写入图片数据

fileIo.writeSync(file.fd, arrayBuffer)

// 关闭文件

fileIo.closeSync(file.fd)

// 把沙箱中的图片写入相册

// 获取资源文件的url地址

const uri = fileUri.getUriFromPath(imagePath)

// 进行图片资产变更(私有 -> 公有)

const assetChangeRequest = photoAccessHelper.MediaAssetChangeRequest.createImageAssetRequest(ctx, uri)

// 获取相册管理模块的实例

const phAccessHelper = photoAccessHelper.getPhotoAccessHelper(ctx)

// 调用变更方法

await phAccessHelper.applyChanges(assetChangeRequest)

this.controller.close() // 关闭弹窗

promptAction.showToast({ message: '保存成功' }) // 提示保存成功

}

controller: CustomDialogController // 控制器

build() {

Stack({ alignContent: Alignment.BottomEnd }) {

Column({ space: 20 }) {

// 生成二维码

Text('学IT,当牛马')

QRCode('当阳桥头胆气生,吓退曹操百万兵')

.width(160)

.height(160)

.alignSelf(ItemAlign.Center)

}

.id('share') // id

.padding(20)

.alignItems(HorizontalAlign.Start)

.width(300)

.height(300)

.backgroundColor($r('app.color.white'))

// 按钮

Row() {

// 临时获取权限

SaveButton({

icon: SaveIconStyle.FULL_FILLED, // 设置保存按钮的图标风格

text: SaveDescription.SAVE_IMAGE, // 设置保存按钮的文本描述

buttonType: ButtonType.Normal // 设置保存按钮的背景样式

})

.fontColor($r('app.color.white'))

.fontSize(14)

.padding(12)

.backgroundColor(Color.Orange)

.onClick((_event, result) => {

if (result === SaveButtonOnClickResult.SUCCESS) {

this.saveImage()

}

})

}

.borderRadius({ topLeft: 8 })

.clip(true)

}

.borderRadius(8)

.clip(true)

}

}

@Entry

@Component

struct Index {

// 自定义弹窗控制器

dialog= new CustomDialogController({

builder: QuestionShareDialog(), // CustomDialog弹窗内容

customStyle: true, // 使用自定义样式

alignment: DialogAlignment.Center // 居中

})

build() {

Column({ space: 20 }) {

Button('查看二维码')

.onClick(() => {

this.dialog.open()

})

}

.width('100%')

.height('100%')

.justifyContent(FlexAlign.Center)

}

}

知识点:

1. 组件截图componentSnapshot

get(api12有getSync同步方法)

get(id: string, options?: SnapshotOptions): Promise<image.PixelMap>

获取已加载的组件的截图,传入组件的组件标识,找到对应组件进行截图。通过Promise返回结果。

参数名类型必填说明
idstring目标组件的组件标识
options12+SnapshotOptions截图相关的自定义参数。

2. 图片管理工具image.createImagePacker()

把图片数据打包成arrayBuffer数据流:packToData(图片,PackingOption打包配置)

// 进行截图 -> 把一个组件生成的内容搞成一个图片

const pixelMap = await componentSnapshot.get('share') // share对应组件的id 来绑定羁绊

// 图片数据

const imagePacker = image.createImagePacker() // 创建处理图片的实例

// packToData image实例的这个方法用来把图片数据打包成arrayBuffer数据流

// format:图片格式,quality: 压缩程度

const arrayBuffer = await imagePacker.packToData(pixelMap, { format: "image/jpeg", quality: 98 })

3. 获取沙箱路径cacheDir

要根据上下文获取沙箱路径

const ctx = getContext(this) // 获取当前环境上下文

const path = ctx.cacheDir // 根据上下文拿到沙箱路径

4. 文件管理fileIo

const imagePath = ctx.cacheDir + '/' + Date.now() + '.jpeg'

// 打开文件 创建或读写模式 (没有这个文件就创建有就读写)imagePath是你要创建/读写的文件路径

const file = fileIo.openSync(imagePath, fileIo.OpenMode.CREATE | fileIo.OpenMode.READ_WRITE)

// 同步写入数据 -> fd是这个文件的标识 数据只能是字符串或者buffer

fileIo.writeSync(file.fd, arrayBuffer)

// 关闭文件

fileIo.closeSync(file.fd)

5. 文件URI fileUri

  1. getUriFromPath(path: string): string
  2. 通过传入的路径path生成应用自己的uri
参数名类型必填说明
pathstring文件的沙箱路径。

// 生成资源文件的url地址 imagePath 沙箱路径

const uri = fileUri.getUriFromPath(imagePath)

6. 相册管理模块photoAccessHelper

  1. 资产变更请求MediaAssetChangeRequest 这个玩意是个类 创建图片资产变更请求createImageAssetRequest
  2. static createImageAssetRequest(context: Context, fileUri: string): MediaAssetChangeRequest
参数名类型必填说明
contextContext传入Ability实例的Context。
fileUristring图片资产的数据来源,在应用沙箱下的uri。
  1.  获取相册管理模块的实例getPhotoAccessHelper
  2. getPhotoAccessHelper(context: Context): PhotoAccessHelper
  3.  提交媒体变更请求applyChanges
  4. applyChanges(mediaChangeRequest: MediaChangeRequest): Promise
  5. 需要权限:ohos.permission.WRITE_IMAGEVIDEO

// 语法

// 生成资源文件的url地址

const uri = fileUri.getUriFromPath(imagePath)

// 创建图片资产变更方法(私有 -> 公有)

const assetChangeRequest = photoAccessHelper.MediaAssetChangeRequest.createImageAssetRequest(ctx, uri)

// 获取相册管理模块的实例

const phAccessHelper = photoAccessHelper.getPhotoAccessHelper(ctx)

// 调用变更方法

await phAccessHelper.applyChanges(assetChangeRequest)

7. 临时权限按钮SaveButton

SaveButton(options:SaveButtonOptions)

onClick 点击回调 onClick((event: ClickEvent, result: SaveButtonOnClickResult) => void)

参数名类型必填说明
eventClickEvent见ClickEvent对象说明
resultSaveButtonOnClickResult存储权限的授权结果,授权时长为10秒

// 临时获取权限

SaveButton({

icon: SaveIconStyle.FULL_FILLED, // 设置保存按钮的图标风格 不可自定义

text: SaveDescription.SAVE_IMAGE, // 设置按钮的文本描述 以及对应的权限

buttonType: ButtonType.Normal // 设置保存按钮的背景样式

})

.onClick((_event, result) => {

if (result === SaveButtonOnClickResult.SUCCESS) {

this.saveImage()

}

})