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