效果图:
编辑
先看一下整体的结构:
@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生成的二维码
现在要实现点击按钮保存图片:
- 把二维码那个组件进行截图,并获取。(用componentSnapshot.get('id')截图组件,记得绑定羁绊)
async saveImage(){
// 进行截图 -> 组件生成的内容搞成一个图片
const pixelMap = await componentSnapshot.get('share') // share对应组件的id 来绑定羁绊
}
- 把图片写入到沙箱中。
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返回结果。
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| id | string | 是 | 目标组件的组件标识。 |
| 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
- getUriFromPath(path: string): string
- 通过传入的路径path生成应用自己的uri
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| path | string | 是 | 文件的沙箱路径。 |
// 生成资源文件的url地址 imagePath 沙箱路径
const uri = fileUri.getUriFromPath(imagePath)
6. 相册管理模块photoAccessHelper
- 资产变更请求MediaAssetChangeRequest 这个玩意是个类 创建图片资产变更请求createImageAssetRequest
- static createImageAssetRequest(context: Context, fileUri: string): MediaAssetChangeRequest
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| context | Context | 是 | 传入Ability实例的Context。 |
| fileUri | string | 是 | 图片资产的数据来源,在应用沙箱下的uri。 |
- 获取相册管理模块的实例getPhotoAccessHelper
- getPhotoAccessHelper(context: Context): PhotoAccessHelper
- 提交媒体变更请求applyChanges
- applyChanges(mediaChangeRequest: MediaChangeRequest): Promise
- 需要权限: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)
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| event | ClickEvent | 是 | 见ClickEvent对象说明 |
| result | SaveButtonOnClickResult | 是 | 存储权限的授权结果,授权时长为10秒 |
// 临时获取权限
SaveButton({
icon: SaveIconStyle.FULL_FILLED, // 设置保存按钮的图标风格 不可自定义
text: SaveDescription.SAVE_IMAGE, // 设置按钮的文本描述 以及对应的权限
buttonType: ButtonType.Normal // 设置保存按钮的背景样式
})
.onClick((_event, result) => {
if (result === SaveButtonOnClickResult.SUCCESS) {
this.saveImage()
}
})