源码下载:https://download.csdn.net/download/zhongcongxu01/89826921
随着 HarmonyOS 的不断发展和完善,开发者们在构建应用时有了更多选择和灵活性。其中,promptAction 是一个非常有用的 API,允许开发者创建全局的弹窗,从而增强应用的用户体验。然而,在使用 promptAction 时,可能会遇到一些挑战,如如何正确地封装弹窗逻辑,确保弹窗的稳定性和易用性。
本文将详细介绍如何在 HarmonyOS 应用开发中,使用 promptAction API 封装一个全局弹窗工具类,并探讨一些常见问题及其解决方案。
起因
在开发过程中,我们发现使用 promptAction 创建全局弹窗时存在几个关键问题:
- @Builder 函数中的参数要求:在 @Builder 函数中,必须要有参数,即使不使用也会导致应用崩溃。这种情况下,即使是使用 try-catch 也不能捕获异常。
- 参数类型限制:对于 new ComponentContent(...) 的第三个参数,必须是 Object 或者类接口,而不能是基本数据类型(如 string)。如果传入基本数据类型,虽然编译时不会报错,但在运行时会引发错误 OpenCustomDialog args error code is 401。
工具类设计思路
为了解决上述问题,我们设计了一套工具类,以简化弹窗的创建、显示和关闭过程。
- 封装弹窗操作:MyPromptActionUtil 类封装了弹窗的基本操作,包括创建、显示和关闭。
- 生成唯一标识:MyPromptInfo 类用于生成每个弹窗的唯一标识符 dialogID,便于后续的事件监听和管理。
- 配置弹窗属性:通过 MyPromptActionUtil 类的链式调用,可以轻松配置弹窗的各种属性,如对齐方式、是否允许侧滑关闭等。
使用示例
【工具类】src/main/ets/utils/MyPromptActionUtil.ets
import { ComponentContent, PromptAction } from '@kit.ArkUI';
import { BusinessError } from '@kit.BasicServicesKit';
// MyPromptInfo 类用于生成唯一的 dialogID
export 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
@Component
export 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
@Component
export 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 callable
SourceCode:
(parent ? parent : this).observeComponentCreation2((elmtId, isInitialRender) => {
*/
// @Builder
// function test_1() {
// MyDialog_1()
// }
@Builder
function test_1(data: MyPromptInfo) {
MyDialog_1({ dialogID: data.dialogID })
}
@Builder
function 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
@Component
struct 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()
}
}
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。