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 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 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 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 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 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 717