写点什么

鸿蒙应用示例:基于 promptAction 封装全局弹窗工具类

作者:zhongcx
  • 2024-10-12
    广东
  • 本文字数:5579 字

    阅读完需:约 18 分钟

源码下载:https://download.csdn.net/download/zhongcongxu01/89826921


​随着 HarmonyOS 的不断发展和完善,开发者们在构建应用时有了更多选择和灵活性。其中,promptAction 是一个非常有用的 API,允许开发者创建全局的弹窗,从而增强应用的用户体验。然而,在使用 promptAction 时,可能会遇到一些挑战,如如何正确地封装弹窗逻辑,确保弹窗的稳定性和易用性。

本文将详细介绍如何在 HarmonyOS 应用开发中,使用 promptAction API 封装一个全局弹窗工具类,并探讨一些常见问题及其解决方案。


起因

在开发过程中,我们发现使用 promptAction 创建全局弹窗时存在几个关键问题:

1. @Builder 函数中的参数要求:在 @Builder 函数中,必须要有参数,即使不使用也会导致应用崩溃。这种情况下,即使是使用 try-catch 也不能捕获异常。

2. 参数类型限制:对于 new ComponentContent(...) 的第三个参数,必须是 Object 或者类接口,而不能是基本数据类型(如 string)。如果传入基本数据类型,虽然编译时不会报错,但在运行时会引发错误 OpenCustomDialog args error code is 401。


工具类设计思路

为了解决上述问题,我们设计了一套工具类,以简化弹窗的创建、显示和关闭过程。

1. 封装弹窗操作:MyPromptActionUtil 类封装了弹窗的基本操作,包括创建、显示和关闭。

2. 生成唯一标识:MyPromptInfo 类用于生成每个弹窗的唯一标识符 dialogID,便于后续的事件监听和管理。

3. 配置弹窗属性:通过 MyPromptActionUtil 类的链式调用,可以轻松配置弹窗的各种属性,如对齐方式、是否允许侧滑关闭等。


使用示例

【工具类】src/main/ets/utils/MyPromptActionUtil.ets

import { ComponentContent, PromptAction } from '@kit.ArkUI';import { BusinessError } from '@kit.BasicServicesKit';// MyPromptInfo 类用于生成唯一的 dialogIDexport class MyPromptInfo {  public dialogID: string
constructor() { this.dialogID = this.generateRandomString(10) } // 生成指定长度的随机字符串 generateRandomString(length: number): string { const characters = 'abcdefghijklmnopqrstuvwxyz0123456789'; let result = '';
for (let i = 0; i < length; i++) { const randomIndex = Math.floor(Math.random() * characters.length); result += characters.charAt(randomIndex); }
return result; }}// MyPromptActionUtil 类用于封装弹窗操作export class MyPromptActionUtil<T extends MyPromptInfo> { private uiContext: UIContext; private promptAction: PromptAction; private contentNode: ComponentContent<T> | undefined; private wrapBuilder: WrappedBuilder<[T]>; private t: T; private isModal: boolean = true; private alignment: DialogAlignment = DialogAlignment.Center; private isSwipeBackEnabled: boolean = true; private isMaskTapToCloseEnabled: boolean = true; public dialogID: string
constructor(uiContext: UIContext, wrapBuilder: WrappedBuilder<[T]>, t: T) { this.uiContext = uiContext; this.promptAction = uiContext.getPromptAction(); this.wrapBuilder = wrapBuilder; this.t = t; this.dialogID = t.dialogID }
setSwipeBackEnabled(isSwipeBackEnabled: boolean) { this.isSwipeBackEnabled = isSwipeBackEnabled; return this; }
setMaskTapToCloseEnabled(isMaskTapToCloseEnabled: boolean) { this.isMaskTapToCloseEnabled = isMaskTapToCloseEnabled return this; }
setAlignment(alignment: DialogAlignment) { this.alignment = alignment; return this; }
setModal(isModal: boolean) { this.isModal = isModal; return this; }
onDidAppear(callback: () => void) { this.onDidAppearCallback = callback; return this; }
onDidDisappear(callback: () => void) { this.onDidDisappearCallback = callback; return this; }
onWillAppear(callback: () => void) { this.onWillAppearCallback = callback; return this; }
onWillDisappear(callback: () => void) { this.onWillDisappearCallback = callback; return this; }
private onDidAppearCallback?: () => void; private onDidDisappearCallback?: () => void; private onWillAppearCallback?: () => void; private onWillDisappearCallback?: () => void;
closeCustomDialog() { if (this.contentNode) { this.promptAction.closeCustomDialog(this.contentNode); } return this; } // 显示自定义弹窗 showCustomDialog() { try { if (!this.contentNode) { this.contentNode = new ComponentContent(this.uiContext, this.wrapBuilder, this.t); } this.promptAction.openCustomDialog(this.contentNode, { // 打开自定义弹窗 alignment: this.alignment, isModal: this.isModal, showInSubWindow: false, maskRect: { x: 0, y: 0, width: '100%', height: '100%' }, onWillDismiss: (dismissDialogAction: DismissDialogAction) => { //弹窗响应 console.info("reason" + JSON.stringify(dismissDialogAction.reason)) console.log("dialog onWillDismiss") if (dismissDialogAction.reason == 0 && this.isSwipeBackEnabled) { //手势返回时,关闭弹窗。 this.promptAction.closeCustomDialog(this.contentNode) } if (dismissDialogAction.reason == 1 && this.isMaskTapToCloseEnabled) { this.promptAction.closeCustomDialog(this.contentNode) } }, onDidAppear: this.onDidAppearCallback ? this.onDidAppearCallback : () => { }, onDidDisappear: this.onDidDisappearCallback ? this.onDidDisappearCallback : () => { }, onWillAppear: this.onWillAppearCallback ? this.onWillAppearCallback : () => { }, onWillDisappear: this.onWillDisappearCallback ? this.onWillDisappearCallback : () => { }, }); } catch (error) {// 错误处理 let message = (error as BusinessError).message; let code = (error as BusinessError).code; console.error(`OpenCustomDialog args error code is ${code}, message is ${message}`); } return this; }}
复制代码

【使用示例】src/main/ets/dialog/MyDialog_1.ets

@Componentexport struct MyDialog_1 {  @Prop dialogID: string  @State title: string = '加载中...'
build() { Stack() { Column() { LoadingProgress() .color(Color.White).width(100).height(100) Text(this.title) .fontSize(18).fontColor(0xffffff).margin({ top: 8 }) .visibility(this.title ? Visibility.Visible : Visibility.None) } } .onClick(() => { getContext(this).eventHub.emit(this.dialogID, "关闭弹窗") }) .width(180) .height(180) .backgroundColor(0x88000000) .borderRadius(10) .shadow({ radius: 10, color: Color.Gray, offsetX: 3, offsetY: 3 }) }}
复制代码

src/main/ets/dialog/MyDialog_2.ets

@Componentexport struct MyDialog_2 {  @Prop dialogID: string  @Prop message: string
aboutToAppear(): void { setTimeout(() => { console.info(`this.dialogID:${this.dialogID}`) getContext(this).eventHub.emit(this.dialogID, "关闭弹窗") }, 2000) }
build() { Column() { Text(this.message) .fontSize('36lpx') .fontColor(Color.White) .backgroundColor("#B2000000") .borderRadius(8) .constraintSize({ maxWidth: '80%' }) .padding({ bottom: '28lpx', left: '60lpx', right: '60lpx', top: '28lpx' })
}
}}
复制代码

src/main/ets/pages/Page23.ets

import { MyPromptActionUtil, MyPromptInfo } from '../utils/MyPromptActionUtil'// MyDialog_1 和 MyDialog_2 类用于定义弹窗内容import { MyDialog_1 } from '../dialog/MyDialog_1'import { MyDialog_2 } from '../dialog/MyDialog_2'
/*【注意事项】必须有参数,没参数在运行时会闪退,报错内容 Error message:is not callableSourceCode: (parent ? parent : this).observeComponentCreation2((elmtId, isInitialRender) => { */// @Builder// function test_1() {// MyDialog_1()// }

@Builderfunction test_1(data: MyPromptInfo) { MyDialog_1({ dialogID: data.dialogID })}

@Builderfunction test_2(data: MyDialog_2_Info) { MyDialog_2({ dialogID: data.dialogID, message: data.message })}
class MyDialog_2_Info extends MyPromptInfo { public message: string = ""
constructor(message: string) { super() this.message = message }}
@Entry@Componentstruct Page23 { myDialog_1: MyPromptActionUtil<MyPromptInfo> | undefined myDialog_2: MyPromptActionUtil<MyDialog_2_Info> | undefined
build() { Column() { Button('显示Loading').onClick(() => { this.showLoadingDialog() }) Button('显示Toast').onClick(() => { this.showToast(`随机数:${this.getRandomInt(1, 100)}`) }) } .height('100%') .width('100%') }
getRandomInt(min: number, max: number): number { min = Math.ceil(min); max = Math.floor(max); return Math.floor(Math.random() * (max - min + 1)) + min; }
// 显示 Loading 弹窗 showLoadingDialog() { if (!this.myDialog_1) { this.myDialog_1 = new MyPromptActionUtil<MyPromptInfo>(this.getUIContext(), wrapBuilder(test_1), new MyPromptInfo()) .setModal(true)//true:存在黑色半透明蒙层,false:没有蒙层 .setSwipeBackEnabled(true)//true:侧滑允许关闭弹窗 .setMaskTapToCloseEnabled(true)//true:点击半透明蒙层可关闭弹窗【注:如果setModal(false),那么就没有蒙层,所以点击对话框外也没有响应事件,也就是这里设置了也没效果,并且事件会穿透】 .setAlignment(DialogAlignment.Center)//对齐方式 .onDidAppear(() => { console.info('当对话框完全呈现出来,即完成打开动画后') }) .onDidDisappear(() => { console.info('当对话框完全消失,即关闭动画结束后') }) .onWillAppear(() => { console.info('在对话框的打开动画开始之前调用的回调函数') getContext(this).eventHub.on(this.myDialog_1?.dialogID, (data: string) => { //监听结果 if (data == '关闭弹窗') { this.myDialog_1?.closeCustomDialog() } }) }) .onWillDisappear(() => { console.info('在对话框的关闭动画开始之前调用的回调函数') getContext(this).eventHub.off(this.myDialog_1?.dialogID) }) } this.myDialog_1.showCustomDialog() }
// 显示 Toast 弹窗 showToast(toastInfo: string) { getContext(this).eventHub.off(this.myDialog_2?.dialogID) this.myDialog_2?.closeCustomDialog() //如果之前有的toast对话框,并且正在显示,则先关闭toast提示 this.myDialog_2 = new MyPromptActionUtil<MyDialog_2_Info>(this.getUIContext(), wrapBuilder(test_2), new MyDialog_2_Info(toastInfo)) .setModal(false)//true:存在黑色半透明蒙层,false:没有蒙层 .setSwipeBackEnabled(false)//true:侧滑允许关闭弹窗 .setMaskTapToCloseEnabled(true)//true:点击半透明蒙层可关闭弹窗【注:如果setModal(false),那么就没有蒙层,所以点击对话框外也没有响应事件,也就是这里设置了也没效果,并且事件会穿透】 .setAlignment(DialogAlignment.Center) .onWillAppear(() => { console.info('在对话框的打开动画开始之前调用的回调函数') getContext(this).eventHub.on(this.myDialog_2?.dialogID, (data: string) => { //监听结果 if (data == '关闭弹窗') { this.myDialog_2?.closeCustomDialog() } }) }) .onWillDisappear(() => { console.info('在对话框的关闭动画开始之前调用的回调函数') getContext(this).eventHub.off(this.myDialog_2?.dialogID) }) .showCustomDialog() }}
复制代码


用户头像

zhongcx

关注

还未添加个人签名 2024-09-27 加入

还未添加个人简介

评论

发布
暂无评论
鸿蒙应用示例:基于 promptAction 封装全局弹窗工具类_zhongcx_InfoQ写作社区