如何实现全局可调用弹窗
===========

#### 问题现象

应用/元服务如何实现一个全局可调用的弹窗？

#### 背景知识

*   [windowStage](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/arkts-apis-window-windowstage)为窗口管理器，管理各个基本窗口单元。
*   [通过CustomDialogController类显示自定义弹窗](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/ts-methods-custom-dialog-box)，需要使用@CustomDialog定义弹窗内容。调用弹窗时，需要new CustomDialogController()，并将@CustomDialog作为[CustomDialogControllerOptions](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/ts-methods-custom-dialog-box#customdialogcontrolleroptions对象说明)中的builder参数传入。
*   [openCustomDialog](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/arkts-apis-uicontext-promptaction#opencustomdialog12)用于创建并弹出dialogContent对应的自定义弹窗。

#### 解决方案

实现一个全局可调用弹窗，可通过如下三种方式实现：

*   方案一：实现全局可调用弹窗推荐使用：[不依赖UI组件的全局自定义弹出框 (openCustomDialog)](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-uicontext-custom-dialog)，示例请参考[完整示例](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-uicontext-custom-dialog#完整示例)。
*   方案二：可使用子窗口方式实现全局可调用弹窗，实现方式如下：
    1.  在EntryAbility.ets中通过[AppStorage](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-appstorage)获取窗口管理器用于后续获取Window实例实现子窗口的创建与销毁，代码如下：
        
        import { ConfigurationConstant, UIAbility } from '@kit.AbilityKit';
        import { hilog } from '@kit.PerformanceAnalysisKit';
        import { window } from '@kit.ArkUI';
        
        
        const DOMAIN = 0x0000;
        
        
        export default class EntryAbility extends UIAbility {
          onCreate(): void {
            try {
              this.context.getApplicationContext().setColorMode(ConfigurationConstant.ColorMode.COLOR\_MODE\_NOT\_SET);
            } catch (err) {
              hilog.error(DOMAIN, 'testTag', 'Failed to set colorMode. Cause: %{public}s', JSON.stringify(err));
            }
            hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onCreate');
          }
        
        
          onDestroy(): void {
            hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onDestroy');
          }
        
        
          onWindowStageCreate(windowStage: window.WindowStage): void {
            AppStorage.setOrCreate('windowStage', windowStage);
            windowStage.loadContent('pages/Index', (err) => {
              if (err.code) {
                hilog.error(DOMAIN, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err));
                return;
              }
              hilog.info(DOMAIN, 'testTag', 'Succeeded in loading the content.');
            });
          }
        
        
          onWindowStageDestroy(): void {
           _ // Main window is destroyed, release UI related resources_
            hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onWindowStageDestroy');
          }
        
        
          onForeground(): void {
         _   // Ability has brought to foreground_
            hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onForeground');
          }
        
        
          onBackground(): void {
           _ // Ability has back to background_
            hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onBackground');
          }
        };
        
    2.  SubWindow.ets页面封装打开弹窗createMySubWindowFun方法，通过[createSubWindow](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/arkts-apis-window-windowstage#createsubwindow9)实现弹窗的弹出，使用[setUIContent](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/arkts-apis-window-window#setuicontent9)设置SubPage页面为子窗口加载的具体页面内容，通过[moveWindowTo](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/arkts-apis-window-window#movewindowto9)、[resize](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/arkts-apis-window-window#resize9)等方法设置子窗口样式。封装deleteMySubWindowFun方法通过[destroyWindow](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/arkts-apis-window-window#destroywindow9)实现弹窗的关闭。代码如下：
        
        import { window } from '@kit.ArkUI';
        import { BusinessError } from '@kit.BasicServicesKit';
        
        _// 获取windowStage_
        let windowStage = AppStorage.get('windowStage') as window.WindowStage;
        let windowClass: window.Window | undefined = undefined;
        
        
        export class mySubWindow {
          createMySubWindowFun(subWindowName: string) {
          _  // 创建子窗口_
            windowStage.createSubWindow(subWindowName, (err: BusinessError, data) => {
              windowClass = data;
              data.setUIContent('pages/SubPage', () => {
             _   // 设置子窗口左上角坐标_
                data.moveWindowTo(150, 300);
               _ // 设置子窗口大小_
                data.resize(1000, 1000);
             _   // 展示子窗口_
                data.showWindow();
              });
            });
          }
        
        
          deleteMySubWindowFun() {
            if (windowClass) {
              windowClass.destroyWindow();
            }
          }
        }
        
    3.  SubPage.ets页面用于展示子窗页面内容，代码如下：
        
        _// 子窗口显示页面_
        import { mySubWindow } from '../utils/SubWindow';
        
        
        @Entry
        @Component
        struct SubPage {
          mySubWindow: mySubWindow = new mySubWindow();
        
        
          build() {
            Column() {
              Text('子窗口页面')
                .fontSize(40)
                .margin({ top: 30 });
              Button('点击关闭')
                .onClick(() => {
                  this.mySubWindow.deleteMySubWindowFun();
                })
                .margin({ top: 100 });
            }
            .width('100%')
            .height('100%');
          }
        }
        
    4.  Index.ets页面使用方式通过引入SubWindow页面，通过调用封装的createMySubWindowFun方法实现弹窗的打开，代码如下：
        
        import { mySubWindow } from '../utils/SubWindow';
        
        
        @Entry
        @Component
        struct Index {
          mySubWindow: mySubWindow = new mySubWindow();
        
        
          build() {
            RelativeContainer() {
              Text('点击打开弹窗')
                .id('HelloWorld')
                .fontSize(30)
                .fontWeight(FontWeight.Bold)
                .alignRules({
                  center: { anchor: '\_\_container\_\_', align: VerticalAlign.Center },
                  middle: { anchor: '\_\_container\_\_', align: HorizontalAlign.Center }
                })
                .onClick(() => {
                  this.mySubWindow.createMySubWindowFun('mySubWindow');
                });
            }
            .height('100%')
            .width('100%')
            .backgroundColor('#F1F3F5');
          }
        }
        
        运行效果图如下：
        
        ![](https://contentcenter-vali-drcn.dbankcdn.cn/pvt_2/DeveloperAlliance_scene_100_1/44/v3/Eyn4ZTOqRQGxvei95GcAXg/zh-cn_image_0000002628565460.png?HW-CC-KV=V1&HW-CC-Date=20260709T134105Z&HW-CC-Expire=86400&HW-CC-Sign=E9BB4F1B8E6C692D9245B84B629F483804A9358C74C1AA9FD313D12F826ABB9A "点击放大")
        
*   方案三：通过自定义弹窗(CustomDialog)封装成自定义组件的方式：
    1.  封装弹窗控制方法Dialog.ets，使用[@watch](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-watch)监听外部传入的变量变化，通过CustomDialogController控制弹窗的显示隐藏，代码如下：
        
        import { CustomDialogView } from './CustomDialogView';
        
        
        @Component
        export struct Dialog {
         _ // 监听外部传入的visible变量，visible值发生变化时触发onChange回调函数_
          @Watch('onChange') @Link visible: boolean;
          onCancel?: () => void;
          onConfirm?: () => void;
         _ // 通过CustomDialogController的builder参数绑定弹窗组件CustomDialogView_
          private controller = new CustomDialogController({
            builder: CustomDialogView({
              visible: $visible,
              onCancel: this.onCancel,
              onConfirm: this.onConfirm,
            }),
            autoCancel: false,
            alignment: DialogAlignment.Center,
          });
        
        
          _// 当visible的值变化时触发回调_
          onChange(): void {
            if (this.visible) {
              this.controller.open();
            } else {
              this.controller.close();
            }
          }
        
        
         _ // 二次封装的Dialog组件主要通过控制器控制弹窗，不需要任何界面_
          build() {
          }
        }
        
    2.  封装弹窗视图页面CustomDialogView.ets，作用于弹窗中作为展示页面，代码如下：
        
        @CustomDialog
        export struct CustomDialogView {
          @Link visible: boolean;
          controller: CustomDialogController;
          _// 弹窗交互事件参数，点击确认和取消按钮时的回调函数_
          onCancel?: () => void;
          onConfirm?: () => void;
        
        
          build() {
            Column() {
              Text('自定义弹窗')
                .margin({ top: 20, bottom: 40 });
              Button('点击关闭弹窗').onClick(() => {
              _  // 重置弹窗显隐参数_
                this.visible = false;
              _  // 关闭弹窗_
                this.controller.close();
              });
            }
            .width('100%')
            .height(200)
            .backgroundColor(Color.White);
          }
        }
        
    3.  使用弹窗页面CustomDialog.ets，通过给引入的Dialog组件传入visible控制弹窗的弹出，代码如下：
        
        import { Dialog } from './Dialog';
        
        
        @Entry
        @Component
        export struct CustomDialog {
         _ // 外部定义visible变量作为弹窗组件入参，控制弹窗显隐_
          @State visible: boolean = false;
        
        
          build() {
            Column({ space: 20 }) {
              Button('点击打开弹窗')
            _  // 点击修改visible变量后，visible的值可以被Dialog组件监听并响应_
                .onClick(() => this.visible = !this.visible)
                .margin({ top: 40 });
              _// 通过双向绑定visible变量，实现外部控制弹窗_
              Dialog({
                visible: $visible,
              });
            }
            .width('100%');
          }
        }
        
        运行效果图如下：
        
        ![](https://contentcenter-vali-drcn.dbankcdn.cn/pvt_2/DeveloperAlliance_scene_100_1/9b/v3/lLZCuzzrTcuHrtrWMtQNow/zh-cn_image_0000002658924769.png?HW-CC-KV=V1&HW-CC-Date=20260709T134105Z&HW-CC-Expire=86400&HW-CC-Sign=5BE5318F86AEC9CA955D318807C45D967E3B6598EA5BD7AE19871E24A15E85D1 "点击放大")
        

#### 总结

实现全局可调用弹窗可通过openCustomDialog、子窗口与自定义弹窗方式实现，openCustomDialog通过ComponentContent封装内容可以与UI界面解耦，调用更加灵活，可以满足开发者的封装诉求，优先推荐使用。子窗口方式需要使用页面当作弹窗内容且需要获取窗口管理器，代码复杂性较高。自定义弹窗方式与调用UI界面耦合性较高也不建议使用。