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