源码下载:https://download.csdn.net/download/zhongcongxu01/89826921

随着 HarmonyOS 的不断发展和完善,开发者们在构建应用时有了更多选择和灵活性。其中,promptAction 是一个非常有用的 API,允许开发者创建全局的弹窗,从而增强应用的用户体验。然而,在使用 promptAction 时,可能会遇到一些挑战,如如何正确地封装弹窗逻辑,确保弹窗的稳定性和易用性。

本文将详细介绍如何在 HarmonyOS 应用开发中,使用 promptAction API 封装一个全局弹窗工具类,并探讨一些常见问题及其解决方案。

起因
在开发过程中,我们发现使用 promptAction 创建全局弹窗时存在几个关键问题:

  1. @Builder 函数中的参数要求:在 @Builder 函数中,必须要有参数,即使不使用也会导致应用崩溃。这种情况下,即使是使用 try-catch 也不能捕获异常。
  2. 参数类型限制:对于 new ComponentContent(...) 的第三个参数,必须是 Object 或者类接口,而不能是基本数据类型(如 string)。如果传入基本数据类型,虽然编译时不会报错,但在运行时会引发错误 OpenCustomDialog args error code is 401。

工具类设计思路

为了解决上述问题,我们设计了一套工具类,以简化弹窗的创建、显示和关闭过程。

  1. 封装弹窗操作:MyPromptActionUtil 类封装了弹窗的基本操作,包括创建、显示和关闭。
  2. 生成唯一标识:MyPromptInfo 类用于生成每个弹窗的唯一标识符 dialogID,便于后续的事件监听和管理。
  3. 配置弹窗属性:通过 MyPromptActionUtil 类的链式调用,可以轻松配置弹窗的各种属性,如对齐方式、是否允许侧滑关闭等。

使用示例
【工具类】src/main/ets/utils/MyPromptActionUtil.ets

import { ComponentContent, PromptAction } from '@kit.ArkUI';
import { BusinessError } from '@kit.BasicServicesKit';
// MyPromptInfo 类用于生成唯一的 dialogID
export class MyPromptInfo {
  public dialogID: string

  constructor() {
    this.dialogID = this.generateRandomString(10)
  }
  // 生成指定长度的随机字符串
  generateRandomString(length: number): string {
    const characters = 'abcdefghijklmnopqrstuvwxyz0123456789';
    let result = '';

    for (let i = 0; i < length; i++) {
      const randomIndex = Math.floor(Math.random() * characters.length);
      result += characters.charAt(randomIndex);
    }

    return result;
  }
}
// MyPromptActionUtil 类用于封装弹窗操作
export class MyPromptActionUtil<T extends MyPromptInfo> {
  private uiContext: UIContext;
  private promptAction: PromptAction;
  private contentNode: ComponentContent<T> | undefined;
  private wrapBuilder: WrappedBuilder<[T]>;
  private t: T;
  private isModal: boolean = true;
  private alignment: DialogAlignment = DialogAlignment.Center;
  private isSwipeBackEnabled: boolean = true;
  private isMaskTapToCloseEnabled: boolean = true;
  public dialogID: string

  constructor(uiContext: UIContext, wrapBuilder: WrappedBuilder<[T]>, t: T) {
    this.uiContext = uiContext;
    this.promptAction = uiContext.getPromptAction();
    this.wrapBuilder = wrapBuilder;
    this.t = t;
    this.dialogID = t.dialogID
  }

  setSwipeBackEnabled(isSwipeBackEnabled: boolean) {
    this.isSwipeBackEnabled = isSwipeBackEnabled;
    return this;
  }

  setMaskTapToCloseEnabled(isMaskTapToCloseEnabled: boolean) {
    this.isMaskTapToCloseEnabled = isMaskTapToCloseEnabled
    return this;
  }

  setAlignment(alignment: DialogAlignment) {
    this.alignment = alignment;
    return this;
  }

  setModal(isModal: boolean) {
    this.isModal = isModal;
    return this;
  }

  onDidAppear(callback: () => void) {
    this.onDidAppearCallback = callback;
    return this;
  }

  onDidDisappear(callback: () => void) {
    this.onDidDisappearCallback = callback;
    return this;
  }

  onWillAppear(callback: () => void) {
    this.onWillAppearCallback = callback;
    return this;
  }

  onWillDisappear(callback: () => void) {
    this.onWillDisappearCallback = callback;
    return this;
  }

  private onDidAppearCallback?: () => void;
  private onDidDisappearCallback?: () => void;
  private onWillAppearCallback?: () => void;
  private onWillDisappearCallback?: () => void;

  closeCustomDialog() {
    if (this.contentNode) {
      this.promptAction.closeCustomDialog(this.contentNode);
    }
    return this;
  }
  // 显示自定义弹窗
  showCustomDialog() {
    try {
      if (!this.contentNode) {
        this.contentNode = new ComponentContent(this.uiContext, this.wrapBuilder, this.t);
      }
      this.promptAction.openCustomDialog(this.contentNode, { // 打开自定义弹窗
        alignment: this.alignment,
        isModal: this.isModal,
        showInSubWindow: false,
        maskRect: {
          x: 0,
          y: 0,
          width: '100%',
          height: '100%'
        },
        onWillDismiss: (dismissDialogAction: DismissDialogAction) => { //弹窗响应
          console.info("reason" + JSON.stringify(dismissDialogAction.reason))
          console.log("dialog onWillDismiss")
          if (dismissDialogAction.reason == 0 && this.isSwipeBackEnabled) { //手势返回时,关闭弹窗。
            this.promptAction.closeCustomDialog(this.contentNode)
          }
          if (dismissDialogAction.reason == 1 && this.isMaskTapToCloseEnabled) {
            this.promptAction.closeCustomDialog(this.contentNode)
          }
        },
        onDidAppear: this.onDidAppearCallback ? this.onDidAppearCallback : () => {
        },
        onDidDisappear: this.onDidDisappearCallback ? this.onDidDisappearCallback : () => {
        },
        onWillAppear: this.onWillAppearCallback ? this.onWillAppearCallback : () => {
        },
        onWillDisappear: this.onWillDisappearCallback ? this.onWillDisappearCallback : () => {
        },
      });
    } catch (error) {// 错误处理
      let message = (error as BusinessError).message;
      let code = (error as BusinessError).code;
      console.error(`OpenCustomDialog args error code is ${code}, message is ${message}`);
    }
    return this;
  }
}

【使用示例】src/main/ets/dialog/MyDialog_1.ets

@Component
export struct MyDialog_1 {
  @Prop dialogID: string
  @State title: string = '加载中...'

  build() {
    Stack() {
      Column() {
        LoadingProgress()
          .color(Color.White).width(100).height(100)
        Text(this.title)
          .fontSize(18).fontColor(0xffffff).margin({ top: 8 })
          .visibility(this.title ? Visibility.Visible : Visibility.None)
      }
    }
    .onClick(() => {
      getContext(this).eventHub.emit(this.dialogID, "关闭弹窗")
    })
    .width(180)
    .height(180)
    .backgroundColor(0x88000000)
    .borderRadius(10)
    .shadow({
      radius: 10,
      color: Color.Gray,
      offsetX: 3,
      offsetY: 3
    })
  }
}

src/main/ets/dialog/MyDialog_2.ets

@Component
export struct MyDialog_2 {
  @Prop dialogID: string
  @Prop message: string

  aboutToAppear(): void {
    setTimeout(() => {
      console.info(`this.dialogID:${this.dialogID}`)
      getContext(this).eventHub.emit(this.dialogID, "关闭弹窗")
    }, 2000)
  }

  build() {
    Column() {
      Text(this.message)
        .fontSize('36lpx')
        .fontColor(Color.White)
        .backgroundColor("#B2000000")
        .borderRadius(8)
        .constraintSize({ maxWidth: '80%' })
        .padding({
          bottom: '28lpx',
          left: '60lpx',
          right: '60lpx',
          top: '28lpx'
        })

    }

  }
}

src/main/ets/pages/Page23.ets

import { MyPromptActionUtil, MyPromptInfo } from '../utils/MyPromptActionUtil'
// MyDialog_1 和 MyDialog_2 类用于定义弹窗内容
import { MyDialog_1 } from '../dialog/MyDialog_1'
import { MyDialog_2 } from '../dialog/MyDialog_2'

/*【注意事项】必须有参数,没参数在运行时会闪退,报错内容
 Error message:is not callable
SourceCode:
        (parent ? parent : this).observeComponentCreation2((elmtId, isInitialRender) => {
 */
// @Builder
// function test_1() {
//   MyDialog_1()
// }


@Builder
function test_1(data: MyPromptInfo) {
  MyDialog_1({ dialogID: data.dialogID })
}


@Builder
function test_2(data: MyDialog_2_Info) {
  MyDialog_2({ dialogID: data.dialogID, message: data.message })
}

class MyDialog_2_Info extends MyPromptInfo {
  public message: string = ""

  constructor(message: string) {
    super()
    this.message = message
  }
}

@Entry
@Component
struct Page23 {
  myDialog_1: MyPromptActionUtil<MyPromptInfo> | undefined
  myDialog_2: MyPromptActionUtil<MyDialog_2_Info> | undefined

  build() {
    Column() {
      Button('显示Loading').onClick(() => {
        this.showLoadingDialog()
      })
      Button('显示Toast').onClick(() => {
        this.showToast(`随机数:${this.getRandomInt(1, 100)}`)
      })
    }
    .height('100%')
    .width('100%')
  }

  getRandomInt(min: number, max: number): number {
    min = Math.ceil(min);
    max = Math.floor(max);
    return Math.floor(Math.random() * (max - min + 1)) + min;
  }

  // 显示 Loading 弹窗
  showLoadingDialog() {
    if (!this.myDialog_1) {
      this.myDialog_1 =
        new MyPromptActionUtil<MyPromptInfo>(this.getUIContext(), wrapBuilder(test_1), new MyPromptInfo())
          .setModal(true)//true:存在黑色半透明蒙层,false:没有蒙层
          .setSwipeBackEnabled(true)//true:侧滑允许关闭弹窗
          .setMaskTapToCloseEnabled(true)//true:点击半透明蒙层可关闭弹窗【注:如果setModal(false),那么就没有蒙层,所以点击对话框外也没有响应事件,也就是这里设置了也没效果,并且事件会穿透】
          .setAlignment(DialogAlignment.Center)//对齐方式
          .onDidAppear(() => {
            console.info('当对话框完全呈现出来,即完成打开动画后')
          })
          .onDidDisappear(() => {
            console.info('当对话框完全消失,即关闭动画结束后')
          })
          .onWillAppear(() => {
            console.info('在对话框的打开动画开始之前调用的回调函数')
            getContext(this).eventHub.on(this.myDialog_1?.dialogID, (data: string) => {
              //监听结果
              if (data == '关闭弹窗') {
                this.myDialog_1?.closeCustomDialog()
              }
            })
          })
          .onWillDisappear(() => {
            console.info('在对话框的关闭动画开始之前调用的回调函数')
            getContext(this).eventHub.off(this.myDialog_1?.dialogID)
          })
    }
    this.myDialog_1.showCustomDialog()
  }

  // 显示 Toast 弹窗
  showToast(toastInfo: string) {
    getContext(this).eventHub.off(this.myDialog_2?.dialogID)
    this.myDialog_2?.closeCustomDialog() //如果之前有的toast对话框,并且正在显示,则先关闭toast提示
    this.myDialog_2 =
      new MyPromptActionUtil<MyDialog_2_Info>(this.getUIContext(), wrapBuilder(test_2), new MyDialog_2_Info(toastInfo))
        .setModal(false)//true:存在黑色半透明蒙层,false:没有蒙层
        .setSwipeBackEnabled(false)//true:侧滑允许关闭弹窗
        .setMaskTapToCloseEnabled(true)//true:点击半透明蒙层可关闭弹窗【注:如果setModal(false),那么就没有蒙层,所以点击对话框外也没有响应事件,也就是这里设置了也没效果,并且事件会穿透】
        .setAlignment(DialogAlignment.Center)
        .onWillAppear(() => {
          console.info('在对话框的打开动画开始之前调用的回调函数')
          getContext(this).eventHub.on(this.myDialog_2?.dialogID, (data: string) => {
            //监听结果
            if (data == '关闭弹窗') {
              this.myDialog_2?.closeCustomDialog()
            }
          })
        })
        .onWillDisappear(() => {
          console.info('在对话框的关闭动画开始之前调用的回调函数')
          getContext(this).eventHub.off(this.myDialog_2?.dialogID)
        })
        .showCustomDialog()
  }
}

zhongcx
1 声望3 粉丝