• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# 模态弹窗 (ModelDialog)
2
3## 概述
4
5模态(Modal)是UI组件或视图的一种状态。其在消失之前,用户只能对处于模态的组件或视图进行响应,不能操作其他非模态的组件或视图,干扰性比较强。
6
7ArkUI中可通过使用[AlertDialog](../reference/apis-arkui/arkui-ts/ts-methods-alert-dialog-box.md)、[CustomDialog](../reference/apis-arkui/arkui-ts/ts-methods-custom-dialog-box.md)、[ActionSheet](../reference/apis-arkui/arkui-ts/ts-methods-action-sheet.md)、[Popup](../reference/apis-arkui/arkui-ts/ts-universal-attributes-popup.md)、[Menu](../reference/apis-arkui/arkui-ts/ts-basic-components-menu.md)、[ContextMenu](../reference/apis-arkui/arkui-ts/ts-methods-menu.md)等组件实现模态类弹窗能力。
8
9| 名称                                 | 使用场景                                                     |
10| ------------------------------------ | ------------------------------------------------------------ |
11| AlertDialog | 通常用来展示用户当前需要或必须关注的信息或操作。如用户操作一个敏感行为时响应一个二次确认的弹窗。 |
12| ActionSheet | 当需要用户关注或确认的信息存在列表选择时使用。 |
13| CustomDialog | 当用户需要自定义弹窗内的组件和内容时使用。 |
14| Popup                                | 用于为指定的组件做信息提示。如点击一个问号图标弹出一段气泡提示。          |
15| Menu/ContextMenu                     | 用于给指定的组件绑定用户可执行的操作,如长按图标展示操作选项等。       |
16
17> **说明:**
18>
19> - 本指导介绍模态弹窗,开发者可通过配置参数(例如:CustomDialog的isModal)调整弹窗为非模态弹窗,从而满足不同的使用场景。
20>
21> - 移动设备中,子窗模式的弹窗当前无法超出主窗口。
22>
23> - 多个弹窗组件先后弹出时,后弹出的组件的层级高于先弹出的层级,退出时按照层级从高到低的顺序逐次退出。
24
25## 使用全局弹窗
26
27全局弹窗不与任何组件绑定,一般用于针对用户触发的操作进行必要提示时使用。ArkUI当前提供了定制和自定义两类弹窗组件。
28
29定制:AlertDialog、ActionSheet、promptAction.showDialogpromptAction.showActionMenu。开发者可使用此类组件,指定需要显示的文本内容和按钮操作即可完成简单的交互效果。
30
31自定义:CustomDialog、promptAction.openCustomDialog。开发者需要根据场景传入自定义组件填充在弹窗中实现自定义的弹窗内容。
32
33下面以AlertDialog、ActionSheet 和 CustomDialog 为例说明相应的弹窗效果与使用方法。
34
35- **AlertDialog:** 警告弹窗,需要向用户提问或得到用户的许可。
36  - 警告弹窗用来提示重要信息,但会中断当前任务,尽量提供必要的信息和有用的操作。
37  - 避免仅使用警告弹窗提供信息,用户不喜欢被信息丰富但不可操作的警告打断。
38  - 必选内容包含:标题、可选信息文本、最多3个按钮。
39  - 可选内容包含:输入框、icon、checkBox和HelpButton。
40
41  ![img](figures/AlertDialog.png)
42
43  ```ts
44  @Entry
45  @Component
46  struct AlertDialogExample {
47    build() {
48      Column({ space: 5 }) {
49        Button('two button dialog')
50          .onClick(() => {
51            AlertDialog.show(
52              {
53                title: 'title',
54                subtitle: 'subtitle',
55                message: 'text',
56                autoCancel: true,
57                alignment: DialogAlignment.Bottom,
58                gridCount: 4,
59                offset: { dx: 0, dy: -20 },
60                primaryButton: {
61                  value: 'cancel',
62                  action: () => {
63                    console.info('Callback when the first button is clicked')
64                  }
65                },
66                secondaryButton: {
67                  enabled: true,
68                  defaultFocus: true,
69                  style: DialogButtonStyle.HIGHLIGHT,
70                  value: 'ok',
71                  action: () => {
72                    console.info('Callback when the second button is clicked')
73                  }
74                }
75              }
76            )
77          }).backgroundColor(0x317aff)
78      }.width('100%').margin({ top: 5 })
79    }
80  }
81  ```
82
83- **ActionSheet:** 列表选择弹窗。
84
85  适合展示多个操作项,尤其是除了操作列表以外没有其他的展示内容。
86
87  ![img](figures/ActionSheet.png)
88
89
90  ```ts
91  @Entry
92  @Component
93  struct ActionSheetExample {
94    build() {
95      Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) {
96        Button('Click to Show ActionSheet')
97          .onClick(() => {
98            ActionSheet.show({
99              title: 'ActionSheet title',
100              subtitle: 'ActionSheet subtitle',
101              message: 'message',
102              autoCancel: true,
103              confirm: {
104                defaultFocus: true,
105                value: 'Confirm button',
106                action: () => {
107                  console.log('Get Alert Dialog handled')
108                }
109              },
110              alignment: DialogAlignment.Bottom,
111              offset: { dx: 0, dy: -10 },
112              sheets: [
113                {
114                  title: 'apples',
115                  action: () => {
116                    console.log('apples')
117                  }
118                },
119                {
120                  title: 'bananas',
121                  action: () => {
122                    console.log('bananas')
123                  }
124                },
125                {
126                  title: 'pears',
127                  action: () => {
128                    console.log('pears')
129                  }
130                }
131              ]
132            })
133          })
134      }.width('100%')
135      .height('100%')
136    }
137  }
138  ```
139
140- **CustomDialog:**  自定义弹窗。
141
142  当开发者需要自定义弹窗的内容和样式时,可选择CustomDialog。更建议使用[promptAction.openCustomDialog](../reference/apis-arkui/js-apis-promptAction.md#promptactionopencustomdialog11)。
143
144  ![img](figures/CustomDialog.png)
145
146  ```ts
147  // xxx.ets
148  @CustomDialog
149  @Component
150  struct CustomDialogExample {
151    @Link textValue: string
152    @Link inputValue: string
153    controller?: CustomDialogController
154    cancel: () => void = () => {
155    }
156    confirm: () => void = () => {
157    }
158
159    build() {
160      Column() {
161        Text('Change text').fontSize(20).margin({ top: 10, bottom: 10 })
162        TextInput({ placeholder: '', text: this.textValue }).height(60).width('90%')
163          .onChange((value: string) => {
164            this.textValue = value
165          })
166        Text('Whether to change a text?').fontSize(16).margin({ bottom: 10 })
167        Flex({ justifyContent: FlexAlign.SpaceAround }) {
168          Button('cancel')
169            .onClick(() => {
170              if (this.controller != undefined) {
171                this.controller.close()
172                this.cancel()
173              }
174            }).backgroundColor(0xffffff).fontColor(Color.Black)
175          Button('confirm')
176            .onClick(() => {
177              if (this.controller != undefined) {
178                this.inputValue = this.textValue
179                this.controller.close()
180                this.confirm()
181              }
182            }).backgroundColor(0xffffff).fontColor(Color.Red)
183        }.margin({ bottom: 10 })
184      }.borderRadius(10)
185    }
186  }
187
188  @Entry
189  @Component
190  struct CustomDialogUser {
191    @State textValue: string = ''
192    @State inputValue: string = 'click me'
193    dialogController: CustomDialogController | null = new CustomDialogController({
194      builder: CustomDialogExample({
195        cancel: () => {
196          this.onCancel()
197        },
198        confirm: () => {
199          this.onAccept()
200        },
201        textValue: $textValue,
202        inputValue: $inputValue
203      }),
204      cancel: this.exitApp,
205      autoCancel: true,
206      onWillDismiss: (dismissDialogAction: DismissDialogAction) => {
207        console.info("reason=" + JSON.stringify(dismissDialogAction.reason))
208        console.log("dialog onWillDismiss")
209        if (dismissDialogAction.reason == DismissReason.PRESS_BACK) {
210          dismissDialogAction.dismiss()
211        }
212        if (dismissDialogAction.reason == DismissReason.TOUCH_OUTSIDE) {
213          dismissDialogAction.dismiss()
214        }
215      },
216      alignment: DialogAlignment.Bottom,
217      offset: { dx: 0, dy: -20 },
218      gridCount: 4,
219      customStyle: false,
220      cornerRadius: 10,
221    })
222
223    // 在自定义组件即将析构销毁时将dialogController置空
224    aboutToDisappear() {
225      this.dialogController = null // 将dialogController置空
226    }
227
228    onCancel() {
229      console.info('Callback when the first button is clicked')
230    }
231
232    onAccept() {
233      console.info('Callback when the second button is clicked')
234    }
235
236    exitApp() {
237      console.info('Click the callback in the blank area')
238    }
239
240    build() {
241      Column() {
242        Button(this.inputValue)
243          .onClick(() => {
244            if (this.dialogController != null) {
245              this.dialogController.open()
246            }
247          }).backgroundColor(0x317aff)
248      }.width('100%').margin({ top: 5 })
249    }
250  }
251  ```
252
253## 使用气泡Popup
254
255当点击目标组件或交互区时,弹出内容在其他内容之上,可使用Popup来指示当前功能如何操作。
256
257![img](figures/Popup03.PNG)
258
259```ts
260@Entry
261@Component
262struct PopupExample {
263  @State handlePopup: boolean = false
264
265  build() {
266    Flex({ direction: FlexDirection.Column }) {
267      // PopupOptions 类型设置弹框内容
268      Button('PopupOptions')
269        .onClick(() => {
270          this.handlePopup = !this.handlePopup
271        })
272        .bindPopup(this.handlePopup, {
273          message: 'This is a popup with PopupOptions',
274          placementOnTop: true,
275          showInSubWindow: false,
276          primaryButton: {
277            value: 'confirm',
278            action: () => {
279              this.handlePopup = !this.handlePopup
280              console.info('confirm Button click')
281            }
282          },
283          // 第二个按钮
284          secondaryButton: {
285            value: 'cancel',
286            action: () => {
287              this.handlePopup = !this.handlePopup
288              console.info('cancel Button click')
289            }
290          },
291          onStateChange: (e) => {
292            console.info(JSON.stringify(e.isVisible))
293            if (!e.isVisible) {
294              this.handlePopup = false
295            }
296          }
297        })
298        .position({ x: 100, y: 150 })
299    }.width('100%').padding({ top: 5 })
300  }
301}
302```
303
304## 使用菜单Menu
305
306很多时候需要通过交互弹出一些菜单选项,用于用户操作。此时可通过Menu和MenuItem组件组合成需要弹出的菜单选项内容,然后借助bindMenu和bindContextMenu方法将菜单和组件绑定。
307
308| 方法名                   | 使用场景                   |
309| -------------------------- | -------------------------- |
310| bindMenu | 无需预览图场景,需要在非子窗场景显示。 |
311| bindContextMenu | 需要预览图场景使用,只能在子窗中显示。 |
312
313- **bindMenu**:一种临时性弹出组件,用于展示用户可执行的操作。
314
315  | 一级Menu                   | 多级Menu                   |
316  | -------------------------- | -------------------------- |
317  | ![img](figures/Menu01.png) | ![img](figures/Menu02.png) |
318
319	```ts
320  @Entry
321	@Component
322	struct Index {
323	  @State select: boolean = true
324
325	  @Builder
326	  MyMenu() {
327	    Menu() {
328	      MenuItem({ content: "菜单选项" })
329	      MenuItem({ content: "菜单选项" })
330	      MenuItem({ content: "菜单选项" })
331	      MenuItem({ content: "菜单选项" })
332	    }
333	  }
334
335	  build() {
336	    Row() {
337	      Column() {
338	        Text('click to show menu')
339	          .fontSize(50)
340	          .fontWeight(FontWeight.Bold)
341	      }
342	      .bindMenu(this.MyMenu)
343	      .width('100%')
344	    }
345	    .height('100%')
346	  }
347	}
348	```
349
350
351- **bindContextMenu:** 内容包括菜单、预览图、蒙层,通常在长按桌面图标时使用。
352
353  | 相对父组件区域弹出                | 相对点击位置弹出                  |
354  | --------------------------------- | --------------------------------- |
355  | ![img](figures/ContextMenu03.png) | ![img](figures/ContextMenu04.png) |
356
357  ```ts
358  @Entry
359  @Component
360  struct Index {
361    @State select: boolean = true
362
363    @Builder
364    MyMenu(){
365      Menu() {
366        MenuItem({ content: "菜单选项" })
367        MenuItem({ content: "菜单选项" })
368        MenuItem({ content: "菜单选项" })
369        MenuItem({ content: "菜单选项" })
370      }
371    }
372
373    build() {
374      Row() {
375        Column() {
376          Text('click to show menu')
377            .fontSize(50)
378            .fontWeight(FontWeight.Bold)
379        }
380        .bindContextMenu(this.MyMenu, ResponseType.LongPress,{
381          placement: Placement.Left,
382          preview: MenuPreviewMode.IMAGE
383        })
384        .width('100%')
385      }
386      .height('100%')
387    }
388  }
389  ```
390
391
392## 超出应用界面
393
394在2in1设备上,使用模态类弹窗时,会出现超出主窗口显示的场景,如下图所示。
395
396![img](figures/Dialog01.png)
397
398开发者可通过[支持子窗口](#支持子窗口)和[默认子窗口](#默认子窗口)实现超出应用界面效果。
399
400### 支持子窗口
401
402自定义弹窗(CustomDialog)、警告弹窗(AlertDialog)、列表选择弹窗(ActionSheet)、气泡提示(Popup)可通过showInSubWindow,设置弹窗或气泡在子窗口中,从而实现超出主窗口的显示效果。
403
404```ts
405// xxx.ets
406@CustomDialog
407struct CustomDialogExample {
408  controller?: CustomDialogController
409  cancel: () => void = () => {
410  }
411  confirm: () => void = () => {
412  }
413  build() {
414    Column() {
415      Text('可展示在主窗口外的弹窗')
416        .fontSize(30)
417        .height(100)
418      Button('点我关闭弹窗')
419        .onClick(() => {
420          if (this.controller != undefined) {
421            this.controller.close()
422          }
423        })
424        .margin(20)
425    }
426  }
427}
428@Entry
429@Component
430struct CustomDialogUser {
431  dialogController: CustomDialogController | null = new CustomDialogController({
432    builder: CustomDialogExample({
433      cancel: ()=> { this.onCancel() },
434      confirm: ()=> { this.onAccept() }
435    }),
436    cancel: this.existApp,
437    autoCancel: true,
438    alignment: DialogAlignment.Center,
439    offset: { dx: 0, dy: -20 },
440    gridCount: 4,
441    showInSubWindow: true,
442    isModal: true,
443    customStyle: false,
444    cornerRadius: 10,
445  })
446  // 在自定义组件即将析构销毁时将dialogController置空
447  aboutToDisappear() {
448    this.dialogController = null // 将dialogController置空
449  }
450
451  onCancel() {
452    console.info('Callback when the first button is clicked')
453  }
454
455  onAccept() {
456    console.info('Callback when the second button is clicked')
457  }
458
459  existApp() {
460    console.info('Click the callback in the blank area')
461  }
462
463  build() {
464    Column() {
465      Button('click me')
466        .onClick(() => {
467          if (this.dialogController != null) {
468            this.dialogController.open()
469          }
470        }).backgroundColor(0x317aff)
471    }.width('100%').margin({ top: 5 })
472  }
473}
474```
475
476### 默认子窗口
477
478可使用 bindContextMenu为组件绑定菜单,触发方式为长按或者右键点击,弹出菜单项需要自定义。
479
480```ts
481@Entry
482@Component
483struct ContextMenuExample {
484  @Builder MenuBuilder() {
485    Flex({ direction: FlexDirection.Column, justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) {
486      Text('Test menu item 1')
487        .fontSize(20)
488        .width(100)
489        .height(50)
490        .textAlign(TextAlign.Center)
491      Divider().height(10)
492      Text('Test menu item 2')
493        .fontSize(20)
494        .width(100)
495        .height(50)
496        .textAlign(TextAlign.Center)
497    }.width(100)
498  }
499
500  build() {
501    Column() {
502      Text('LongPress for menu')
503    }
504    .width('100%')
505    .margin({ top: 5 })
506    .bindContextMenu(this.MenuBuilder, ResponseType.LongPress)
507  }
508}
509```
510
511