鸿蒙全局自定义弹出框、页面弹出框实践
今天和大家分享一下我自己封装的全局通用确认弹窗,欢迎大家指点。
官方文档:弹窗概述-使用弹窗-UI开发 (ArkTS声明式开发范式)-ArkUI(方舟UI框架)-应用框架 - 华为HarmonyOS开发者
1. 弹窗类型概述
鸿蒙系统中的弹窗主要分为三种类型:
-
基础自定义弹出框 (customDialog):与UI组件紧密耦合的传统弹窗方式
-
不依赖UI组件的全局自定义弹出框:可跨页面显示的独立弹窗
-
页面级弹出框:局限于当前页面范围内的弹窗组件
封装重点说明
由于 customDialog 与 UI 组件的高耦合特性,在实际项目开发中我们重点关注以下两类:
1.1 全局自定义弹出框
-
不依赖于特定页面或组件的独立弹窗
-
可在应用任意位置调用显示
-
适合用于全局提示、通用确认框等场景
1.2 页面级弹出框
-
作用域限定在当前页面内
-
与页面生命周期关联
-
适用于页面内的特定功能提示或操作确认
2. 全局自定义弹出框封装
这里演示实际开发中常用的通用确认弹出框的封装。
首先对自定义弹窗做一个通用的封装
▼ts复制代码import { ComponentContent, LevelMode, PromptAction, promptAction, window } from '@kit.ArkUI'; import { util } from '@kit.ArkTS'; import { hilog } from '@kit.PerformanceAnalysisKit'; const DOMAIN = 0x0000; /** * 自定义弹窗工具类(基础工具类) */ class BaseCustomDialogUtil { /** * 上下文 */ private context: Context | undefined = undefined; /** * 存储打开的弹窗 ComponentContent */ private dialogMap: Map<string, ComponentContent<Object>> = new Map(); /** * openCustomDialog 默认弹窗配置 */ private defaultCustomDialogOptions: promptAction.BaseDialogOptions = { alignment: DialogAlignment.Center, autoCancel: false, levelMode: LevelMode.EMBEDDED, dialogTransition: TransitionEffect.move(TransitionEdge.BOTTOM) .animation({ duration: 300, curve: Curve.Smooth }) }; /** * 设置上下文,使用该工具类前需要设置上下文 * @param context */ setContext(context?: Context) { this.context = context; } /** * 获取上下文 * @returns */ getContext(): Context { return this.context || getContext(); } /** * 打开自定义弹窗 * @param model 实体类型 * @param customBuilder 自定义弹窗构造器 * @param options 弹窗配置,不传使用默认配置 * @returns 弹窗唯一标识 */ async openCustomDialog<T extends Object>(model: T, customBuilder: WrappedBuilder<[T]>, options?: promptAction.BaseDialogOptions): Promise<string> { const name = `custom_dialog_${util.generateRandomUUID()}`; try { const win: window.Window = await window.getLastWindow(this.getContext()); const uiContext: UIContext = win.getUIContext(); const promptAction: PromptAction = uiContext.getPromptAction(); const contentNode: ComponentContent<T> = new ComponentContent(uiContext, customBuilder, model); // 将 contentNode 添加到 map 中 this.dialogMap.set(name, contentNode); const useOptions = options || this.defaultCustomDialogOptions; promptAction.openCustomDialog(contentNode, useOptions).catch(() => { hilog.error(DOMAIN, 'BaseCustomDialogUtil', 'CustomDialogUtil promptAction openCustomDialog failed'); }); return name; } catch (e) { hilog.error(DOMAIN, 'BaseCustomDialogUtil', 'CustomDialogUtil openCustomDialog failed, reason is: ', JSON.stringify(e)); return ''; } } /** * 关闭自定义弹窗 * @param dialogName 如果传了,关闭指定的弹窗,否则关闭所有弹窗 */ async closeCustomDialog(dialogName?: string) { try { const win: window.Window = await window.getLastWindow(this.getContext()); const uiContext: UIContext = win.getUIContext(); const promptAction: PromptAction = uiContext.getPromptAction(); if (dialogName) { // 关闭指定弹窗 promptAction.closeCustomDialog(this.dialogMap.get(dialogName)).catch(() => { hilog.error(DOMAIN, 'BaseCustomDialogUtil', 'CustomDialogUtil promptAction closeCustomDialog failed'); }); // 删除 map 中对应的 contentNode this.dialogMap.delete(dialogName); return; } // 关闭所有弹窗 this.dialogMap?.forEach((value: ComponentContent<Object>, key: string) => { promptAction.closeCustomDialog(value).catch(() => { hilog.error(DOMAIN, 'BaseCustomDialogUtil', 'CustomDialogUtil promptAction closeCustomDialog failed'); }); }); // 清空 dialogMap this.dialogMap.clear(); } catch (e) { hilog.error(DOMAIN, 'BaseCustomDialogUtil', 'CustomDialogUtil closeCustomDialog failed, reason is: ', JSON.stringify(e)); } } } export const baseCustomDialogUtil = new BaseCustomDialogUtil();
在 EntryAbility 中设置上下文
▼ts复制代码onWindowStageCreate(windowStage: window.WindowStage): void { baseCustomDialogUtil.setContext(this.context); // 设置context // 其他代码省略... }
搭建通用确认弹框的UI
利用 @Component 装饰器自定义通用确认弹出框的ui组件。
以下是我封装的一个通用确认弹窗组件,支持6种格式。
- 通用通知类型

- 正常文本内容类型

- 富文本内容类型

- 自定义内容类型

- 协议弹窗类型

- 倒计时按钮类型弹窗

▼ts复制代码import { CustomRichText } from '.'; import { CustomButtonModel, RichTextAlignWay } from '../viewmodels'; @Component export struct CommonConfirm { /** * 标题 */ title: string = ''; /** * 内容 */ content: string = ''; /** * 内容是否为富文本 */ isRichText: boolean = false; /** * 富文本内容对齐方式 */ richTextAlign: RichTextAlignWay = 'center'; /** * 按钮列表 */ @Prop buttonList: CustomButtonModel[] = []; /** * 定时器列表 */ private timerList: number[] = []; /** * 默认内边距 上右下左 padding */ trblPadding: number[] = [20, 24, 20, 24]; /** * 默认内容对齐方式 */ defaultTextAlign: TextAlign = TextAlign.Center; /** * 默认内容颜色 */ defaultContentColor: ResourceColor = Color.Black; /** * 自定义内容 Builder,如果存在,则不使用 content 内容 */ @BuilderParam customContentBuilder?: CustomBuilder; /** * 扩展builder,比如说协议之类的 */ @BuilderParam extContentBuilder?: CustomBuilder; aboutToAppear(): void { this.setBtnDownHandle(); } aboutToDisappear(): void { this.clearBtnDownHandle(); } /** * 如果按钮存在倒计时,则设置倒计时 */ setBtnDownHandle() { this.timerList = Array(this.buttonList.length).fill(-1); this.buttonList.forEach((item: CustomButtonModel, index: number) => { if (item.needCountDown) { this.timerList[index] = setInterval(() => { if (item.countDown <= 0) { clearInterval(this.timerList[index]); return; } this.buttonList[index] = new CustomButtonModel({ text: item.text, fc: item.fc, fs: item.fs, fw: item.fw, lh: item.lh, callback: item.callback, needCountDown: item.needCountDown, countDown: item.countDown - 1, countDownFc: item.countDownFc }); item.countDown = item.countDown - 1; }, 1000); } }); } /** * 清除倒计时定时器 */ clearBtnDownHandle() { this.timerList.forEach((item: number) => { if (item !== -1) { clearInterval(item); } }); } build() { Column() { Column({ space: 12 }) { if (this.title) { Text(this.title) .textCommonStyles(18, Color.Black, 600, 26) .onClick(() => { const controller: PromptActionDialogController = this.getDialogController(); if (controller) { controller.close(); } }); } if (this.content) { if (this.isRichText) { // 富文本 CustomRichText({ content: this.content, richContentTextAlign: this.richTextAlign }); } else { Text(this.content) .width('100%') .textCommonStyles(16, this.defaultContentColor, 400, 20) .textAlign(this.defaultTextAlign); } } // 自定义builder if (this.customContentBuilder) { this.customContentBuilder(); } } .padding({ top: this.trblPadding[0], right: this.trblPadding[1], bottom: this.trblPadding[2], left: this.trblPadding[3] }); if (this.extContentBuilder) { Column() { this.extContentBuilder(); } .padding({ left: this.trblPadding[3], right: this.trblPadding[1], bottom: 12 }); } Row() { ForEach(this.buttonList, (item: CustomButtonModel, index: number) => { Button({ type: ButtonType.Normal }) { Text((item.needCountDown && item.countDown) ? `${item.text} ${item.countDown}s` : item.text) .textCommonStyles(item.fs, (item.needCountDown && item.countDown > 0) ? (item.countDownFc || item.fc) : item.fc, item.fw, item.lh); } .layoutWeight(1) .height(52) .align(Alignment.Center) .borderRadius(0) .align(Alignment.Center) .backgroundColor(item.bgColor) .border({ width: { right: index === (this.buttonList.length - 1) ? 0 : 0.5 }, color: '#E4E5E6' }) .enabled(!!(!item.needCountDown || (item.needCountDown && !item.countDown))) .onClick(() => { item.callback(); }); }); } .width('100%') .border({ width: { top: 0.5 }, color: '#E4E5E6' }); } .width(300) .borderRadius(12) .clip(true) .backgroundColor(Color.White); } } @Extend(Text) function textCommonStyles(fs?: number, fc?: ResourceColor, fw?: FontWeight, lh?: number) { .fontSize(fs || 16) .fontColor(fc || Color.Black) .lineHeight(lh || 16) .fontWeight(fw || 400); }
富文本渲染组件
▼ts复制代码import { webview } from '@kit.ArkWeb'; import { RichTextAlignWay } from '../viewmodels'; @Component export struct CustomRichText { content: string = ''; richContentTextAlign: RichTextAlignWay = 'center'; controller = new webview.WebviewController(); build() { Web({ src: $rawfile('richText.html'), controller: this.controller }) .height(0) .layoutMode(WebLayoutMode.FIT_CONTENT) .javaScriptAccess(true) .domStorageAccess(true) .metaViewport(true) // 等页面加载完毕,去执行web页面的函数 .onPageEnd(() => { if (this.content) { this.controller.runJavaScript(`writeHtml(\`${this.content}\`,\`${this.richContentTextAlign}\`)`); } }); } }
3. 页面级弹出框
页面级弹出框同样也支持以上通用确认弹出框的效果,并且实现了支持底部弹出的页面级自定义抽屉。
▼ts复制代码import { CommonConfirm } from '../components'; import { GlobalDialogManager, NavigationDialogModel } from '../utils'; import { CustomButtonModel, CustomDialogBuilderModel } from '../viewmodels'; @Component export struct NavigationDialogView { @Consume stackPath: NavPathStack; param: NavigationDialogModel = new NavigationDialogModel(); @State confirmDialogModel: CustomDialogBuilderModel = new CustomDialogBuilderModel(); @BuilderParam customAllContentBuilder?: CustomBuilder; @State radius: number = 10; @State maskColor: ResourceColor = 'rgba(0,0,0,0.4)'; @State canSlideExit: boolean = false; @State show: boolean = false; @State isCenter: boolean = false; async aboutToAppear() { if (!this.param) { this.stackPath.pop(); return; } this.paramHandle(this.param); } paramHandle(model: NavigationDialogModel) { if (model.canSlideExit) { this.canSlideExit = true; } if (model.align === 'center') { this.isCenter = true; } if (model.customAllContentBuilder) { this.customAllContentBuilder = model.customAllContentBuilder; if (model.radius) { this.radius = model.radius; } if (model.maskColor) { this.maskColor = model.maskColor; } this.show = true; return; } const obj = new CustomDialogBuilderModel(); obj.title = model.title || ''; obj.content = model.content || ''; obj.isRichText = model.isRichText || false; obj.richTextAlign = model.richTextTextAlign || 'center'; obj.defaultContentColor = model.defaultContentColor || '#727376'; obj.defaultTextAlign = model.defaultTextAlign; obj.customContentBuilder = model.customContentBuilder; obj.extContentBuilder = model.extContentBuilder; if (model.hideLeftBtn) { obj.buttonList = [ new CustomButtonModel({ text: model.rightBtn || '知道了', fc: model.rightBtnColor || '#FA940F', fs: 18, fw: 500, bgColor: model.rightBtnBg || '#FFF', callback: () => { model.rightCallback?.(); GlobalDialogManager.closeNavigationDialog(); } }) ]; } else { obj.buttonList = [ new CustomButtonModel({ text: model.leftBtn || '取消', fs: 16, lh: 24, fw: 400, fc: model.leftBtnColor || '#727376', callback: () => { model.leftCallback?.(); GlobalDialogManager.closeNavigationDialog(); } }), new CustomButtonModel({ text: model.rightBtn || '确定', fs: 16, lh: 24, fw: 500, fc: model.rightBtnColor || '#FA940F', needCountDown: model.rightNeedCountDown, countDown: model.rightCountDown, countDownFc: model.rightCountDownFc, callback: () => { model.rightCallback?.(); } }) ]; } this.confirmDialogModel = obj; } build() { NavDestination() { Column() { if (this.customAllContentBuilder) { Blank().layoutWeight(1) .onClick(() => { if (!this.isCenter) { GlobalDialogManager.closeNavigationDialog(); } }); Column() { this.customAllContentBuilder(); } .borderRadius({ topRight: this.radius, topLeft: this.radius, bottomLeft: this.isCenter ? this.radius : 0, bottomRight: this.isCenter ? this.radius : 0 }) .clip(true); if (this.isCenter) { Blank().layoutWeight(1); } } else { CommonConfirm({ title: this.confirmDialogModel.title, content: this.confirmDialogModel.content, isRichText: this.confirmDialogModel.isRichText, richTextAlign: this.confirmDialogModel.richTextAlign, buttonList: this.confirmDialogModel.buttonList, defaultTextAlign: this.confirmDialogModel.defaultTextAlign, defaultContentColor: this.confirmDialogModel.defaultContentColor, customContentBuilder: this.confirmDialogModel.customContentBuilder, extContentBuilder: this.confirmDialogModel.extContentBuilder }); } } .width('100%') .height('100%') .justifyContent(FlexAlign.Center); } .backgroundColor((this.customAllContentBuilder && !this.show) ? Color.Transparent : this.maskColor) .hideTitleBar(true) .mode(NavDestinationMode.DIALOG) // .systemTransition(this.customAllContentBuilder ? NavigationSystemTransitionType.DEFAULT : // NavigationSystemTransitionType.NONE) .onWillDisappear(() => { if (this.customAllContentBuilder) { this.show = false; } }) .onBackPressed(() => { return !this.canSlideExit; }); } } @Builder export function NavigationDialogViewBuilder(name: string, param: NavigationDialogModel) { NavigationDialogView({ param }); }
页面级抽屉使用
▼ts复制代码@Builder customDrawerBuilder() { Column() { Text('这是页面级抽屉'); } .width('100%') .height(600) .justifyContent(FlexAlign.Center) .backgroundColor('#FFF'); }
▼ts复制代码this.btnItemBuilder('打开页面级抽屉', () => { GlobalDialogManager.showNavigationDialog({ customAllContentBuilder: () => { this.customDrawerBuilder(); }, radius: 20, maskColor: 'rgba(0,0,0,0.8)', canSlideExit: false, }); });
效果:

完整代码地址:https://gitee.com/coderyuyuzi/global-dialog-application.git
项目目录结构如下:
- ets
- components
- CommonConfirm.ets 通用确认弹窗组件ui
- CustomRichText.ets 富文本渲染组件ui
- utils
- BaseCustomDialogUtil.ets 自定义弹窗基础工具类
- GlobalDialogManager.ets 通用确认弹窗统一管理工具类(对外使用)
- viewsmodels
- confirm_model.ets 弹窗类型定义
- views
- NavigationDialogView.ets 页面级弹窗封装
- PrivacyView.ets 隐私协议示例页面
- components
评论
问答助学
相关内容
0个评论
全部评论
点击登录,快来和大家讨论吧~
表情
图片
暂无评论
