效果图:

先看一下整体的结构:

@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 来绑定羁绊
    }
  2. 把图片写入到沙箱中。 

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

  1. getUriFromPath(path: string): string
  2. 通过传入的路径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

        1. applyChanges(mediaChangeRequest: MediaChangeRequest): Promise<void>
        2. 需要权限: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()
        }
      })

    Logo

    讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

    更多推荐