一、引言

在 Harmony OS NEXT / 5.0 / API 12+ 版本的应用开发中,实现页面分享到相册的功能可以极大提升用户体验。本文将详细介绍如何通过 ArkTS 实现这一功能,从项目效果、思路分析、源码解读到核心要点总结,全方位带你了解其开发过程。

二、适用版本说明

本文所涉及的功能开发基于 Harmony OS NEXT / 5.0 / API 12+ 版本。该版本提供了丰富的 API 和框架支持,如 @kit.ArkUI@kit.ImageKit@ohos.file.fs 以及 @kit.MediaLibraryKit 等,为实现页面分享至相册功能奠定了坚实基础。

三、项目的效果

                                        

四、思路分析

  1. 捕获当前界面内容:利用 componentSnapshot.get('IdStr') 捕获特定对话框的当前内容,生成一张图片(pixelMap),就像是给当前界面拍了一张 “照片”。
  2. 将图片压缩为可存储格式:借助 image.createImagePacker() 创建图片打包器,把捕获的图片压缩成 JPEG 格式的二进制数据流,如同将 “照片” 进行优化处理,便于后续存储。
  3. 将图片写入临时文件:获取应用沙箱的缓存路径,创建临时文件。把压缩后的二进制数据流写入该文件并关闭文件句柄,这一步如同把优化后的 “照片” 存放在一个临时 “文件夹” 里。
  4. 将图片从临时文件移动到相册:获取临时文件的 URI 地址,通过 photoAccessHelper.MediaAssetChangeRequest 将图片从应用私有空间转移到相册公共空间,就像把 “照片” 从临时 “文件夹” 移动到了手机相册这个 “公共相册” 中。提交变更请求,完成图片保存。
  5. 构建用户界面并处理交互:运用 StackColumnRow 组件搭建对话框布局,包含二维码、分享文本和保存按钮。当用户点击保存按钮,触发 saveImage 方法保存图片并关闭对话框;若用户未授权,则提示授权失败。
  6. 提供操作反馈:在保存图片过程中,通过 promptAction.showToast 向用户展示操作结果(成功或失败),让用户及时了解当前状态,如同给用户一个实时的 “小提示”。

五、源码详解

首页静态样式

import { buildDialog } from './buildDialog';

@Entry
@Component
struct Index {
    dialog = new CustomDialogController({
        builder: buildDialog(),
        customStyle: true,
        alignment: DialogAlignment.Center
    });

    build() {
        Stack({ alignContent: Alignment.Center }) {
            Column({ space: 30 }) {
                Text('欢迎来到 ArkTS 页面')
                   .fontSize(36)
                   .fontWeight(FontWeight.Bold)
                   .fontColor('#333')
                   .margin({ top: 80 });

                Text('这是一个简单的静态页面示例,为你展示基本的页面布局和分享功能。')
                   .fontSize(18)
                   .fontColor('#666')
                   .lineHeight(28)
                   .width('80%')
                   .textAlign(TextAlign.Center);

                Button('分享页面')
                   .width(220)
                   .height(55)
                   .fontSize(20)
                   .fontWeight(FontWeight.Medium)
                   .backgroundColor('#007aff')
                   .fontColor(Color.White)
                   .borderRadius(28)
                   .shadow({
                        offsetX: 0,
                        offsetY: 4,
                        radius: 8,
                        color: '#007aff40'
                    })
                   .onClick(() => {
                        this.dialog.open();
                    });
            }
           .width('90%')
           .padding({ top: 40, bottom: 40, left: 20, right: 20 })
           .backgroundColor(Color.White)
           .borderRadius(16)
           .shadow({
                offsetX: 0,
                offsetY: 8,
                radius: 24,
                color: '#0000001a'
            })
           .alignItems(HorizontalAlign.Center);
        }
       .width('100%')
       .height('100%')
       .backgroundColor('#f4f4f9');
    }
}

这段代码构建了应用的首页,是整个应用的 “入口”。

  • 引入模块:导入 buildDialog,用于创建自定义弹窗。
  • CustomDialogController:创建 dialog,配置自定义弹窗的构建器、样式和对齐方式。
  • build 方法
    • 布局构建:通过 StackColumn 构建页面布局,展示欢迎信息、功能描述以及 “分享页面” 按钮。
    • 按钮交互:点击 “分享页面” 按钮,打开自定义弹窗,为用户提供分享功能入口。

自定义弹窗页码(逻辑现实区域)

import { componentSnapshot, promptAction } from "@kit.ArkUI";
import { image } from "@kit.ImageKit";
import fileIo from "@ohos.file.fs";
import fileUri from "@ohos.file.fileuri";
import { photoAccessHelper } from "@kit.MediaLibraryKit";

@CustomDialog
export struct buildDialog {
    controller: CustomDialogController;
    private shareInfo: string = '这是一个待分享的精彩页面内容,快来看看吧!';

    async saveImage() {
        // 1. 根据组件的id生成截图对象
        const pixelMap = await componentSnapshot.get('IdStr');
        // 2. 借助ImagePacker去把图片对象生成二级制数据流
        const imagePacker = image.createImagePacker();
        // 图片是否压缩
        const arrayBuffer = await imagePacker.packToData(pixelMap, { format: "image/jpeg", quality: 98 });
        // 3. 借助fileIo读写文件
        // 3.1 获取上下文
        const ctx = getContext(this);
        // 3.2 获取沙箱中存图的路径
        const imagePath = ctx.cacheDir + '/' + Date.now() + '.jpeg';
        // 3.3 以 创建 或 读写 的模式打开文件(没有则创建并打开, 有则打开)
        const file = fileIo.openSync(imagePath, fileIo.OpenMode.CREATE | fileIo.OpenMode.READ_WRITE);
        // 3.4 同步写入二级制数据流到文件中
        fileIo.writeSync(file.fd, arrayBuffer);
        // 3.5 同步去关闭文件
        fileIo.closeSync(file.fd);

        // 4. 把沙箱中的文件写入相册
        // 4.1 获取资源文件的uri地址
        const imgUrl = fileUri.getUriFromPath(imagePath);
        // 4.2 进行图片资产变更(私有->公有)
        const assetChangeRequest = photoAccessHelper.MediaAssetChangeRequest.createImageAssetRequest(ctx, imgUrl);
        // 4.3 提交媒体变更请求
        // 4.3.1 获取相册管理模块的实例
        const phAccessHelper = photoAccessHelper.getPhotoAccessHelper(ctx);
        // 4.3.2 调用变更方法
        await phAccessHelper.applyChanges(assetChangeRequest);

        promptAction.showToast({ message: '图片写入相册成功' });
    }

    build() {
        Stack({ alignContent: Alignment.Center }) {
            Column({ space: 30 }) {
                QRCode('这里可以传(链接、参数)生成二维码')
                   .width(160)
                   .height(160)
                   .alignSelf(ItemAlign.Center);
                Text(this.shareInfo)
                   .fontSize(18)
                   .fontColor('#666')
                   .lineHeight(28)
                   .width('80%')
                   .textAlign(TextAlign.Center)
                   .margin({ top: 50 });

                Row({ space: 20 }) {
                    SaveButton({
                        icon: SaveIconStyle.FULL_FILLED,
                        text: SaveDescription.SAVE_IMAGE,
                        buttonType: ButtonType.Normal
                    })
                       .width(160)
                       .height(50)
                       .fontSize(18)
                       .fontWeight(FontWeight.Medium)
                       .backgroundColor('#007aff')
                       .fontColor(Color.White)
                       .borderRadius(25)
                       .onClick((event: ClickEvent, result: SaveButtonOnClickResult) => {
                            if (result === SaveButtonOnClickResult.SUCCESS) {
                                this.saveImage();
                                this.controller.close();
                            } else {
                                promptAction.showToast({ message: '授权失败' });
                            }
                        });
                }
               .margin({ bottom: 50 });
            }
           .width('90%')
           .padding({ top: 40, bottom: 40, left: 20, right: 20 })
           .backgroundColor(Color.White)
           .borderRadius(16)
           .alignItems(HorizontalAlign.Center);
        }
       .id('IdStr')
       .width('100%')
       .height('100%')
       .backgroundColor('#f4f4f9');
    }
}

此部分代码实现了自定义弹窗的逻辑和布局,是页面分享功能的核心区域。

  • 引入模块:导入多个模块,包括用于组件快照的 @kit.ArkUI 中的 componentSnapshot 和提示框的 promptAction,用于图片处理的 @kit.ImageKit,用于文件操作的 @ohos.file.fs@ohos.file.fileuri,以及用于相册管理的 @kit.MediaLibraryKit 中的 photoAccessHelper
  • saveImage 方法
    • 截图生成:使用 componentSnapshot.get('IdStr') 获取指定组件的截图,生成 pixelMap
    • 图片压缩:通过 image.createImagePacker() 创建图片打包器,并调用 packToData 方法将 pixelMap 压缩为 JPEG 格式的二进制数据流 arrayBuffer
    • 文件操作:获取应用上下文,得到沙箱缓存路径,创建并打开临时文件,将二进制数据流同步写入文件后关闭文件。
    • 相册写入:获取临时文件的 URI 地址,创建图片资产变更请求,通过相册管理模块实例提交变更请求,将图片保存到相册。最后通过提示框告知用户图片保存成功。
  • build 方法
    • 布局构建:通过 StackColumnRow 构建弹窗布局,包含二维码组件、分享文本和保存按钮。
    • 按钮交互:点击保存按钮,根据点击结果执行相应操作。若授权成功,调用 saveImage 方法保存图片并关闭弹窗;若授权失败,提示用户授权失败。

六、详细分析

  1. 根据组件 ID 生成截图对象:利用 componentSnapshot 这个组件快照生成工具,通过 get 方法并传入唯一 ID(IdStr),为指定组件拍摄 “快照”,生成截图对象。
  2. 将生成的截图对象转换为二进制数据流
    • 创建图片编码器实例对象:调用 image.createImagePacker() 创建图片编码器实例,就像准备好了一个专门处理图片的 “工具”。
    • 配置并转换:调用实例的 packToData 方法,传入截图对象 pixelMap,并配置输出图片格式(format)为 image/jpeg,压缩质量(quality)为 98,将截图对象转换为二进制数据流 arrayBuffer
  3. 文件的读写操作
    • 获取上下文与路径:使用 getContext(this) 获取上下文,进而得到沙箱缓存路径 ctx.cacheDir,并结合当前时间生成唯一文件名,如 ctx.cacheDir + '/' + Date.now() + '.jpeg'
    • 打开或创建文件:以 CREATE(创建文件)和 READ_WRITE(读写权限)模式通过 fileIo.openSync 打开或创建文件,确保文件可用于存储截图。
    • 写入和关闭文件:通过 fileIo.writeSync 将二进制数据流写入文件,再用 fileIo.closeSync 关闭文件,完成文件操作。
  4. 调用权限与相册写入
    • 临时权限按钮:在项目中使用 SaveButton 作为临时权限管理按钮。虽然方便,但在大型项目开发中,建议配置全局权限管理,并在调用时动态检查授权情况。
    • 获取资源地址与变更请求:通过 fileUri.getUriFromPath 获取存入沙箱的资源文件的 URI 地址,然后利用 photoAccessHelper.MediaAssetChangeRequest.createImageAssetRequest 创建图片资产变更请求,将私有文件转变为公有资产。
    • 提交变更请求:获取相册管理模块实例 phAccessHelper,调用 applyChanges 方法提交媒体变更请求,将图片保存到相册。

核心点总结

  • 截图与压缩:依靠 componentSnapshotimage.createImagePacker() 实现界面截图生成与图片压缩编码。
  • 文件操作:运用 fileIo 模块完成沙箱文件的创建、写入和关闭操作。
  • 相册管理:借助 photoAccessHelper 实现将图片从应用私有空间移动到相册公共空间。
  • 用户交互:通过 SaveButton 处理用户授权,并使用 promptAction.showToast 提供操作反馈。
  • 二维码生成:使用 QRCode 组件实现二维码的生成与展示。

核心实现步骤精要

1、动态截图与压缩(关键技术点)

// 捕获组件快照  
const pixelMap = await componentSnapshot.get('IdStr');  
// 压缩为 JPEG 二进制流  
const imagePacker = image.createImagePacker();  
const arrayBuffer = await imagePacker.packToData(pixelMap, {  
    format: "image/jpeg",  
    quality: 98 // 压缩质量(1 - 100)  
});  

2、沙箱文件操作(避坑指南)

// 获取沙箱缓存路径  
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);  

3、相册写入(系统级交互)

// 转换 URI 并提交媒体变更请求  
const imgUrl = fileUri.getUriFromPath(imagePath);  
const assetChangeRequest = photoAccessHelper.MediaAssetChangeRequest.createImageAssetRequest(ctx, imgUrl);  
const phAccessHelper = photoAccessHelper.getPhotoAccessHelper(ctx);  
await phAccessHelper.applyChanges(assetChangeRequest);  

功能模块与技术方案总结

功能模块 技术方案 核心 API / 组件
界面截图生成 组件快照捕获 componentSnapshot.get()
图片压缩编码 ImageKit 图片处理 image.createImagePacker()
文件存储 沙箱文件 I/O 操作 fileIo.openSync() / fileIo.writeSync()
相册写入 媒体库资源管理 photoAccessHelper.MediaAssetChangeRequest
用户交互 权限按钮与反馈 SaveButton / promptAction.showToast()
二维码生成 图形组件 QRCode()

七、总结

核心结论

通过本次开发实践,我们成功在 Harmony OS NEXT / 5.0 / API 12+ 版本上实现了页面分享至相册的功能。该功能综合运用了组件快照捕获、图片压缩处理、沙箱文件操作以及媒体库资源管理等多种技术,为用户提供了便捷的分享体验。

延伸学习

对于想要深入学习的开发者,可以进一步探索:

  • 权限管理优化:在大型项目中,如何更好地配置全局权限管理,动态检查权限状态,提升应用的安全性和稳定性。
  • 图片处理进阶:研究 ImageKit 中更多的图片处理功能,如图片裁剪、添加水印等,丰富分享图片的内容和形式。
  • 用户体验提升:优化界面设计,例如增加动画效果,让分享过程更加流畅和有趣,提升用户满意度。

关于 Harmony OS 开发的更多技巧和实践经验,我会在后续博客中持续分享,感兴趣的话欢迎关注。对于本文介绍的页面分享功能开发,你有什么疑问或者想法吗?欢迎随时交流。

Logo

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

更多推荐