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

【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"
  }
}
相关推荐
kyriewen4 分钟前
我用Claude Code两天干完了团队两周的排期——周报发出去那一刻我就后悔了
前端·javascript·ai编程
IT_陈寒30 分钟前
JavaScript类型转换把我坑惨了,这破玩意真该早点搞明白
前端·人工智能·后端
用户938515635071 小时前
Type vs Interface:读完这篇就没有面试官能难倒你了
前端·面试·typescript
油丶酸萝卜别吃2 小时前
jquery-ajax.js 说明文档
前端·javascript·jquery
windliang2 小时前
Claude Code 源码分析(九):子 Agent 如何分叉、继续与回到父会话
前端·javascript·面试
柒和远方2 小时前
V063: TS 面试必考:interface 与 type 的四大差异,与 LLM Harness 的自动化择优
前端·javascript
半个落月2 小时前
React useRef 详解:DOM 引用、持久化值与 Worker 实例
前端·react.js
阿黎梨梨2 小时前
TypeScript 类型编程:从新手到 Harness 工程实践
前端
黄金决明子2 小时前
Vue3 + Vite 打包后打开空白?
前端
张龙6872 小时前
终端效率翻倍实战:fzf + zoxide + ripgrep + bat 组合拳,告别重复敲命令
前端