1# Modal Transition
2
3
4Modal transition is a type of transition achieved by a modal – a view that appears on top of the current view while the current view remains.
5
6
7**Table 1** Modal transition APIs
8| API                                      | Description               | Usage                                    |
9| ---------------------------------------- | ----------------- | ---------------------------------------- |
10| [bindContentCover](../reference/apis-arkui/arkui-ts/ts-universal-attributes-modal-transition.md#bindcontentcover) | Binds a modal to the component.       | Use this API to display a custom modal. It can work with the transition animation and shared element animation to implement complex transition animation effects, for example, displaying an image in full in the modal upon the click of a thumbnail.|
11| [bindSheet](../reference/apis-arkui/arkui-ts/ts-universal-attributes-sheet-transition.md#bindsheet) | Binds a sheet to the component.         | Use this API to display a custom sheet, for example, a sharing confirmation dialog box.                         |
12| [bindMenu](../reference/apis-arkui/arkui-ts/ts-universal-attributes-menu.md#bindmenu11) | Binds a menu to the component, which is displayed when the component is clicked.    | Use this API where a menu is required, for example, for the plus sign (+), a common menu indicator in applications.                |
13| [bindContextMenu](../reference/apis-arkui/arkui-ts/ts-universal-attributes-menu.md#bindcontextmenu12) | Binds a context menu to the component, which is displayed when the user long-presses or right-clicks the component.| Use this API for components that bounce up when long-pressed, for example, home screen icons.            |
14| [bindPopup](../reference/apis-arkui/arkui-ts/ts-universal-attributes-popup.md#bindpopup) | Binds a popup to the component.       | Use this API to display a popup containing additional information about a component when the component is clicked.              |
15| [if](../quick-start/arkts-rendering-control-ifelse.md)                                       | Adds or deletes the component.     | Use this API to display a temporary page in a certain state. In this mode, the return navigation needs to be implemented with a listener. |
16
17
18## Creating Modal Transition with bindContentCover
19
20You can bind a full-screen modal to a component through the [bindContentCover](../reference/apis-arkui/arkui-ts/ts-universal-attributes-modal-transition.md#bindcontentcover) attribute. Better yet, with the **ModalTransition** parameter, you can apply a transition effect for when the component appears or disappears.
21
221. Define [bindContentCover](../reference/apis-arkui/arkui-ts/ts-universal-attributes-modal-transition.md#bindcontentcover).
23
242. Define the modal view.
25
26   ```ts
27   // Use @Builder to build a modal view.
28   @Builder MyBuilder() {
29     Column() {
30       Text('my model view')
31     }
32     // Use the transition API to implement the transition animation for component appearance and disappearance. The transition API must be added to the first component of the builder.
33     .transition(TransitionEffect.translate({ y: 1000 }).animation({ curve: curves.springMotion(0.6, 0.8) }))
34   }
35   ```
36
373. Call the modal API to display the modal. Implement an animation by using the animation or shared element transition APIs.
38
39   ```ts
40   // Define the state variable to control the visibility of the modal.
41   @State isPresent: boolean = false;
42
43   Button('Click to present model view')
44     // Bind a modal to the component. ModalTransition.NONE means not to use the default transition animation for the modal. You can use onDisappear to control state variable changes.
45     .bindContentCover(this.isPresent, this.MyBuilder(), {
46               modalTransition: ModalTransition.NONE,
47               onDisappear: () => {
48                 this.isPresent = !this.isPresent;
49               }
50             })
51     .onClick(() => {
52       // Change the state variable to display the modal.
53       this.isPresent = !this.isPresent;
54     })
55   ```
56
57
58Below is the complete sample code and effect.
59
60
61
62```ts
63import { curves } from '@kit.ArkUI';
64
65interface PersonList {
66  name: string,
67  cardnum: string
68}
69
70@Entry
71@Component
72struct BindContentCoverDemo {
73  private personList: Array<PersonList> = [
74    { name: 'Wang **', cardnum: '1234***********789' },
75    { name: 'Song*', cardnum: '2345***********789' },
76    { name: 'Xu **', cardnum: '3456***********789' },
77    { name: 'Tang*', cardnum: '4567***********789' }
78  ];
79  // Step 1: Define bindContentCover.
80  // Define the state variable to control the visibility of the modal.
81  @State isPresent: boolean = false;
82
83  // Step 2: Define the modal view.
84  // Use @Builder to build a modal view.
85  @Builder
86  MyBuilder() {
87    Column() {
88      Row() {
89        Text('Select passengers')
90          .fontSize(20)
91          .fontColor(Color.White)
92          .width('100%')
93          .textAlign(TextAlign.Center)
94          .padding({ top: 30, bottom: 15 })
95      }
96      .backgroundColor(0x007dfe)
97
98      Row() {
99        Text('+ Add')
100          .fontSize(16)
101          .fontColor(0x333333)
102          .margin({ top: 10 })
103          .padding({ top: 20, bottom: 20 })
104          .width('92%')
105          .borderRadius(10)
106          .textAlign(TextAlign.Center)
107          .backgroundColor(Color.White)
108      }
109
110      Column() {
111        ForEach(this.personList, (item: PersonList, index: number) => {
112          Row() {
113            Column() {
114              if (index % 2 == 0) {
115                Column()
116                  .width(20)
117                  .height(20)
118                  .border({ width: 1, color: 0x007dfe })
119                  .backgroundColor(0x007dfe)
120              } else {
121                Column()
122                  .width(20)
123                  .height(20)
124                  .border({ width: 1, color: 0x007dfe })
125              }
126            }
127            .width('20%')
128
129            Column() {
130              Text(item.name)
131                .fontColor(0x333333)
132                .fontSize(18)
133              Text(item.cardnum)
134                .fontColor(0x666666)
135                .fontSize(14)
136            }
137            .width('60%')
138            .alignItems(HorizontalAlign.Start)
139
140            Column() {
141              Text ('Edit')
142                .fontColor(0x007dfe)
143                .fontSize(16)
144            }
145            .width('20%')
146          }
147          .padding({ top: 10, bottom: 10 })
148          .border({ width: { bottom: 1 }, color: 0xf1f1f1 })
149          .width('92%')
150          .backgroundColor(Color.White)
151        })
152      }
153      .padding({ top: 20, bottom: 20 })
154
155      Text ('OK')
156        .width('90%')
157        .height(40)
158        .textAlign(TextAlign.Center)
159        .borderRadius(10)
160        .fontColor(Color.White)
161        .backgroundColor(0x007dfe)
162        .onClick(() => {
163          this.isPresent = !this.isPresent;
164        })
165    }
166    .size({ width: '100%', height: '100%' })
167    .backgroundColor(0xf5f5f5)
168    // Use the transition API to implement the transition animation for component appearance and disappearance.
169    .transition(TransitionEffect.translate({ y: 1000 }).animation({ curve: curves.springMotion(0.6, 0.8) }))
170  }
171
172  build() {
173    Column() {
174      Row() {
175        Text('Ticket details')
176          .fontSize(20)
177          .fontColor(Color.White)
178          .width('100%')
179          .textAlign(TextAlign.Center)
180          .padding({ top: 30, bottom: 60 })
181      }
182      .backgroundColor(0x007dfe)
183
184      Column() {
185        Row() {
186          Column() {
187            Text('00:25')
188            Text('From')
189          }
190          .width('30%')
191
192          Column() {
193            Text('G1234')
194            Text ('8 h 1 min')
195          }
196          .width('30%')
197
198          Column() {
199            Text('08:26')
200            Text ('To')
201          }
202          .width('30%')
203        }
204      }
205      .width('92%')
206      .padding(15)
207      .margin({ top: -30 })
208      .backgroundColor(Color.White)
209      .shadow({ radius: 30, color: '#aaaaaa' })
210      .borderRadius(10)
211
212      Column() {
213        Text('+ Select passengers')
214          .fontSize(18)
215          .fontColor(Color.Orange)
216          .fontWeight(FontWeight.Bold)
217          .padding({ top: 10, bottom: 10 })
218          .width('60%')
219          .textAlign(TextAlign.Center)
220          .borderRadius(15)// Bind a modal to the component. ModalTransition.DEFAULT means to use the slide-up and slide-down animation type. You can use onDisappear to control state variable changes.
221          .bindContentCover(this.isPresent, this.MyBuilder(), {
222            modalTransition: ModalTransition.DEFAULT,
223            onDisappear: () => {
224              this.isPresent = !this.isPresent;
225            }
226          })
227          .onClick(() => {
228            // Step 3: Call the modal API to display the modal. Implement an animation by using the animation or shared element transition APIs.
229            // Change the state variable to display the modal.
230            this.isPresent = !this.isPresent;
231          })
232      }
233      .padding({ top: 60 })
234    }
235  }
236}
237```
238
239
240
241![en-us_image_0000001646921957](figures/en-us_image_0000001646921957.gif)
242
243
244
245## Creating Sheet Transition with bindSheet
246
247You can bind a sheet to a component through the [bindSheet](../reference/apis-arkui/arkui-ts/ts-universal-attributes-sheet-transition.md#bindsheet) attribute. You can also set the sheet to the preset or custom height for when the component appears. The process of creating a sheet transition is basically the same as that of creating a modal transition using [bindContentCover](../reference/apis-arkui/arkui-ts/ts-universal-attributes-modal-transition.md#bindcontentcover).
248
249Below is the complete sample code and effect.
250
251
252```ts
253@Entry
254@Component
255struct BindSheetDemo {
256  // Set visibility of the sheet.
257  @State isShowSheet: boolean = false;
258  private menuList: string[] = ['No ice', 'Light ice', 'Extra ice', 'Light sauce', 'Extra sauce', 'Plastic cutlery', 'No plastic cutlery'];
259
260  // Use @Builder to build a sheet view.
261  @Builder
262  mySheet() {
263    Column() {
264      Flex({ direction: FlexDirection.Row, wrap: FlexWrap.Wrap }) {
265        ForEach(this.menuList, (item: string) => {
266          Text(item)
267            .fontSize(16)
268            .fontColor(0x333333)
269            .backgroundColor(0xf1f1f1)
270            .borderRadius(8)
271            .margin(10)
272            .padding(10)
273        })
274      }
275      .padding({ top: 18 })
276    }
277    .width('100%')
278    .height('100%')
279    .backgroundColor(Color.White)
280  }
281
282  build() {
283    Column() {
284      Text ('Preferences')
285        .fontSize(28)
286        .padding({ top: 30, bottom: 30 })
287      Column() {
288        Row() {
289          Row()
290            .width(10)
291            .height(10)
292            .backgroundColor('#a8a8a8')
293            .margin({ right: 12 })
294            .borderRadius(20)
295
296          Column() {
297            Text('Customize')
298              .fontSize(16)
299              .fontWeight(FontWeight.Medium)
300          }
301          .alignItems(HorizontalAlign.Start)
302
303          Blank()
304
305          Row()
306            .width(12)
307            .height(12)
308            .margin({ right: 15 })
309            .border({
310              width: { top: 2, right: 2 },
311              color: 0xcccccc
312            })
313            .rotate({ angle: 45 })
314        }
315        .borderRadius(15)
316        .shadow({ radius: 100, color: '#ededed' })
317        .width('90%')
318        .alignItems(VerticalAlign.Center)
319        .padding({ left: 15, top: 15, bottom: 15 })
320        .backgroundColor(Color.White)
321        // Bind a sheet to the component. Set height (sheet height; large by default) and DragBar (whether to display the drag bar; true by default). You can use onDisappear to control state variable changes.
322        .bindSheet(this.isShowSheet, this.mySheet(), {
323          height: 300,
324          dragBar: false,
325          onDisappear: () => {
326            this.isShowSheet = !this.isShowSheet;
327          }
328        })
329        .onClick(() => {
330          this.isShowSheet = !this.isShowSheet;
331        })
332      }
333      .width('100%')
334    }
335    .width('100%')
336    .height('100%')
337    .backgroundColor(0xf1f1f1)
338  }
339}
340```
341
342![en-us_image_0000001599977924](figures/en-us_image_0000001599977924.gif)
343
344
345## Creating a Menu with bindMenu
346
347You can bind a menu to component through the [bindMenu](../reference/apis-arkui/arkui-ts/ts-universal-attributes-menu.md#bindmenu) attribute. The menu can then be triggered by clicking. Below is the complete sample code and effect.
348
349
350```ts
351class BMD{
352  value:ResourceStr = ''
353  action:() => void = () => {}
354}
355@Entry
356@Component
357struct BindMenuDemo {
358
359  // Step 1: Define a data array to represent menu items.
360  @State items:BMD[] = [
361    {
362      value:'Menu item 1',
363      action: () => {
364        console.info('handle Menu1 select')
365      }
366    },
367    {
368      value:'Menu item 2',
369      action: () => {
370        console.info('handle Menu2 select')
371      }
372    },
373  ]
374
375  build() {
376    Column() {
377      Button('click')
378        .backgroundColor(0x409eff)
379        .borderRadius(5)
380          // Step 2: Bind the menu data to the component through bindMenu.
381        .bindMenu(this.items)
382    }
383    .justifyContent(FlexAlign.Center)
384    .width('100%')
385    .height(437)
386  }
387}
388```
389
390![en-us_image_0000001599643478](figures/en-us_image_0000001599643478.gif)
391
392
393## Creating a Context Menu with bindContextMenu
394
395You can bind a menu to component through the [bindContextMenu](../reference/apis-arkui/arkui-ts/ts-universal-attributes-menu.md#bindcontextmenu8) attribute. The menu can then be triggered by long-pressing or right-clicking.  
396
397Below is the complete sample code and effect.
398
399
400```ts
401@Entry
402@Component
403struct BindContextMenuDemo {
404  private menu: string[] = ['Save', 'Favorite', 'Search'];
405  private pics: Resource[] = [$r('app.media.icon_1'), $r('app.media.icon_2')];
406
407  // Use @Builder to build custom menu items.
408  @Builder myMenu() {
409    Column() {
410      ForEach(this.menu, (item: string) => {
411        Row() {
412          Text(item)
413            .fontSize(18)
414            .width('100%')
415            .textAlign(TextAlign.Center)
416        }
417        .padding(15)
418        .border({ width: { bottom: 1 }, color: 0xcccccc })
419      })
420    }
421    .width(140)
422    .borderRadius(15)
423    .shadow({ radius: 15, color: 0xf1f1f1 })
424    .backgroundColor(0xf1f1f1)
425  }
426
427  build() {
428    Column() {
429      Row() {
430        Text ('View image')
431          .fontSize(20)
432          .fontColor(Color.White)
433          .width('100%')
434          .textAlign(TextAlign.Center)
435          .padding({ top: 20, bottom: 20 })
436      }
437      .backgroundColor(0x007dfe)
438
439      Column() {
440        ForEach(this.pics, (item: Resource) => {
441          Row(){
442            Image(item)
443              .width('100%')
444              .draggable(false)
445          }
446          .padding({ top: 20, bottom: 20, left: 10, right: 10 })
447          .bindContextMenu(this.myMenu, ResponseType.LongPress)
448        })
449      }
450    }
451    .width('100%')
452    .alignItems(HorizontalAlign.Center)
453  }
454}
455```
456
457![en-us_image_0000001600137920](figures/en-us_image_0000001600137920.gif)
458
459
460## Creating a Popup with bindPopUp
461
462You can bind a popup to a component through the [bindpopup](../reference/apis-arkui/arkui-ts/ts-universal-attributes-popup.md#bindpopup) attribute, specifying its content, interaction logic, and display status.
463
464Below is the complete sample code and effect.
465
466
467```ts
468@Entry
469@Component
470struct BindPopupDemo {
471
472  // Step 1: Define the state variable to control the visibility of the popup.
473  @State customPopup: boolean = false;
474
475  // Step 2: Use @Builder to build a custom popup.
476  @Builder popupBuilder() {
477    Column({ space: 2 }) {
478      Row().width(64)
479        .height(64)
480        .backgroundColor(0x409eff)
481      Text('Popup')
482        .fontSize(10)
483        .fontColor(Color.White)
484    }
485    .justifyContent(FlexAlign.SpaceAround)
486    .width(100)
487    .height(100)
488    .padding(5)
489  }
490
491  build() {
492    Column() {
493
494      Button('click')
495        // Step 4: Add a click event to control the visibility of the popup.
496        .onClick(() => {
497          this.customPopup = !this.customPopup;
498        })
499        .backgroundColor(0xf56c6c)
500          // Step 5: Bind the popup to the component through bindPopup.
501        .bindPopup(this.customPopup, {
502          builder: this.popupBuilder,
503          placement: Placement.Top,
504          maskColor: 0x33000000,
505          popupColor: 0xf56c6c,
506          enableArrow: true,
507          onStateChange: (e) => {
508            if (!e.isVisible) {
509              this.customPopup = false;
510            }
511          }
512        })
513    }
514    .justifyContent(FlexAlign.Center)
515    .width('100%')
516    .height(437)
517  }
518}
519```
520
521
522
523![en-us_image_0000001649282285](figures/en-us_image_0000001649282285.gif)
524
525
526## Creating Modal Transition with if
527
528In addition to the preceding modal transition APIs, you can also use the **if** syntax to create a modal transition, eliminating the need for binding to the component and listening for state variable changes.
529
530Below is the complete sample code and effect.
531
532
533```ts
534@Entry
535@Component
536struct ModalTransitionWithIf {
537  private listArr: string[] = ['WLAN', 'Bluetooth', 'Personal hotspot', 'Connected devices'];
538  private shareArr: string[] = ['Projection', 'Printing', 'VPN','Private DNS', 'NFC'];
539  // Step 1: Define a state variable to control page display.
540  @State isShowShare: boolean = false;
541  private shareFunc(): void {
542    animateTo({ duration: 500 }, () => {
543      this.isShowShare = !this.isShowShare;
544    })
545  }
546
547  build(){
548    // Step 2: Define a stack layout to display the current view and modal view.
549    Stack() {
550      Column() {
551        Column() {
552          Text('Settings')
553            .fontSize(28)
554            .fontColor(0x333333)
555        }
556        .width('90%')
557        .padding({ top: 30, bottom: 15 })
558        .alignItems(HorizontalAlign.Start)
559
560        TextInput({ placeholder: 'Search by keyword' })
561          .width('90%')
562          .height(40)
563          .margin({ bottom: 10 })
564          .focusable(false)
565
566        List({ space: 12, initialIndex: 0 }) {
567          ForEach(this.listArr, (item: string, index: number) => {
568            ListItem() {
569              Row() {
570                Row() {
571                  Text(`${item.slice(0, 1)}`)
572                    .fontColor(Color.White)
573                    .fontSize(14)
574                    .fontWeight(FontWeight.Bold)
575                }
576                .width(30)
577                .height(30)
578                .backgroundColor('#a8a8a8')
579                .margin({ right: 12 })
580                .borderRadius(20)
581                .justifyContent(FlexAlign.Center)
582
583                Column() {
584                  Text(item)
585                    .fontSize(16)
586                    .fontWeight(FontWeight.Medium)
587                }
588                .alignItems(HorizontalAlign.Start)
589
590                Blank()
591
592                Row()
593                  .width(12)
594                  .height(12)
595                  .margin({ right: 15 })
596                  .border({
597                    width: { top: 2, right: 2 },
598                    color: 0xcccccc
599                  })
600                  .rotate({ angle: 45 })
601              }
602              .borderRadius(15)
603              .shadow({ radius: 100, color: '#ededed' })
604              .width('90%')
605              .alignItems(VerticalAlign.Center)
606              .padding({ left: 15, top: 15, bottom: 15 })
607              .backgroundColor(Color.White)
608            }
609            .width('100%')
610            .onClick(() => {
611              // Step 3: Change the state variable to display the modal view.
612              if(item.slice(-2) === 'Share'){
613                this.shareFunc();
614              }
615            })
616          }, (item: string): string => item)
617        }
618        .width('100%')
619      }
620      .width('100%')
621      .height('100%')
622      .backgroundColor(0xfefefe)
623
624      // Step 4: Define the modal view in if and display it at the top layer. Use if to control the appearance and disappearance of the modal view.
625      if(this.isShowShare){
626        Column() {
627          Column() {
628            Row() {
629              Row() {
630                Row()
631                  .width(16)
632                  .height(16)
633                  .border({
634                    width: { left: 2, top: 2 },
635                    color: 0x333333
636                  })
637                  .rotate({ angle: -45 })
638              }
639              .padding({ left: 15, right: 10 })
640              .onClick(() => {
641                this.shareFunc();
642              })
643              Text('Connected devices')
644                .fontSize(28)
645                .fontColor(0x333333)
646            }
647            .padding({ top: 30 })
648          }
649          .width('90%')
650          .padding({bottom: 15})
651          .alignItems(HorizontalAlign.Start)
652
653          List({ space: 12, initialIndex: 0 }) {
654            ForEach(this.shareArr, (item: string) => {
655              ListItem() {
656                Row() {
657                  Row() {
658                    Text(`${item.slice(0, 1)}`)
659                      .fontColor(Color.White)
660                      .fontSize(14)
661                      .fontWeight(FontWeight.Bold)
662                  }
663                  .width(30)
664                  .height(30)
665                  .backgroundColor('#a8a8a8')
666                  .margin({ right: 12 })
667                  .borderRadius(20)
668                  .justifyContent(FlexAlign.Center)
669
670                  Column() {
671                    Text(item)
672                      .fontSize(16)
673                      .fontWeight(FontWeight.Medium)
674                  }
675                  .alignItems(HorizontalAlign.Start)
676
677                  Blank()
678
679                  Row()
680                    .width(12)
681                    .height(12)
682                    .margin({ right: 15 })
683                    .border({
684                      width: { top: 2, right: 2 },
685                      color: 0xcccccc
686                    })
687                    .rotate({ angle: 45 })
688                }
689                .borderRadius(15)
690                .shadow({ radius: 100, color: '#ededed' })
691                .width('90%')
692                .alignItems(VerticalAlign.Center)
693                .padding({ left: 15, top: 15, bottom: 15 })
694                .backgroundColor(Color.White)
695              }
696              .width('100%')
697            }, (item: string): string => item)
698          }
699          .width('100%')
700        }
701        .width('100%')
702        .height('100%')
703        .backgroundColor(0xffffff)
704        // Step 5: Define the mode in which the modal view disappears.
705        .transition(TransitionEffect.OPACITY
706          .combine(TransitionEffect.translate({ x: '100%' }))
707          .combine(TransitionEffect.scale({ x: 0.95, y: 0.95 })))
708      }
709    }
710  }
711}
712```
713
714![en-us_image_0000001597792146](figures/en-us_image_0000001597792146.gif)
715