写点什么

【HarmonyOS Next】鸿蒙中自定义弹框 OpenCustomDialog、CustomDialog 与 DialogHub 的区别详解

作者:GeorgeGcs
  • 2025-03-24
    上海
  • 本文字数:5634 字

    阅读完需:约 18 分钟

【HarmonyOS Next】鸿蒙中自定义弹框OpenCustomDialog、CustomDialog与DialogHub的区别详解

【HarmonyOS Next】鸿蒙中自定义弹框 OpenCustomDialog、CustomDialog 与 DialogHub 的区别详解

一、三者的区别与关系

1. 官方迭代过程为:CustomDialog = 》 OpenCustomDialog = 》 DialogHub


迭代过程表明,弹框的调用越来越便捷,与 UI 解耦,最终达到在纯逻辑中使用自定义弹出,弹框内容更新和生命周期可控,写法简洁。


2.CustomDialog 的用法:首先需要创建 @CustomDialog 装饰的自定义弹框布局,CustomDialogController 来实现弹窗弹出和关闭。


@CustomDialogstruct CustomDialogUI {  // CustomDialog可直接获取到dialogController  dialogController: CustomDialogController;  // 定义事件回调给外部使用  onClose?: () => void;
build() { Column() { Text('我是内容') .fontSize(20)
Button('Close') .onClick(() => { // 点击关闭弹框 this.dialogController.close(); if (this.onClose) { this.onClose() } }).backgroundColor(Color.White).fontColor(Color.Black) }.height(60).justifyContent(FlexAlign.Center) }}
@Entry@Componentstruct CustomDialogPage { // CustomDialog - CustomDialogController需在@Component内定义初始化。 dialogController: CustomDialogController | null = new CustomDialogController({ builder: CustomDialogUI({ onClose: ()=> { console.info('Callback when the onClose button is clicked') }, }), })
build() { Column() { Button('click me') .onClick(() => { this.dialogController.open() }) }.width('100%').margin({ top: 5 }) }}
复制代码


综上所述,CustomDialog 因为 CustomDialogController 强耦合于 UI,需要在 UI 界面或者自定义 View 中使用 CustomDialogController 控制弹框显示隐藏。无法在纯逻辑类中处理弹框时机的显示。(这种情况下只能想办法发送通知给 UI,UI 再处理回调显示,处理起来麻烦。)致命的问题是,弹框内的 UI 无法动态刷新。需要重新创建渲染。


3.OpenCustomDialog 的用法:对标 CustomDialog 的 CustomDialogController。官方通过将弹框对象实例,放到上下文中,实现在纯逻辑类中也可以调用弹框的显示和隐藏。


将 @CustomDialog 弹框布局内容,放到 ComponentContent 节点对象中,实现弹框 UI 的解耦。


@Builderfunction ComponentContentBuildText() {
Column() { Text("测试数据") .fontSize(50) .fontWeight(FontWeight.Bold) .margin({ bottom: 36 }) }.backgroundColor('#FFF0F0F0')}
// OpenCustomDialog - ComponentContent // 建议整体抽个单例 private contentNode: ComponentContent<Object> = new ComponentContent(this.getUIContext(), wrapBuilder(ComponentContentBuildText));
this.getUIContext().getPromptAction().openCustomDialog(this.contentNode) .then(() => { console.info('UpdateCustomDialog complete.') }) .catch((error: BusinessError) => { let message = (error as BusinessError).message; let code = (error as BusinessError).code; console.error(`onClickOpenCustomDialog args error code is ${code}, message is ${message}`); })
复制代码


DialogHub 的用法:参考:【HarmonyOS Next】鸿蒙应用实现弹框DialogHub详解

二、自定义 View 与 UI 解耦的解决方案:

目前共有三种方式,使用浮层(DialogHub 底层原理),使用 OpenCustomDialog,使用 subWindow。


1.浮层



DialogHub 底层原理。在页面 Page 与弹框层之间,ArkUI 框架有一个浮层。该层通过节点管控(增加,删除)的方式,可以插入自定义 UI。


ComponentContent 可以理解为一个节点内容对象,在其中进行自定义 UI 的界面编写,打包为一个 ComponentContent 节点,添加到浮层上,ArkUI 框架就会加载显示。



@Builderfunction builderOverlay() { Column() {}.focusable(false).width('100%').height('100%').hitTestBehavior(HitTestMode.Transparent)}
private overlayNode : OverlayManager = this.uiContext.getOverlayManager() let componentContent = new ComponentContent( this.uiContext, wrapBuilder<>(builderOverlay) ) this.overlayNode.addComponentContent(componentContent, 0) this.overlayContent.push(componentContent)
复制代码


2.OpenCustomDialog 参考:【HarmonyOS Next】鸿蒙应用弹框和提示气泡详解(一)


3.subWindow 因为三方应用不能使用 FloatWindow,没有悬浮窗。只能通过子窗口 SubWindow 实现独立的自定义 View 层级。


import { window } from '@kit.ArkUI';import { BusinessError } from '@kit.BasicServicesKit';
@Entry@Componentstruct SubWinPage { private TAG: string = "SubWinPage"; private sub_windowClass: window.Window | null = null;
aboutToAppear() { this.showSubWindow() setTimeout(()=>{ try { this.destroySubWindow(); // window.getLastWindow(getContext()).then((win)=>{ // console.error(this.TAG, 'win:' + JSON.stringify(win)); // let height = win.getWindowDecorHeight(); // console.error(this.TAG, 'height:' + height); // })
let windowStage_: window.WindowStage = globalThis.mWindowStage; let win = windowStage_.getMainWindowSync(); let height = win.getWindowDecorHeight(); }catch (e){ console.error(this.TAG, 'e:' + JSON.stringify(e)); } },1000) }
private showSubWindow() { console.log(this.TAG, 'showSubWindow start'); let windowStage_: window.WindowStage = globalThis.mWindowStage; // 1.创建应用子窗口。 if (windowStage_ == null) { console.error(this.TAG, 'Failed to create the subwindow. Cause: windowStage_ is null'); } else { windowStage_.createSubWindow("mySubWindow", (err: BusinessError, data) => { let errCode: number = err.code; if (errCode) { console.error(this.TAG, 'Failed to create the subwindow. Cause: ' + JSON.stringify(err)); return; } this.sub_windowClass = data; console.info(this.TAG, 'Succeeded in creating the subwindow. Data: ' + JSON.stringify(data)); // 2.子窗口创建成功后,设置子窗口的位置、大小及相关属性等。 this.sub_windowClass.moveWindowTo(300, 300, (err: BusinessError) => { let errCode: number = err.code; if (errCode) { console.error(this.TAG, 'Failed to move the window. Cause:' + JSON.stringify(err)); return; } console.info(this.TAG, 'Succeeded in moving the window.'); }); this.sub_windowClass.resize(500, 500, (err: BusinessError) => { let errCode: number = err.code; if (errCode) { console.error(this.TAG, 'Failed to change the window size. Cause:' + JSON.stringify(err)); return; } console.info(this.TAG, 'Succeeded in changing the window size.'); }); // 3.为子窗口加载对应的目标页面。 this.sub_windowClass.setUIContent("pages/SubWinLoadPage", (err: BusinessError) => { let errCode: number = err.code; if (errCode) { console.error(this.TAG, 'Failed to load the content. Cause:' + JSON.stringify(err)); return; } console.info(this.TAG, 'Succeeded in loading the content.'); // 3.显示子窗口。 (this.sub_windowClass as window.Window).showWindow((err: BusinessError) => { let errCode: number = err.code; if (errCode) { console.error(this.TAG, 'Failed to show the window. Cause: ' + JSON.stringify(err)); return; } console.info(this.TAG, 'Succeeded in showing the window.'); }); }); }) } console.log(this.TAG, 'showSubWindow end'); }
destroySubWindow() { // 4.销毁子窗口。当不再需要子窗口时,可根据具体实现逻辑,使用destroy对其进行销毁。 (this.sub_windowClass as window.Window).destroyWindow((err: BusinessError) => { let errCode: number = err.code; if (errCode) { console.error(this.TAG, 'Failed to destroy the window. Cause: ' + JSON.stringify(err)); return; } console.info(this.TAG, 'Succeeded in destroying the window.'); }); }
build() { Column() { Text("点击创建子窗口") .id('SubWinPageHelloWorld') .fontSize(50) .fontWeight(FontWeight.Bold) .onClick(()=>{ this.showSubWindow(); })
Text("点击销毁子窗口") .id('SubWinPageHelloWorld') .fontSize(50) .fontWeight(FontWeight.Bold) .onClick(()=>{ this.destroySubWindow(); }) } .height('100%') .width('100%') .justifyContent(FlexAlign.Center) }}
复制代码

三、多弹框源码示例:

import {  DialogHub} from "@hadss/dialoghub"import { ComponentContent } from "@kit.ArkUI";import { BusinessError } from "@kit.BasicServicesKit";
@CustomDialogstruct CustomDialogUI { // CustomDialog可直接获取到dialogController dialogController: CustomDialogController; // 定义事件回调给外部使用 onClose?: () => void;
build() { Column() { Text('我是内容') .fontSize(20)
Button('Close') .onClick(() => { // 点击关闭弹框 this.dialogController.close(); if (this.onClose) { this.onClose() } }).backgroundColor(Color.White).fontColor(Color.Black) }.height(60).justifyContent(FlexAlign.Center) }}
@Builderfunction ComponentContentBuildText() {
Column() { Text("测试数据") .fontSize(50) .fontWeight(FontWeight.Bold) .margin({ bottom: 36 }) }.backgroundColor('#FFF0F0F0')}
/** * 弹框测试页 */@Entry@Componentstruct DialogTestPage {
// CustomDialog - CustomDialogController需在@Component内定义初始化。 dialogController: CustomDialogController | null = new CustomDialogController({ builder: CustomDialogUI({ onClose: ()=> { console.info('Callback when the onClose button is clicked') }, }), })
// OpenCustomDialog - ComponentContent // 建议整体抽个单例 private contentNode: ComponentContent<Object> = new ComponentContent(this.getUIContext(), wrapBuilder(ComponentContentBuildText));
/** * 统一样式封装 */ @Styles ButtonStyle(){ .width(px2vp(350)) .height(px2vp(200)) .margin({ top: px2vp(66) }) }
/** * 点击显示CustomDialog弹框 【官方不推荐】 */ onClickCustomDialog = ()=>{ this.dialogController?.open() }
/** * 点击显示OpenCustomDialog */ onClickOpenCustomDialog = ()=>{ this.getUIContext().getPromptAction().openCustomDialog(this.contentNode) .then(() => { console.info('UpdateCustomDialog complete.') }) .catch((error: BusinessError) => { let message = (error as BusinessError).message; let code = (error as BusinessError).code; console.error(`onClickOpenCustomDialog args error code is ${code}, message is ${message}`); }) }
/** * 点击显示DialogHub弹框 */ onClickDialogHub = ()=>{ DialogHub.getToast().setTextContent("测试数据").setDuration(2000).build().show(); }

aboutToDisappear() { // 在自定义组件即将析构销毁时将dialogController置空 this.dialogController = null; // 将dialogController置空 }
build() { Column(){ Button("customDialog") .ButtonStyle() .onClick(this.onClickCustomDialog)
Button("openCustomDialog") .ButtonStyle() .onClick(this.onClickOpenCustomDialog)
Button("dialogHub") .ButtonStyle() .onClick(this.onClickDialogHub)
}.size({ width: "100%", height: "100%" }) }}
复制代码


{  "name": "entry",  "version": "1.0.0",  "description": "Please describe the basic information.",  "main": "",  "author": "",  "license": "",  "dependencies": {    "@hadss/dialoghub": "^1.0.0-rc.1"  }}
复制代码


发布于: 刚刚阅读数: 4
用户头像

GeorgeGcs

关注

路漫漫其修远兮,吾将上下而求索。 2024-12-24 加入

历经腾讯,宝马,研究所,金融。 待过私企,外企,央企。 深耕大应用开发领域十年。 OpenHarmony,HarmonyOS,Flutter,H5,Android,IOS。 目前任职鸿蒙应用架构师。 HarmonyOS官方认证创作先锋

评论

发布
暂无评论
【HarmonyOS Next】鸿蒙中自定义弹框OpenCustomDialog、CustomDialog与DialogHub的区别详解_DialogHub_GeorgeGcs_InfoQ写作社区