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                 if (this.isPresent) {
49                   this.isPresent = !this.isPresent;
50                 }
51               }
52             })
53     .onClick(() => {
54       // Change the state variable to display the modal.
55       this.isPresent = !this.isPresent;
56     })
57   ```
58
59
60Below is the complete sample code and effect.
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              if (this.isPresent) {
225                this.isPresent = !this.isPresent;
226              }
227            }
228          })
229          .onClick(() => {
230            // Step 3: Call the modal API to display the modal. Implement an animation by using the animation or shared element transition APIs.
231            // Change the state variable to display the modal.
232            this.isPresent = !this.isPresent;
233          })
234      }
235      .padding({ top: 60 })
236    }
237  }
238}
239```
240
241
242
243![en-us_image_0000001646921957](figures/en-us_image_0000001646921957.gif)
244
245
246
247## Creating Sheet Transition with bindSheet
248
249You 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).
250
251Below is the complete sample code and effect.
252
253
254```ts
255@Entry
256@Component
257struct BindSheetDemo {
258  // Set visibility of the sheet.
259  @State isShowSheet: boolean = false;
260  private menuList: string[] = ['No ice', 'Light ice', 'Extra ice', 'Light sauce', 'Extra sauce', 'Plastic cutlery', 'No plastic cutlery'];
261
262  // Use @Builder to build a sheet view.
263  @Builder
264  mySheet() {
265    Column() {
266      Flex({ direction: FlexDirection.Row, wrap: FlexWrap.Wrap }) {
267        ForEach(this.menuList, (item: string) => {
268          Text(item)
269            .fontSize(16)
270            .fontColor(0x333333)
271            .backgroundColor(0xf1f1f1)
272            .borderRadius(8)
273            .margin(10)
274            .padding(10)
275        })
276      }
277      .padding({ top: 18 })
278    }
279    .width('100%')
280    .height('100%')
281    .backgroundColor(Color.White)
282  }
283
284  build() {
285    Column() {
286      Text('Preferences')
287        .fontSize(28)
288        .padding({ top: 30, bottom: 30 })
289      Column() {
290        Row() {
291          Row()
292            .width(10)
293            .height(10)
294            .backgroundColor('#a8a8a8')
295            .margin({ right: 12 })
296            .borderRadius(20)
297
298          Column() {
299            Text('Customize')
300              .fontSize(16)
301              .fontWeight(FontWeight.Medium)
302          }
303          .alignItems(HorizontalAlign.Start)
304
305          Blank()
306
307          Row()
308            .width(12)
309            .height(12)
310            .margin({ right: 15 })
311            .border({
312              width: { top: 2, right: 2 },
313              color: 0xcccccc
314            })
315            .rotate({ angle: 45 })
316        }
317        .borderRadius(15)
318        .shadow({ radius: 100, color: '#ededed' })
319        .width('90%')
320        .alignItems(VerticalAlign.Center)
321        .padding({ left: 15, top: 15, bottom: 15 })
322        .backgroundColor(Color.White)
323        // 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.
324        .bindSheet(this.isShowSheet, this.mySheet(), {
325          height: 300,
326          dragBar: false,
327          onDisappear: () => {
328            this.isShowSheet = !this.isShowSheet;
329          }
330        })
331        .onClick(() => {
332          this.isShowSheet = !this.isShowSheet;
333        })
334      }
335      .width('100%')
336    }
337    .width('100%')
338    .height('100%')
339    .backgroundColor(0xf1f1f1)
340  }
341}
342```
343
344![en-us_image_0000001599977924](figures/en-us_image_0000001599977924.gif)
345
346
347## Creating a Menu with bindMenu
348
349You 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.
350
351
352```ts
353class BMD{
354  value:ResourceStr = ''
355  action:() => void = () => {}
356}
357@Entry
358@Component
359struct BindMenuDemo {
360
361  // Step 1: Define a data array to represent menu items.
362  @State items:BMD[] = [
363    {
364      value:'Menu item 1',
365      action: () => {
366        console.info('handle Menu1 select')
367      }
368    },
369    {
370      value:'Menu item 2',
371      action: () => {
372        console.info('handle Menu2 select')
373      }
374    },
375  ]
376
377  build() {
378    Column() {
379      Button('click')
380        .backgroundColor(0x409eff)
381        .borderRadius(5)
382          // Step 2: Bind the menu data to the component through bindMenu.
383        .bindMenu(this.items)
384    }
385    .justifyContent(FlexAlign.Center)
386    .width('100%')
387    .height(437)
388  }
389}
390```
391
392![en-us_image_0000001599643478](figures/en-us_image_0000001599643478.gif)
393
394
395## Creating a Context Menu with bindContextMenu
396
397You 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.
398
399Below is the complete sample code and effect.
400
401
402```ts
403@Entry
404@Component
405struct BindContextMenuDemo {
406  private menu: string[] = ['Save', 'Favorite', 'Search'];
407  private pics: Resource[] = [$r('app.media.icon_1'), $r('app.media.icon_2')];
408
409  // Use @Builder to build custom menu items.
410  @Builder myMenu() {
411    Column() {
412      ForEach(this.menu, (item: string) => {
413        Row() {
414          Text(item)
415            .fontSize(18)
416            .width('100%')
417            .textAlign(TextAlign.Center)
418        }
419        .padding(15)
420        .border({ width: { bottom: 1 }, color: 0xcccccc })
421      })
422    }
423    .width(140)
424    .borderRadius(15)
425    .shadow({ radius: 15, color: 0xf1f1f1 })
426    .backgroundColor(0xf1f1f1)
427  }
428
429  build() {
430    Column() {
431      Row() {
432        Text('View image')
433          .fontSize(20)
434          .fontColor(Color.White)
435          .width('100%')
436          .textAlign(TextAlign.Center)
437          .padding({ top: 20, bottom: 20 })
438      }
439      .backgroundColor(0x007dfe)
440
441      Column() {
442        ForEach(this.pics, (item: Resource) => {
443          Row(){
444            Image(item)
445              .width('100%')
446              .draggable(false)
447          }
448          .padding({ top: 20, bottom: 20, left: 10, right: 10 })
449          .bindContextMenu(this.myMenu, ResponseType.LongPress)
450        })
451      }
452    }
453    .width('100%')
454    .alignItems(HorizontalAlign.Center)
455  }
456}
457```
458
459![en-us_image_0000001600137920](figures/en-us_image_0000001600137920.gif)
460
461
462## Creating a Popup with bindPopUp
463
464You 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.
465
466Below is the complete sample code and effect.
467
468
469```ts
470@Entry
471@Component
472struct BindPopupDemo {
473
474  // Step 1: Define the state variable to control the visibility of the popup.
475  @State customPopup: boolean = false;
476
477  // Step 2: Use @Builder to build a custom popup.
478  @Builder popupBuilder() {
479    Column({ space: 2 }) {
480      Row().width(64)
481        .height(64)
482        .backgroundColor(0x409eff)
483      Text('Popup')
484        .fontSize(10)
485        .fontColor(Color.White)
486    }
487    .justifyContent(FlexAlign.SpaceAround)
488    .width(100)
489    .height(100)
490    .padding(5)
491  }
492
493  build() {
494    Column() {
495
496      Button('click')
497        // Step 4: Add a click event to control the visibility of the popup.
498        .onClick(() => {
499          this.customPopup = !this.customPopup;
500        })
501        .backgroundColor(0xf56c6c)
502          // Step 5: Bind the popup to the component through bindPopup.
503        .bindPopup(this.customPopup, {
504          builder: this.popupBuilder,
505          placement: Placement.Top,
506          maskColor: 0x33000000,
507          popupColor: 0xf56c6c,
508          enableArrow: true,
509          onStateChange: (e) => {
510            if (!e.isVisible) {
511              this.customPopup = false;
512            }
513          }
514        })
515    }
516    .justifyContent(FlexAlign.Center)
517    .width('100%')
518    .height(437)
519  }
520}
521```
522
523
524
525![en-us_image_0000001649282285](figures/en-us_image_0000001649282285.gif)
526
527
528## Creating Modal Transition with if
529
530In 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.
531
532Below is the complete sample code and effect.
533
534
535```ts
536@Entry
537@Component
538struct ModalTransitionWithIf {
539  private listArr: string[] = ['WLAN', 'Bluetooth', 'Personal hotspot', 'Connected devices'];
540  private shareArr: string[] = ['Projection', 'Printing', 'VPN','Private DNS', 'NFC'];
541  // Step 1: Define a state variable to control page display.
542  @State isShowShare: boolean = false;
543  private shareFunc(): void {
544    this.getUIContext()?.animateTo({ duration: 500 }, () => {
545      this.isShowShare = !this.isShowShare;
546    })
547  }
548
549  build(){
550    // Step 2: Define a stack layout to display the current view and modal view.
551    Stack() {
552      Column() {
553        Column() {
554          Text('Settings')
555            .fontSize(28)
556            .fontColor(0x333333)
557        }
558        .width('90%')
559        .padding({ top: 30, bottom: 15 })
560        .alignItems(HorizontalAlign.Start)
561
562        TextInput({ placeholder: 'Search by keyword' })
563          .width('90%')
564          .height(40)
565          .margin({ bottom: 10 })
566          .focusable(false)
567
568        List({ space: 12, initialIndex: 0 }) {
569          ForEach(this.listArr, (item: string, index: number) => {
570            ListItem() {
571              Row() {
572                Row() {
573                  Text(`${item.slice(0, 1)}`)
574                    .fontColor(Color.White)
575                    .fontSize(14)
576                    .fontWeight(FontWeight.Bold)
577                }
578                .width(30)
579                .height(30)
580                .backgroundColor('#a8a8a8')
581                .margin({ right: 12 })
582                .borderRadius(20)
583                .justifyContent(FlexAlign.Center)
584
585                Column() {
586                  Text(item)
587                    .fontSize(16)
588                    .fontWeight(FontWeight.Medium)
589                }
590                .alignItems(HorizontalAlign.Start)
591
592                Blank()
593
594                Row()
595                  .width(12)
596                  .height(12)
597                  .margin({ right: 15 })
598                  .border({
599                    width: { top: 2, right: 2 },
600                    color: 0xcccccc
601                  })
602                  .rotate({ angle: 45 })
603              }
604              .borderRadius(15)
605              .shadow({ radius: 100, color: '#ededed' })
606              .width('90%')
607              .alignItems(VerticalAlign.Center)
608              .padding({ left: 15, top: 15, bottom: 15 })
609              .backgroundColor(Color.White)
610            }
611            .width('100%')
612            .onClick(() => {
613              // Step 3: Change the state variable to display the modal view.
614              if(item.slice(-2) === 'Share'){
615                this.shareFunc();
616              }
617            })
618          }, (item: string): string => item)
619        }
620        .width('100%')
621      }
622      .width('100%')
623      .height('100%')
624      .backgroundColor(0xfefefe)
625
626      // 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.
627      if(this.isShowShare){
628        Column() {
629          Column() {
630            Row() {
631              Row() {
632                Row()
633                  .width(16)
634                  .height(16)
635                  .border({
636                    width: { left: 2, top: 2 },
637                    color: 0x333333
638                  })
639                  .rotate({ angle: -45 })
640              }
641              .padding({ left: 15, right: 10 })
642              .onClick(() => {
643                this.shareFunc();
644              })
645              Text('Connected devices')
646                .fontSize(28)
647                .fontColor(0x333333)
648            }
649            .padding({ top: 30 })
650          }
651          .width('90%')
652          .padding({bottom: 15})
653          .alignItems(HorizontalAlign.Start)
654
655          List({ space: 12, initialIndex: 0 }) {
656            ForEach(this.shareArr, (item: string) => {
657              ListItem() {
658                Row() {
659                  Row() {
660                    Text(`${item.slice(0, 1)}`)
661                      .fontColor(Color.White)
662                      .fontSize(14)
663                      .fontWeight(FontWeight.Bold)
664                  }
665                  .width(30)
666                  .height(30)
667                  .backgroundColor('#a8a8a8')
668                  .margin({ right: 12 })
669                  .borderRadius(20)
670                  .justifyContent(FlexAlign.Center)
671
672                  Column() {
673                    Text(item)
674                      .fontSize(16)
675                      .fontWeight(FontWeight.Medium)
676                  }
677                  .alignItems(HorizontalAlign.Start)
678
679                  Blank()
680
681                  Row()
682                    .width(12)
683                    .height(12)
684                    .margin({ right: 15 })
685                    .border({
686                      width: { top: 2, right: 2 },
687                      color: 0xcccccc
688                    })
689                    .rotate({ angle: 45 })
690                }
691                .borderRadius(15)
692                .shadow({ radius: 100, color: '#ededed' })
693                .width('90%')
694                .alignItems(VerticalAlign.Center)
695                .padding({ left: 15, top: 15, bottom: 15 })
696                .backgroundColor(Color.White)
697              }
698              .width('100%')
699            }, (item: string): string => item)
700          }
701          .width('100%')
702        }
703        .width('100%')
704        .height('100%')
705        .backgroundColor(0xffffff)
706        // Step 5: Define the mode in which the modal view disappears.
707        .transition(TransitionEffect.OPACITY
708          .combine(TransitionEffect.translate({ x: '100%' }))
709          .combine(TransitionEffect.scale({ x: 0.95, y: 0.95 })))
710      }
711    }
712  }
713}
714```
715
716![en-us_image_0000001597792146](figures/en-us_image_0000001597792146.gif)
717