鸿蒙应用示例:基于 promptAction 封装全局弹窗工具类

 

源码下载: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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
@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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
@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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
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()
  }
}

  

posted @   zhongcx  阅读(195)  评论(0编辑  收藏  举报
编辑推荐:
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· DeepSeek 开源周回顾「GitHub 热点速览」
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
点击右上角即可分享
微信分享提示