【HarmonyOS Next】鸿蒙中自定义弹框OpenCustomDialog、CustomDialog与DialogHub的区别详解
一、三者的区别与关系
1. 官方迭代过程为 :
CustomDialog = 》 OpenCustomDialog = 》 DialogHub
迭代过程表明,弹框的调用越来越便捷,与UI解耦,最终达到在纯逻辑中使用自定义弹出,弹框内容更新和生命周期可控,写法简洁。
2.CustomDialog的用法:
首先需要创建@CustomDialog装饰的自定义弹框布局,CustomDialogController来实现弹窗弹出和关闭。
dart
@CustomDialog
struct 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
@Component
struct 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的解耦。
dart
@Builder
function 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框架就会加载显示。
dart
@Builder
function 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层级。
dart
import { window } from '@kit.ArkUI';
import { BusinessError } from '@kit.BasicServicesKit';
@Entry
@Component
struct 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)
}
}
三、多弹框源码示例:
dart
import {
DialogHub
} from "@hadss/dialoghub"
import { ComponentContent } from "@kit.ArkUI";
import { BusinessError } from "@kit.BasicServicesKit";
@CustomDialog
struct 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)
}
}
@Builder
function ComponentContentBuildText() {
Column() {
Text("测试数据")
.fontSize(50)
.fontWeight(FontWeight.Bold)
.margin({ bottom: 36 })
}.backgroundColor('#FFF0F0F0')
}
/**
* 弹框测试页
*/
@Entry
@Component
struct 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%"
})
}
}
dart
{
"name": "entry",
"version": "1.0.0",
"description": "Please describe the basic information.",
"main": "",
"author": "",
"license": "",
"dependencies": {
"@hadss/dialoghub": "^1.0.0-rc.1"
}
}