• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# 自定义弹窗 (CustomDialog)
2
3
4CustomDialog是自定义弹窗,可用于广告、中奖、警告、软件更新等与用户交互响应操作。开发者可以通过CustomDialogController类显示自定义弹窗。具体用法请参考[自定义弹窗](../reference/apis-arkui/arkui-ts/ts-methods-custom-dialog-box.md)。
5
6> **说明:**
7>
8> 当前,ArkUI弹窗均为非页面级弹窗,在页面路由跳转时,如果开发者未调用close方法将其关闭,弹窗将不会自动关闭。若需实现在跳转页面时弹窗同步关闭的场景,建议使用Navigation。具体使用方法,请参考[组件导航子页面显示类型的弹窗类型](arkts-navigation-navigation.md#页面显示类型)。
9
10## 创建自定义弹窗
11
121. 使用\@CustomDialog装饰器装饰自定义弹窗,可在此装饰器内自定义弹窗内容。
13
14   ```ts
15   @CustomDialog
16   struct CustomDialogExample {
17     controller: CustomDialogController = new CustomDialogController({
18       builder: CustomDialogExample({}),
19     })
20
21     build() {
22       Column() {
23         Text('我是内容')
24           .fontSize(20)
25           .margin({ top: 10, bottom: 10 })
26       }
27     }
28   }
29   ```
30
313. 创建构造器,与装饰器呼应相连。
32
33   ```ts
34    @Entry
35    @Component
36    struct CustomDialogUser {
37      dialogController: CustomDialogController = new CustomDialogController({
38        builder: CustomDialogExample(),
39      })
40    }
41   ```
42
434. 点击与onClick事件绑定的组件使弹窗弹出。
44
45   ```ts
46   @Entry
47   @Component
48   struct CustomDialogUser {
49     dialogController: CustomDialogController = new CustomDialogController({
50       builder: CustomDialogExample(),
51     })
52
53     build() {
54       Column() {
55         Button('click me')
56           .onClick(() => {
57             this.dialogController.open()
58           })
59       }.width('100%').margin({ top: 5 })
60     }
61   }
62   ```
63
64   ![zh-cn_image_0000001562700493](figures/zh-cn_image_0000001562700493.png)
65
66
67## 弹窗的交互
68
69弹窗可用于数据交互,完成用户一系列响应操作。
70
71
721. 在\@CustomDialog装饰器内添加按钮,同时添加数据函数。
73
74   ```ts
75   @CustomDialog
76   struct CustomDialogExample {
77     cancel?: () => void
78     confirm?: () => void
79     controller: CustomDialogController
80
81     build() {
82       Column() {
83         Text('我是内容').fontSize(20).margin({ top: 10, bottom: 10 })
84         Flex({ justifyContent: FlexAlign.SpaceAround }) {
85           Button('cancel')
86             .onClick(() => {
87               this.controller.close()
88               if (this.cancel) {
89                 this.cancel()
90               }
91             }).backgroundColor(0xffffff).fontColor(Color.Black)
92           Button('confirm')
93             .onClick(() => {
94               this.controller.close()
95               if (this.confirm) {
96                 this.confirm()
97               }
98             }).backgroundColor(0xffffff).fontColor(Color.Red)
99         }.margin({ bottom: 10 })
100       }
101     }
102   }
103   ```
104
1052. 页面内需要在构造器内进行接收,同时创建相应的函数操作。
106
107     ```ts
108   @Entry
109   @Component
110   struct CustomDialogUser {
111       dialogController: CustomDialogController = new CustomDialogController({
112         builder: CustomDialogExample({
113           cancel: ()=> { this.onCancel() },
114           confirm: ()=> { this.onAccept() },
115         }),
116       })
117
118       onCancel() {
119         console.info('Callback when the first button is clicked')
120       }
121
122       onAccept() {
123         console.info('Callback when the second button is clicked')
124       }
125
126       build() {
127         Column() {
128           Button('click me')
129             .onClick(() => {
130               this.dialogController.open()
131             })
132         }.width('100%').margin({ top: 5 })
133       }
134     }
135   ```
136
137      ![zh-cn_image_0000001511421320](figures/zh-cn_image_0000001511421320.png)
138
139   3.可通过弹窗中的按钮实现路由跳转,同时获取跳转页面向当前页传入的参数。
140
141   ```ts
142   // Index.ets
143   @CustomDialog
144   struct CustomDialogExample {
145     @Link textValue: string
146     controller?: CustomDialogController
147     cancel: () => void = () => {
148     }
149     confirm: () => void = () => {
150     }
151
152     build() {
153       Column({ space: 20 }) {
154         if (this.textValue != '') {
155           Text(`第二个页面的内容为:${this.textValue}`)
156             .fontSize(20)
157         } else {
158           Text('是否获取第二个页面的内容')
159             .fontSize(20)
160         }
161         Flex({ justifyContent: FlexAlign.SpaceAround }) {
162           Button('cancel')
163             .onClick(() => {
164               if (this.controller != undefined) {
165                 this.controller.close()
166                 this.cancel()
167               }
168             }).backgroundColor(0xffffff).fontColor(Color.Black)
169           Button('confirm')
170             .onClick(() => {
171               if (this.controller != undefined && this.textValue != '') {
172                 this.controller.close()
173               } else if (this.controller != undefined) {
174                 this.getUIContext().getRouter().pushUrl({
175                   url: 'pages/Index2'
176                 })
177                 this.controller.close()
178               }
179             }).backgroundColor(0xffffff).fontColor(Color.Red)
180         }.margin({ bottom: 10 })
181       }.borderRadius(10).padding({ top: 20 })
182     }
183   }
184
185   @Entry
186   @Component
187   struct CustomDialogUser {
188     @State textValue: string = ''
189     dialogController: CustomDialogController | null = new CustomDialogController({
190       builder: CustomDialogExample({
191         cancel: () => {
192           this.onCancel()
193         },
194         confirm: () => {
195           this.onAccept()
196         },
197         textValue: $textValue
198       })
199     })
200
201     // 在自定义组件即将析构销毁时将dialogController置空
202     aboutToDisappear() {
203       this.dialogController = null // 将dialogController置空
204     }
205
206     onPageShow() {
207       const params = this.getUIContext().getRouter().getParams() as Record<string, string>; // 获取传递过来的参数对象
208       if (params) {
209         this.dialogController?.open()
210         this.textValue = params.info as string; // 获取info属性的值
211       }
212     }
213
214     onCancel() {
215       console.info('Callback when the first button is clicked')
216     }
217
218     onAccept() {
219       console.info('Callback when the second button is clicked')
220     }
221
222     exitApp() {
223       console.info('Click the callback in the blank area')
224     }
225
226     build() {
227       Column() {
228         Button('click me')
229           .onClick(() => {
230             if (this.dialogController != null) {
231               this.dialogController.open()
232             }
233           }).backgroundColor(0x317aff)
234       }.width('100%').margin({ top: 5 })
235     }
236   }
237   ```
238
239   ```ts
240   // Index2.ets
241   @Entry
242   @Component
243   struct Index2 {
244     @State message: string = '点击返回';
245     build() {
246       Column() {
247         Button(this.message)
248           .fontSize(50)
249           .fontWeight(FontWeight.Bold).onClick(() => {
250           this.getUIContext().getRouter().back({
251             url: 'pages/Index',
252             params: {
253               info: 'Hello World'
254             }
255           });
256         })
257       }.width('100%').height('100%').margin({ top: 20 })
258     }
259   }
260   ```
261
262   ![DialogRouter](figures/DialogRouter.gif)
263
264## 弹窗的动画
265
266弹窗通过定义openAnimation控制弹窗出现动画的持续时间,速度等参数。
267
268```ts
269@CustomDialog
270struct CustomDialogExample {
271  controller?: CustomDialogController
272
273  build() {
274    Column() {
275      Text('Whether to change a text?').fontSize(16).margin({ bottom: 10 })
276    }
277  }
278}
279
280@Entry
281@Component
282struct CustomDialogUser {
283  @State textValue: string = ''
284  @State inputValue: string = 'click me'
285  dialogController: CustomDialogController | null = new CustomDialogController({
286    builder: CustomDialogExample(),
287    openAnimation: {
288      duration: 1200,
289      curve: Curve.Friction,
290      delay: 500,
291      playMode: PlayMode.Alternate,
292      onFinish: () => {
293        console.info('play end')
294      }
295    },
296    autoCancel: true,
297    alignment: DialogAlignment.Bottom,
298    offset: { dx: 0, dy: -20 },
299    gridCount: 4,
300    customStyle: false,
301    backgroundColor: 0xd9ffffff,
302    cornerRadius: 10,
303  })
304
305  // 在自定义组件即将析构销毁时将dialogController置空
306  aboutToDisappear() {
307    this.dialogController = null // 将dialogController置空
308  }
309
310  build() {
311    Column() {
312      Button(this.inputValue)
313        .onClick(() => {
314          if (this.dialogController != null) {
315            this.dialogController.open()
316          }
317        }).backgroundColor(0x317aff)
318    }.width('100%').margin({ top: 5 })
319  }
320}
321```
322
323![openAnimator](figures/openAnimator.gif)
324
325## 弹窗的样式
326弹窗通过定义宽度、高度、背景色、阴影等参数来控制样式。
327
328```ts
329@CustomDialog
330struct CustomDialogExample {
331  controller?: CustomDialogController
332
333  build() {
334    Column() {
335      Text('我是内容').fontSize(16).margin({ bottom: 10 })
336    }
337  }
338}
339
340@Entry
341@Component
342struct CustomDialogUser {
343  @State textValue: string = ''
344  @State inputValue: string = 'click me'
345  dialogController: CustomDialogController | null = new CustomDialogController({
346    builder: CustomDialogExample(),
347    autoCancel: true,
348    alignment: DialogAlignment.Center,
349    offset: { dx: 0, dy: -20 },
350    gridCount: 4,
351    customStyle: false,
352    backgroundColor: 0xd9ffffff,
353    cornerRadius: 20,
354    width: '80%',
355    height: '100px',
356    borderWidth: 1,
357    borderStyle: BorderStyle.Dashed,//使用borderStyle属性,需要和borderWidth属性一起使用
358    borderColor: Color.Blue,//使用borderColor属性,需要和borderWidth属性一起使用
359    shadow: ({ radius: 20, color: Color.Grey, offsetX: 50, offsetY: 0}),
360  })
361
362  // 在自定义组件即将析构销毁时将dialogController置空
363  aboutToDisappear() {
364    this.dialogController = null // 将dialogController置空
365  }
366
367  build() {
368    Column() {
369      Button(this.inputValue)
370        .onClick(() => {
371          if (this.dialogController != null) {
372            this.dialogController.open()
373          }
374        }).backgroundColor(0x317aff)
375    }.width('100%').margin({ top: 5 })
376  }
377}
378```
379![custom_style](figures/custom_style.gif)
380
381## 嵌套自定义弹窗
382
383通过第一个弹窗打开第二个弹窗时,最好将第二个弹窗定义在第一个弹窗的父组件处,通过父组件传给第一个弹窗的回调来打开第二个弹窗。
384
385```ts
386@CustomDialog
387struct CustomDialogExampleTwo {
388  controllerTwo?: CustomDialogController
389  @State message: string = "I'm the second dialog box."
390  @State showIf: boolean = false;
391  build() {
392    Column() {
393      if (this.showIf) {
394        Text("Text")
395          .fontSize(30)
396          .height(100)
397      }
398      Text(this.message)
399        .fontSize(30)
400        .height(100)
401      Button("Create Text")
402        .onClick(()=>{
403          this.showIf = true;
404        })
405      Button ('Close Second Dialog Box')
406        .onClick(() => {
407          if (this.controllerTwo != undefined) {
408            this.controllerTwo.close()
409          }
410        })
411        .margin(20)
412    }
413  }
414}
415@CustomDialog
416struct CustomDialogExample {
417  openSecondBox?: ()=>void
418  controller?: CustomDialogController
419
420  build() {
421    Column() {
422      Button ('Open Second Dialog Box and close this box')
423        .onClick(() => {
424          this.controller!.close();
425          this.openSecondBox!();
426        })
427        .margin(20)
428    }.borderRadius(10)
429  }
430}
431@Entry
432@Component
433struct CustomDialogUser {
434  @State inputValue: string = 'Click Me'
435  dialogController: CustomDialogController | null = new CustomDialogController({
436    builder: CustomDialogExample({
437      openSecondBox: ()=>{
438        if (this.dialogControllerTwo != null) {
439          this.dialogControllerTwo.open()
440        }
441      }
442    }),
443    cancel: this.exitApp,
444    autoCancel: true,
445    alignment: DialogAlignment.Bottom,
446    offset: { dx: 0, dy: -20 },
447    gridCount: 4,
448    customStyle: false
449  })
450  dialogControllerTwo: CustomDialogController | null = new CustomDialogController({
451    builder: CustomDialogExampleTwo(),
452    alignment: DialogAlignment.Bottom,
453    offset: { dx: 0, dy: -25 } })
454
455  aboutToDisappear() {
456    this.dialogController = null
457    this.dialogControllerTwo = null
458  }
459
460  onCancel() {
461    console.info('Callback when the first button is clicked')
462  }
463
464  onAccept() {
465    console.info('Callback when the second button is clicked')
466  }
467
468  exitApp() {
469    console.info('Click the callback in the blank area')
470  }
471  build() {
472    Column() {
473      Button(this.inputValue)
474        .onClick(() => {
475          if (this.dialogController != null) {
476            this.dialogController.open()
477          }
478        }).backgroundColor(0x317aff)
479    }.width('100%').margin({ top: 5 })
480  }
481}
482```
483![nested_dialog](figures/nested_dialog.gif)
484
485由于自定义弹窗在状态管理侧有父子关系,如果将第二个弹窗定义在第一个弹窗内,那么当父组件(第一个弹窗)被销毁(关闭)时,子组件(第二个弹窗)内无法再继续创建新的组件。
486
487## 相关实例
488
489针对自定义弹窗开发,有以下相关实例可供参考:
490
491- [自定义弹窗(ArkTS)(API9)](https://gitee.com/openharmony/codelabs/tree/master/ETSUI/CustomDialog)
492
493- [构建多种样式弹窗(ArkTS)(API9)](https://gitee.com/openharmony/codelabs/tree/master/ETSUI/MultipleDialog)
494
495- [目标管理(ArkTS)(API9)](https://gitee.com/openharmony/codelabs/tree/master/ETSUI/TargetManagement)