• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# 基础自定义弹出框 (CustomDialog)(不推荐)
2<!--Kit: ArkUI-->
3<!--Subsystem: ArkUI-->
4<!--Owner: @houguobiao-->
5<!--Designer: @houguobiao-->
6<!--Tester: @lxl007-->
7<!--Adviser: @HelloCrease-->
8CustomDialog是自定义弹出框,可用于广告、中奖、警告、软件更新等与用户交互响应操作。开发者可以通过CustomDialogController类显示自定义弹出框。具体用法请参考[自定义弹出框](../reference/apis-arkui/arkui-ts/ts-methods-custom-dialog-box.md)。
9
10> **说明:**
11>
12> 当前,ArkUI弹出框默认为非页面级弹出框,在页面路由跳转时,如果开发者未调用close方法将其关闭,弹出框将不会自动关闭。若需实现在跳转页面时覆盖弹出框的场景,可以使用[组件导航子页面显示类型的弹窗类型](arkts-navigation-navigation.md#页面显示类型)或者[页面级弹出框](arkts-embedded-dialog.md)。
13
14默认为模态弹窗且有蒙层,不可与蒙层下方控件进行交互(不支持点击和手势等向下透传)。可以通过配置[CustomDialogControllerOptions](../reference/apis-arkui/arkui-ts/ts-methods-custom-dialog-box.md#customdialogcontrolleroptions对象说明)中的isModal属性来实现模态和非模态弹窗,详细说明可参考[弹窗的种类](arkts-dialog-overview.md#弹窗的种类)。
15
16当isModal为true时,弹出框为模态弹窗,且弹窗周围的蒙层区不支持透传。isModal为false时,弹出框为非模态弹窗,且弹窗周围的蒙层区可以透传。因此如果需要同时允许弹出框的交互和弹出框外页面的交互行为,需要将弹出框设置为非模态。
17
18## 生命周期
19
20从API version 19开始,自定义弹出框提供了生命周期函数用于通知用户该弹出框的生命周期。生命周期的触发时序依次为:onWillAppear -> onDidAppear -> onWillDisappear -> onDidDisappear。
21
22| 名称            |类型| 说明                       |
23| ----------------- | ------ | ---------------------------- |
24| onWillAppear    | Callback&lt;void&gt; | 弹出框显示动效前的事件回调。 |
25| onDidAppear    | Callback&lt;void&gt;  | 弹出框弹出后的事件回调。    |
26| onWillDisappear | Callback&lt;void&gt; | 弹出框退出动效前的事件回调。 |
27| onDidDisappear | Callback&lt;void&gt;  | 弹出框消失后的事件回调。    |
28
29## 创建自定义弹出框
30
311. 使用\@CustomDialog装饰器装饰自定义弹出框,可在此装饰器内自定义弹出框内容。CustomDialogController需在@Component内定义。
32
33   ```ts
34   @CustomDialog
35   struct CustomDialogExample {
36     controller: CustomDialogController
37
38     build() {
39       Column() {
40         Text('我是内容')
41           .fontSize(20)
42       }.height(60).justifyContent(FlexAlign.Center)
43     }
44   }
45   ```
462. 创建构造器,与装饰器呼应相连。
47
48   ```ts
49    @Entry
50    @Component
51    struct CustomDialogUser {
52      dialogController: CustomDialogController = new CustomDialogController({
53        builder: CustomDialogExample(),
54      })
55    }
56   ```
573. 点击与onClick事件绑定的组件使弹出框弹出。
58
59   ```ts
60   @Entry
61   @Component
62   struct CustomDialogUser {
63     dialogController: CustomDialogController = new CustomDialogController({
64       builder: CustomDialogExample(),
65     })
66
67     build() {
68       Column() {
69         Button('click me')
70           .onClick(() => {
71             this.dialogController.open();
72           })
73       }.width('100%').margin({ top: 5 })
74     }
75   }
76   ```
77
78   ![zh-cn_image_0000001562700493](figures/zh-cn_image_0000001562700493.png)
79
80## 弹出框的交互
81
82弹出框可用于数据交互,完成用户一系列响应操作。
83
841. 在\@CustomDialog装饰器内添加按钮和数据函数。
85
86   ```ts
87   @CustomDialog
88   struct CustomDialogExample {
89     cancel: () => void = () => {
90     }
91     confirm: () => void = () => {
92     }
93     controller: CustomDialogController;
94
95     build() {
96       Column() {
97         Text('我是内容').fontSize(20).margin({ top: 10, bottom: 10 })
98         Flex({ justifyContent: FlexAlign.SpaceAround }) {
99           Button('cancel')
100             .onClick(() => {
101               this.controller.close();
102               if (this.cancel) {
103                 this.cancel();
104               }
105             }).backgroundColor(0xffffff).fontColor(Color.Black)
106           Button('confirm')
107             .onClick(() => {
108               this.controller.close();
109               if (this.confirm) {
110                 this.confirm();
111               }
112             }).backgroundColor(0xffffff).fontColor(Color.Red)
113         }.margin({ bottom: 10 })
114       }
115     }
116   }
117   ```
1182. 页面内需要在构造器内进行接收,同时创建相应的函数操作。
119
120   ```ts
121   @Entry
122   @Component
123   struct CustomDialogUser {
124     dialogController: CustomDialogController = new CustomDialogController({
125       builder: CustomDialogExample({
126         cancel: ()=> { this.onCancel() },
127         confirm: ()=> { this.onAccept() },
128       }),
129     });
130
131     onCancel() {
132       console.info('Callback when the first button is clicked');
133     }
134
135     onAccept() {
136       console.info('Callback when the second button is clicked');
137     }
138
139     build() {
140       Column() {
141         Button('click me')
142           .onClick(() => {
143             this.dialogController.open();
144           })
145       }.width('100%').margin({ top: 5 })
146     }
147   }
148   ```
149
150   ![zh-cn_image_0000001511421320](figures/zh-cn_image_0000001511421320.png)
151
152   3.可通过弹出框中的按钮实现路由跳转,同时获取跳转页面向当前页传入的参数。
153
154   ```ts
155   // Index.ets
156   @CustomDialog
157   struct CustomDialogExample {
158     @Link textValue: string;
159     controller?: CustomDialogController;
160     cancel: () => void = () => {
161     }
162     confirm: () => void = () => {
163     }
164
165     build() {
166       Column({ space: 20 }) {
167         if (this.textValue != '') {
168           Text(`第二个页面的内容为:${this.textValue}`)
169             .fontSize(20)
170         } else {
171           Text('是否获取第二个页面的内容')
172             .fontSize(20)
173         }
174         Flex({ justifyContent: FlexAlign.SpaceAround }) {
175           Button('cancel')
176             .onClick(() => {
177               if (this.controller != undefined) {
178                 this.controller.close();
179                 this.cancel();
180               }
181             }).backgroundColor(0xffffff).fontColor(Color.Black)
182           Button('confirm')
183             .onClick(() => {
184               if (this.controller != undefined && this.textValue != '') {
185                 this.controller.close();
186               } else if (this.controller != undefined) {
187                 this.getUIContext().getRouter().pushUrl({
188                   url: 'pages/Index2'
189                 });
190                 this.controller.close();
191               }
192             }).backgroundColor(0xffffff).fontColor(Color.Red)
193         }.margin({ bottom: 10 })
194       }.borderRadius(10).padding({ top: 20 })
195     }
196   }
197
198   @Entry
199   @Component
200   struct CustomDialogUser {
201     @State textValue: string = '';
202     dialogController: CustomDialogController | null = new CustomDialogController({
203       builder: CustomDialogExample({
204         cancel: () => {
205           this.onCancel()
206         },
207         confirm: () => {
208           this.onAccept()
209         },
210         textValue: this.textValue
211       })
212     });
213
214     // 在自定义组件即将析构销毁时将dialogController置空
215     aboutToDisappear() {
216       this.dialogController = null; // 将dialogController置空
217     }
218
219     onPageShow() {
220       const params = this.getUIContext().getRouter().getParams() as Record<string, string>; // 获取传递过来的参数对象
221       if (params) {
222         this.dialogController?.open();
223         this.textValue = params.info as string; // 获取info属性的值
224       }
225     }
226
227     onCancel() {
228       console.info('Callback when the first button is clicked');
229     }
230
231     onAccept() {
232       console.info('Callback when the second button is clicked');
233     }
234
235     exitApp() {
236       console.info('Click the callback in the blank area');
237     }
238
239     build() {
240       Column() {
241         Button('click me')
242           .onClick(() => {
243             if (this.dialogController != null) {
244               this.dialogController.open();
245             }
246           }).backgroundColor(0x317aff)
247       }.width('100%').margin({ top: 5 })
248     }
249   }
250   ```
251
252   ```ts
253   // Index2.ets
254   @Entry
255   @Component
256   struct Index2 {
257     @State message: string = '点击返回';
258
259     build() {
260       Column() {
261         Button(this.message)
262           .type(ButtonType.Capsule)
263           .onClick(() => {
264              this.getUIContext().getRouter().back({
265                url: 'pages/Index',
266                params: {
267                info: 'Hello World'
268              }
269           });
270         })
271       }.width('100%').height('100%').margin({ top: 20 })
272     }
273   }
274   ```
275
276   ![DialogRouter](figures/DialogRouter.gif)
277
278## 弹出框的动画
279
280弹出框通过定义openAnimation控制弹出框出现动画的持续时间,速度等参数。
281
282```ts
283@CustomDialog
284struct CustomDialogExample {
285  controller?: CustomDialogController;
286
287  build() {
288    Column() {
289      Text('Whether to change a text?').fontSize(16).margin({ bottom: 10 })
290    }
291  }
292}
293
294@Entry
295@Component
296struct CustomDialogUser {
297  @State textValue: string = '';
298  @State inputValue: string = 'click me';
299  dialogController: CustomDialogController | null = new CustomDialogController({
300    builder: CustomDialogExample(),
301    openAnimation: {
302      duration: 1200,
303      curve: Curve.Friction,
304      delay: 500,
305      playMode: PlayMode.Alternate,
306      onFinish: () => {
307        console.info('play end')
308      }
309    },
310    autoCancel: true,
311    alignment: DialogAlignment.Bottom,
312    offset: { dx: 0, dy: -20 },
313    gridCount: 4,
314    customStyle: false,
315    backgroundColor: 0xd9ffffff,
316    cornerRadius: 10,
317  });
318
319  // 在自定义组件即将析构销毁时将dialogController置空
320  aboutToDisappear() {
321    this.dialogController = null; // 将dialogController置空
322  }
323
324  build() {
325    Column() {
326      Button(this.inputValue)
327        .onClick(() => {
328          if (this.dialogController != null) {
329            this.dialogController.open();
330          }
331        }).backgroundColor(0x317aff)
332    }.width('100%').margin({ top: 5 })
333  }
334}
335```
336
337![openAnimator](figures/openAnimator.gif)
338
339## 弹出框的样式
340
341通过定义弹出框的宽度、高度、背景色、阴影等参数,控制其样式。
342
343```ts
344@CustomDialog
345struct CustomDialogExample {
346  controller?: CustomDialogController;
347
348  build() {
349    Column() {
350      Text('我是内容').fontSize(16).margin({ bottom: 10 })
351    }
352  }
353}
354
355@Entry
356@Component
357struct CustomDialogUser {
358  @State textValue: string = '';
359  @State inputValue: string = 'click me';
360  dialogController: CustomDialogController | null = new CustomDialogController({
361    builder: CustomDialogExample(),
362    autoCancel: true,
363    alignment: DialogAlignment.Center,
364    offset: { dx: 0, dy: -20 },
365    gridCount: 4,
366    customStyle: false,
367    backgroundColor: 0xd9ffffff,
368    cornerRadius: 20,
369    width: '80%',
370    height: '100px',
371    borderWidth: 1,
372    borderStyle: BorderStyle.Dashed,//使用borderStyle属性,需要和borderWidth属性一起使用
373    borderColor: Color.Blue,//使用borderColor属性,需要和borderWidth属性一起使用
374    shadow: ({ radius: 20, color: Color.Grey, offsetX: 50, offsetY: 0}),
375  });
376
377  // 在自定义组件即将析构销毁时将dialogController置空
378  aboutToDisappear() {
379    this.dialogController = null; // 将dialogController置空
380  }
381
382  build() {
383    Column() {
384      Button(this.inputValue)
385        .onClick(() => {
386          if (this.dialogController != null) {
387            this.dialogController.open();
388          }
389        }).backgroundColor(0x317aff)
390    }.width('100%').margin({ top: 5 })
391  }
392}
393```
394
395![custom_style](figures/custom_style.gif)
396
397## 嵌套自定义弹出框
398
399通过第一个弹出框打开第二个弹出框时,最好将第二个弹出框定义在第一个弹出框的父组件处,通过父组件传给第一个弹出框的回调来打开第二个弹出框。
400
401```ts
402@CustomDialog
403struct CustomDialogExampleTwo {
404  controllerTwo?: CustomDialogController;
405  @State message: string = "I'm the second dialog box.";
406  @State showIf: boolean = false;
407
408  build() {
409    Column() {
410      if (this.showIf) {
411        Text("Text")
412          .fontSize(30)
413          .height(100)
414      }
415      Text(this.message)
416        .fontSize(30)
417        .height(100)
418      Button("Create Text")
419        .onClick(() => {
420          this.showIf = true;
421        })
422      Button('Close Second Dialog Box')
423        .onClick(() => {
424          if (this.controllerTwo != undefined) {
425            this.controllerTwo.close();
426          }
427        })
428        .margin(20)
429    }
430  }
431}
432
433@CustomDialog
434struct CustomDialogExample {
435  openSecondBox?: () => void
436  controller?: CustomDialogController
437
438  build() {
439    Column() {
440      Button('Open Second Dialog Box and close this box')
441        .onClick(() => {
442          this.controller!.close();
443          this.openSecondBox!();
444        })
445        .margin(20)
446    }.borderRadius(10)
447  }
448}
449
450@Entry
451@Component
452struct CustomDialogUser {
453  @State inputValue: string = 'Click Me';
454  dialogController: CustomDialogController | null = new CustomDialogController({
455    builder: CustomDialogExample({
456      openSecondBox: () => {
457        if (this.dialogControllerTwo != null) {
458          this.dialogControllerTwo.open()
459        }
460      }
461    }),
462    cancel: this.exitApp,
463    autoCancel: true,
464    alignment: DialogAlignment.Bottom,
465    offset: { dx: 0, dy: -20 },
466    gridCount: 4,
467    customStyle: false
468  });
469  dialogControllerTwo: CustomDialogController | null = new CustomDialogController({
470    builder: CustomDialogExampleTwo(),
471    alignment: DialogAlignment.Bottom,
472    offset: { dx: 0, dy: -25 }
473  });
474
475  aboutToDisappear() {
476    this.dialogController = null;
477    this.dialogControllerTwo = null;
478  }
479
480  onCancel() {
481    console.info('Callback when the first button is clicked');
482  }
483
484  onAccept() {
485    console.info('Callback when the second button is clicked');
486  }
487
488  exitApp() {
489    console.info('Click the callback in the blank area');
490  }
491
492  build() {
493    Column() {
494      Button(this.inputValue)
495        .onClick(() => {
496          if (this.dialogController != null) {
497            this.dialogController.open();
498          }
499        }).backgroundColor(0x317aff)
500    }.width('100%').margin({ top: 5 })
501  }
502}
503```
504
505![nested_dialog](figures/nested_dialog.gif)
506
507由于自定义弹出框在状态管理侧有父子关系,如果将第二个弹出框定义在第一个弹出框内,那么当父组件(第一个弹出框)被销毁(关闭)时,子组件(第二个弹出框)内无法再继续创建新的组件。
508
509## 实现弹出框的物理返回拦截
510
511执行点击遮障层关闭、侧滑(左滑或右滑)、三键Back、键盘ESC关闭等交互操作时,如果注册了[CustomDialogControllerOptions](../reference/apis-arkui/arkui-ts/ts-methods-custom-dialog-box.md#customdialogcontrolleroptions对象说明)中的onWillDismiss回调函数,弹出框不会立即关闭。在回调函数中,通过[DismissDialogAction](../reference/apis-arkui/arkui-ts/ts-methods-custom-dialog-box.md#dismissdialogaction12)中的reason属性获取阻拦关闭弹出框的操作类型,根据原因决定是否关闭弹出框。
512
513```ts
514@CustomDialog
515struct CustomDialogExample {
516  cancel: () => void = () => {
517  }
518  confirm: () => void = () => {
519  }
520  controller?: CustomDialogController;
521
522  build() {
523    Column() {
524      Text('Are you sure?')
525        .fontSize(20)
526        .margin({
527          top: 10,
528          bottom: 10
529        })
530      Row() {
531        Button('cancel')
532          .onClick(() => {
533            if (this.controller != undefined) {
534              this.controller.close();
535            }
536          })
537          .backgroundColor(0xffffff)
538          .fontColor(Color.Black)
539        Button('confirm')
540          .onClick(() => {
541            if (this.controller != undefined) {
542              this.controller.close();
543            }
544          })
545          .backgroundColor(0xffffff)
546          .fontColor(Color.Red)
547      }
548      .width('100%')
549      .justifyContent(FlexAlign.SpaceAround)
550      .margin({ bottom: 10 })
551    }
552  }
553}
554
555@Entry
556@Component
557struct InterceptCustomDialog {
558  dialogController: CustomDialogController = new CustomDialogController({
559    builder: CustomDialogExample({
560      cancel: () => {
561        this.onCancel();
562      },
563      confirm: () => {
564        this.onAccept();
565      }
566    }),
567    onWillDismiss: (dismissDialogAction: DismissDialogAction) => {
568      console.info('dialog onWillDismiss reason: ' + dismissDialogAction.reason);
569      // 1、PRESS_BACK    点击三键back、侧滑(左滑/右滑)、键盘ESC。
570      // 2、TOUCH_OUTSIDE    点击遮障层时
571      // 3、CLOSE_BUTTON    点击关闭按钮
572      if (dismissDialogAction.reason === DismissReason.PRESS_BACK) {
573        // 处理业务逻辑后通过dismiss主动关闭对话框
574        // dismissDialogAction.dismiss();
575      }
576      if (dismissDialogAction.reason === DismissReason.TOUCH_OUTSIDE) {
577        // dismissDialogAction.dismiss();
578      }
579    },
580    alignment: DialogAlignment.Bottom,
581    offset: { dx: 0, dy: -20 }
582  })
583
584  onCancel() {
585    console.info('Callback when the first button is clicked');
586  }
587
588  onAccept() {
589    console.info('Callback when the second button is clicked');
590  }
591
592  build() {
593    Column() {
594      Button('click me')
595        .onClick(() => {
596          this.dialogController.open();
597        })
598    }
599    .width('100%')
600  }
601}
602```
603
604![onWillDismiss_dialog](figures/onWillDismiss_dialog.gif)
605
606## 设置弹出框避让软键盘的距离
607
608为显示弹出框的独立性,弹出框弹出时会与周边进行避让,包括状态栏、导航条以及键盘等留有间距。故当软键盘弹出时,默认情况下,弹出框会自动避开软键盘,并与之保持16vp的距离。从API version 15开始,开发者可以利用[CustomDialogControllerOptions](../reference/apis-arkui/arkui-ts/ts-methods-custom-dialog-box.md#customdialogcontrolleroptions对象说明)中的keyboardAvoidMode和keyboardAvoidDistance这两个配置项,来设置弹出框在软键盘弹出时的行为,包括是否需要避开软键盘以及与软键盘之间的距离。
609设置软键盘间距时,需要将keyboardAvoidMode值设为KeyboardAvoidMode.DEFAULT610
611```ts
612// xxx.ets
613import { LengthMetrics } from '@kit.ArkUI'
614
615@CustomDialog
616struct CustomDialogExample {
617  controller?: CustomDialogController;
618  build() {
619    Column() {
620      Column() {
621        Text('keyboardAvoidDistance: 0vp')
622          .fontSize(20)
623          .margin({ bottom: 36 })
624        TextInput({ placeholder: '' })
625      }.backgroundColor('#FFF0F0F0')
626    }
627  }
628}
629
630@Entry
631@Component
632struct Index {
633  dialogController: CustomDialogController | null = new CustomDialogController({
634    builder: CustomDialogExample({
635    }),
636    autoCancel: true,
637    gridCount: 4,
638    showInSubWindow: true,
639    isModal: true,
640    customStyle: false,
641    cornerRadius: 30,
642    alignment:DialogAlignment.Bottom,
643    keyboardAvoidMode: KeyboardAvoidMode.DEFAULT, // 软键盘弹出时,弹出框自动避让
644    keyboardAvoidDistance: LengthMetrics.vp(0) // 软键盘弹出时与弹出框的距离为0vp
645  })
646
647  build() {
648    Row() {
649      Row({ space: 20 }) {
650        Text('打开弹窗')
651          .fontSize(30)
652          .onClick(() => {
653            if (this.dialogController != null) {
654              this.dialogController.open();
655            }
656          })
657      }
658      .width('100%')
659    }
660    .height('100%')
661  }
662}
663```
664
665 ![UIContextPromptAction](figures/UIContextPromptActionCustomDialog.gif)
666
667## 获取弹出框的状态
668
669在业务模块中,页面上可能会同时出现多个弹出框。为避免重复打开相同的弹出框,建议在显示弹出框前,先通过控制器检查其当前状态。如果弹出框已处于显示状态,则不应再次打开。
670从API version 20开始,新增了getState接口,用于获取弹出框的当前状态。具体的弹出框状态信息,请参见[CommonState](../reference/apis-arkui/js-apis-promptAction.md#commonstate20枚举说明)枚举的详细说明。
671
672以下示例通过[getDialogController](../reference/apis-arkui/arkui-ts/ts-custom-component-api.md#getdialogcontroller18)和[CustomDialogController](../reference/apis-arkui/arkui-ts/ts-methods-custom-dialog-box.md#customdialogcontroller)两种方法,实现了获取弹出框当前状态的功能。
673
674```ts
675// xxx.ets
676@CustomDialog
677struct CustomDialogExample {
678  controller?: CustomDialogController
679
680  build() {
681    Column() {
682      Button('点我查询弹窗状态:通过自定义组件自带controller')
683        .onClick(() => {
684          if (this.getDialogController() != undefined) {
685            console.info('state:' + this.getDialogController().getState())
686          } else {
687            console.info('state: no exist')
688          }
689        }).margin(20)
690      Button('点我查询弹窗状态:通过CustomDialogController ')
691        .onClick(() => {
692          console.info('state:' + this.controller?.getState())
693        }).margin(20)
694      Button('点我关闭弹窗')
695        .onClick(() => {
696          if (this.getDialogController() != undefined) {
697            this.getDialogController().close()
698          }
699        }).margin(20)
700
701    }
702  }
703}
704
705@Entry
706@Component
707struct CustomDialogUser {
708  dialogController: CustomDialogController | null = new CustomDialogController({
709    builder: CustomDialogExample({
710    }),
711    autoCancel: false
712  })
713
714  build() {
715    Column() {
716      Button('click me')
717        .onClick(() => {
718          if (this.dialogController != null) {
719            this.dialogController.open()
720          }
721        })
722    }.width('100%').margin({ top: 5 })
723  }
724}
725```
726
727## 相关实例
728
729针对自定义弹出框开发,有以下相关实例可供参考:
730
731- [自定义弹出框(ArkTS)(API9)](https://gitee.com/openharmony/codelabs/tree/master/ETSUI/CustomDialog)
732- [构建多种样式弹出框(ArkTS)(API9)](https://gitee.com/openharmony/codelabs/tree/master/ETSUI/MultipleDialog)
733- [目标管理(ArkTS)(API9)](https://gitee.com/openharmony/codelabs/tree/master/ETSUI/TargetManagement)
734
735
736