• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# 模态转场
2
3
4模态转场是新的界面覆盖在旧的界面上,旧的界面不消失的一种转场方式。
5
6
7**表1** 模态转场接口
8| 接口                                       | 说明                | 使用场景                                     |
9| ---------------------------------------- | ----------------- | ---------------------------------------- |
10| [bindContentCover](../reference/apis-arkui/arkui-ts/ts-universal-attributes-modal-transition.md) | 弹出全屏的模态组件。        | 用于自定义全屏的模态展示界面,结合转场动画和共享元素动画可实现复杂转场动画效果,如缩略图片点击后查看大图。 |
11| [bindSheet](../reference/apis-arkui/arkui-ts/ts-universal-attributes-sheet-transition.md) | 弹出半模态组件。          | 用于半模态展示界面,如分享框。                          |
12| [bindMenu](../reference/apis-arkui/arkui-ts/ts-universal-attributes-menu.md) | 弹出菜单,点击组件后弹出。     | 需要Menu菜单的场景,如一般应用的“+”号键。                 |
13| [bindContextMenu](../reference/apis-arkui/arkui-ts/ts-universal-attributes-menu.md) | 弹出菜单,长按或者右键点击后弹出。 | 长按浮起效果,一般结合拖拽框架使用,如桌面图标长按浮起。             |
14| [bindPopup](../reference/apis-arkui/arkui-ts/ts-universal-attributes-popup.md) | 弹出Popup弹框。        | Popup弹框场景,如点击后对某个组件进行临时说明。               |
15| if                                       | 通过if新增或删除组件。      | 用来在某个状态下临时显示一个界面,这种方式的返回导航需要由开发者监听接口实现。  |
16
17
18## 使用bindContentCover构建全屏模态转场效果
19
20[bindContentCover](../reference/apis-arkui/arkui-ts/ts-universal-attributes-modal-transition.md)接口用于为组件绑定全屏模态页面,在组件出现和消失时可通过设置转场参数ModalTransition添加过渡动效。
21
221. 定义全屏模态转场效果[bindContentCover](../reference/apis-arkui/arkui-ts/ts-universal-attributes-modal-transition.md)。
23
242. 定义模态展示界面。
25
26   ```ts
27   // 通过@Builder构建模态展示界面
28   @Builder MyBuilder() {
29     Column() {
30       Text('my model view')
31     }
32     // 通过转场动画实现出现消失转场动画效果,transition需要加在builder下的第一个组件
33     .transition(TransitionEffect.translate({ y: 1000 }).animation({ curve: curves.springMotion(0.6, 0.8) }))
34   }
35   ```
36
373. 通过模态接口调起模态展示界面,通过转场动画或者共享元素动画去实现对应的动画效果。
38
39   ```ts
40   // 模态转场控制变量
41   @State isPresent: boolean = false;
42
43   Button('Click to present model view')
44     // 通过选定的模态接口,绑定模态展示界面,ModalTransition是内置的ContentCover转场动画类型,这里选择None代表系统不加默认动画
45     .bindContentCover(this.isPresent, this.MyBuilder, ModalTransition.NONE)
46     .onClick(() => {
47       // 改变状态变量,显示模态界面
48       this.isPresent = !this.isPresent;
49     })
50   ```
51
52
53完整示例代码和效果如下。
54
55
56
57```ts
58import curves from '@ohos.curves';
59
60interface PersonList {
61  name: string,
62  cardnum: string
63}
64
65@Entry
66@Component
67struct BindContentCoverDemo {
68  private personList: Array<PersonList> = [
69    { name: '王**', cardnum: '1234***********789' },
70    { name: '宋*', cardnum: '2345***********789' },
71    { name: '许**', cardnum: '3456***********789' },
72    { name: '唐*', cardnum: '4567***********789' }
73  ];
74
75  // 第一步:定义全屏模态转场效果bindContentCover
76  // 模态转场控制变量
77  @State isPresent: boolean = false;
78
79  // 第二步:定义模态展示界面
80  // 通过@Builder构建模态展示界面
81  @Builder MyBuilder() {
82    Column() {
83      Row() {
84        Text('选择乘车人')
85          .fontSize(20)
86          .fontColor(Color.White)
87          .width('100%')
88          .textAlign(TextAlign.Center)
89          .padding({ top: 30, bottom: 15 })
90      }
91      .backgroundColor(0x007dfe)
92
93      Row() {
94        Text('+ 添加乘车人')
95          .fontSize(16)
96          .fontColor(0x333333)
97          .margin({ top: 10 })
98          .padding({ top: 20, bottom: 20 })
99          .width('92%')
100          .borderRadius(10)
101          .textAlign(TextAlign.Center)
102          .backgroundColor(Color.White)
103      }
104
105      Column() {
106        ForEach(this.personList, (item: PersonList, index: number) => {
107          Row() {
108            Column() {
109              if (index % 2 == 0) {
110                Column()
111                  .width(20)
112                  .height(20)
113                  .border({ width: 1, color: 0x007dfe })
114                  .backgroundColor(0x007dfe)
115              } else {
116                Column()
117                  .width(20)
118                  .height(20)
119                  .border({ width: 1, color: 0x007dfe })
120              }
121            }
122            .width('20%')
123            Column() {
124              Text(item.name)
125                .fontColor(0x333333)
126                .fontSize(18)
127              Text(item.cardnum)
128                .fontColor(0x666666)
129                .fontSize(14)
130            }
131            .width('60%')
132            .alignItems(HorizontalAlign.Start)
133            Column() {
134              Text('编辑')
135                .fontColor(0x007dfe)
136                .fontSize(16)
137            }
138            .width('20%')
139          }
140          .padding({ top: 10, bottom: 10 })
141          .border({ width: { bottom: 1 }, color: 0xf1f1f1 })
142          .width('92%')
143          .backgroundColor(Color.White)
144        })
145      }
146      .padding({ top: 20, bottom: 20 })
147
148      Text('确认')
149        .width('90%')
150        .height(40)
151        .textAlign(TextAlign.Center)
152        .borderRadius(10)
153        .fontColor(Color.White)
154        .backgroundColor(0x007dfe)
155        .onClick(() => {
156          this.isPresent = !this.isPresent;
157        })
158    }
159    .size({ width: '100%', height: '100%' })
160    .backgroundColor(0xf5f5f5)
161    // 通过转场动画实现出现消失转场动画效果
162    .transition(TransitionEffect.translate({ y: 1000 }).animation({ curve: curves.springMotion(0.6, 0.8) }))
163  }
164
165  build() {
166    Column() {
167      Row() {
168        Text('确认订单')
169          .fontSize(20)
170          .fontColor(Color.White)
171          .width('100%')
172          .textAlign(TextAlign.Center)
173          .padding({ top: 30, bottom: 60 })
174      }
175      .backgroundColor(0x007dfe)
176
177      Column() {
178        Row() {
179          Column() {
180            Text('00:25')
181            Text('始发站')
182          }
183          .width('30%')
184          Column() {
185            Text('G1234')
186            Text('8时1分')
187          }
188          .width('30%')
189          Column() {
190            Text('08:26')
191            Text('终点站')
192          }
193          .width('30%')
194        }
195      }
196      .width('92%')
197      .padding(15)
198      .margin({ top: -30 })
199      .backgroundColor(Color.White)
200      .shadow({ radius: 30, color: '#aaaaaa' })
201      .borderRadius(10)
202
203      Column() {
204        Text('+ 选择乘车人')
205          .fontSize(18)
206          .fontColor(Color.Orange)
207          .fontWeight(FontWeight.Bold)
208          .padding({ top: 10, bottom: 10 })
209          .width('60%')
210          .textAlign(TextAlign.Center)
211          .borderRadius(15)
212            // 通过选定的模态接口,绑定模态展示界面,ModalTransition是内置的ContentCover转场动画类型,这里选择DEFAULT代表设置上下切换动画效果。
213          .bindContentCover(this.isPresent, this.MyBuilder(), ModalTransition.DEFAULT)
214          .onClick(() => {
215            // 第三步:通过模态接口调起模态展示界面,通过转场动画或者共享元素动画去实现对应的动画效果
216            // 改变状态变量,显示模态界面
217            this.isPresent = !this.isPresent;
218          })
219      }
220      .padding({ top: 60 })
221    }
222  }
223}
224```
225
226
227
228![zh-cn_image_0000001646921957](figures/zh-cn_image_0000001646921957.gif)
229
230
231
232## 使用bindSheet构建半模态转场效果
233
234[bindSheet](../reference/apis-arkui/arkui-ts/ts-universal-attributes-sheet-transition.md)属性可为组件绑定半模态页面,在组件出现时可通过设置自定义或默认的内置高度确定半模态大小。构建半模态转场动效的步骤基本与使用bindContentCover构建全屏模态转场动效相同。
235
236完整示例和效果如下。
237
238
239```ts
240@Entry
241@Component
242struct BindSheetDemo {
243  // 半模态转场显示隐藏控制
244  @State isShowSheet: boolean = false;
245  private menuList: string[] = ['不要辣', '少放辣', '多放辣', '不要香菜', '不要香葱', '不要一次性餐具', '需要一次性餐具'];
246  // 通过@Builder构建半模态展示界面
247  @Builder mySheet() {
248    Column() {
249      Flex({ direction: FlexDirection.Row, wrap: FlexWrap.Wrap }) {
250        ForEach(this.menuList, (item: string) => {
251          Text(item)
252            .fontSize(16)
253            .fontColor(0x333333)
254            .backgroundColor(0xf1f1f1)
255            .borderRadius(8)
256            .margin(10)
257            .padding(10)
258        })
259      }
260      .padding({top: 18})
261    }
262    .width('100%')
263    .height('100%')
264    .backgroundColor(Color.White)
265  }
266
267  build() {
268    Column() {
269      Text('口味与餐具')
270        .fontSize(28)
271        .padding({ top: 30, bottom: 30 })
272      Column() {
273        Row() {
274          Row()
275          .width(10)
276          .height(10)
277          .backgroundColor('#a8a8a8')
278          .margin({ right: 12 })
279          .borderRadius(20)
280
281          Column() {
282            Text('选择点餐口味和餐具')
283              .fontSize(16)
284              .fontWeight(FontWeight.Medium)
285          }
286          .alignItems(HorizontalAlign.Start)
287
288          Blank()
289
290          Row()
291            .width(12)
292            .height(12)
293            .margin({ right: 15 })
294            .border({
295              width: { top: 2, right: 2 },
296              color: 0xcccccc
297            })
298            .rotate({ angle: 45 })
299        }
300        .borderRadius(15)
301        .shadow({ radius: 100, color: '#ededed' })
302        .width('90%')
303        .alignItems(VerticalAlign.Center)
304        .padding({ left: 15, top: 15, bottom: 15 })
305        .backgroundColor(Color.White)
306        // 通过选定的半模态接口,绑定模态展示界面,style中包含两个参数,一个是设置半模态的高度,不设置时默认高度是Large,一个是是否显示控制条DragBar,默认是true显示控制条
307        .bindSheet(this.isShowSheet, this.mySheet(), {
308          height: 300,
309          dragBar: false
310        })
311        .onClick(() => {
312          this.isShowSheet = !this.isShowSheet;
313        })
314      }
315      .width('100%')
316    }
317    .width('100%')
318    .height('100%')
319    .backgroundColor(0xf1f1f1)
320  }
321}
322```
323
324![zh-cn_image_0000001599977924](figures/zh-cn_image_0000001599977924.gif)
325
326
327## 使用bindMenu实现菜单弹出效果
328
329[bindMenu](../reference/apis-arkui/arkui-ts/ts-universal-attributes-menu.md)为组件绑定弹出式菜单,通过点击触发。完整示例和效果如下。
330
331
332```ts
333class BMD{
334  value:ResourceStr = ''
335  action:() => void = () => {}
336}
337@Entry
338@Component
339struct BindMenuDemo {
340
341  // 第一步: 定义一组数据用来表示菜单按钮项
342  @State items:BMD[] = [
343    {
344      value: '菜单项1',
345      action: () => {
346        console.info('handle Menu1 select')
347      }
348    },
349    {
350      value: '菜单项2',
351      action: () => {
352        console.info('handle Menu2 select')
353      }
354    },
355  ]
356
357  build() {
358    Column() {
359      Button('click')
360        .backgroundColor(0x409eff)
361        .borderRadius(5)
362          // 第二步: 通过bindMenu接口将菜单数据绑定给元素
363        .bindMenu(this.items)
364    }
365    .justifyContent(FlexAlign.Center)
366    .width('100%')
367    .height(437)
368  }
369}
370```
371
372![zh-cn_image_0000001599643478](figures/zh-cn_image_0000001599643478.gif)
373
374
375## 使用bindContextMenu实现菜单弹出效果
376
377[bindContextMenu](../reference/apis-arkui/arkui-ts/ts-universal-attributes-menu.md)为组件绑定弹出式菜单,通过长按或右键点击触发。完整示例和效果如下。
378
379完整示例和效果如下。
380
381
382```ts
383@Entry
384@Component
385struct BindContextMenuDemo {
386  private menu: string[] = ['保存图片', '收藏', '搜一搜'];
387  private pics: Resource[] = [$r('app.media.icon_1'), $r('app.media.icon_2')];
388
389  // 通过@Builder构建自定义菜单项
390  @Builder myMenu() {
391    Column() {
392      ForEach(this.menu, (item: string) => {
393        Row() {
394          Text(item)
395            .fontSize(18)
396            .width('100%')
397            .textAlign(TextAlign.Center)
398        }
399        .padding(15)
400        .border({ width: { bottom: 1 }, color: 0xcccccc })
401      })
402    }
403    .width(140)
404    .borderRadius(15)
405    .shadow({ radius: 15, color: 0xf1f1f1 })
406    .backgroundColor(0xf1f1f1)
407  }
408
409  build() {
410    Column() {
411      Row() {
412        Text('查看图片')
413          .fontSize(20)
414          .fontColor(Color.White)
415          .width('100%')
416          .textAlign(TextAlign.Center)
417          .padding({ top: 20, bottom: 20 })
418      }
419      .backgroundColor(0x007dfe)
420
421      Column() {
422        ForEach(this.pics, (item: Resource) => {
423          Row(){
424            Image(item)
425              .width('100%')
426          }
427          .padding({ top: 20, bottom: 20, left: 10, right: 10 })
428          .bindContextMenu(this.myMenu, ResponseType.LongPress)
429        })
430      }
431    }
432    .width('100%')
433    .alignItems(HorizontalAlign.Center)
434  }
435}
436```
437
438![zh-cn_image_0000001600137920](figures/zh-cn_image_0000001600137920.gif)
439
440
441## 使用bindPopUp实现气泡弹窗效果
442
443[bindpopup](../reference/apis-arkui/arkui-ts/ts-universal-attributes-popup.md)属性可为组件绑定弹窗,并设置弹窗内容,交互逻辑和显示状态。
444
445完整示例和代码如下。
446
447
448```ts
449@Entry
450@Component
451struct BindPopupDemo {
452
453  // 第一步:定义变量控制弹窗显示
454  @State customPopup: boolean = false;
455
456  // 第二步:popup构造器定义弹框内容
457  @Builder popupBuilder() {
458    Column({ space: 2 }) {
459      Row().width(64)
460        .height(64)
461        .backgroundColor(0x409eff)
462      Text('Popup')
463        .fontSize(10)
464        .fontColor(Color.White)
465    }
466    .justifyContent(FlexAlign.SpaceAround)
467    .width(100)
468    .height(100)
469    .padding(5)
470  }
471
472  build() {
473    Column() {
474
475      Button('click')
476        // 第四步:创建点击事件,控制弹窗显隐
477        .onClick(() => {
478          this.customPopup = !this.customPopup;
479        })
480        .backgroundColor(0xf56c6c)
481          // 第三步:使用bindPopup接口将弹窗内容绑定给元素
482        .bindPopup(this.customPopup, {
483          builder: this.popupBuilder,
484          placement: Placement.Top,
485          maskColor: 0x33000000,
486          popupColor: 0xf56c6c,
487          enableArrow: true,
488          onStateChange: (e) => {
489            if (!e.isVisible) {
490              this.customPopup = false;
491            }
492          }
493        })
494    }
495    .justifyContent(FlexAlign.Center)
496    .width('100%')
497    .height(437)
498  }
499}
500```
501
502
503
504![zh-cn_image_0000001649282285](figures/zh-cn_image_0000001649282285.gif)
505
506
507## 使用if实现模态转场
508
509上述模态转场接口需要绑定到其他组件上,通过监听状态变量改变调起模态界面。同时,也可以通过if范式,通过新增/删除组件实现模态转场效果。
510
511完整示例和代码如下。
512
513
514```ts
515@Entry
516@Component
517struct ModalTransitionWithIf {
518  private listArr: string[] = ['WLAN', '蓝牙', '个人热点', '连接与共享'];
519  private shareArr: string[] = ['投屏', '打印', 'VPN', '私人DNS', 'NFC'];
520  // 第一步:定义状态变量控制页面显示
521  @State isShowShare: boolean = false;
522  private shareFunc(): void {
523    animateTo({ duration: 500 }, () => {
524      this.isShowShare = !this.isShowShare;
525    })
526  }
527
528  build(){
529    // 第二步:定义Stack布局显示当前页面和模态页面
530    Stack() {
531      Column() {
532        Column() {
533          Text('设置')
534            .fontSize(28)
535            .fontColor(0x333333)
536        }
537        .width('90%')
538        .padding({ top: 30, bottom: 15 })
539        .alignItems(HorizontalAlign.Start)
540
541        TextInput({ placeholder: '输入关键字搜索' })
542          .width('90%')
543          .height(40)
544          .margin({ bottom: 10 })
545          .focusable(false)
546
547        List({ space: 12, initialIndex: 0 }) {
548          ForEach(this.listArr, (item: string, index: number) => {
549            ListItem() {
550              Row() {
551                Row() {
552                  Text(`${item.slice(0, 1)}`)
553                    .fontColor(Color.White)
554                    .fontSize(14)
555                    .fontWeight(FontWeight.Bold)
556                }
557                .width(30)
558                .height(30)
559                .backgroundColor('#a8a8a8')
560                .margin({ right: 12 })
561                .borderRadius(20)
562                .justifyContent(FlexAlign.Center)
563
564                Column() {
565                  Text(item)
566                    .fontSize(16)
567                    .fontWeight(FontWeight.Medium)
568                }
569                .alignItems(HorizontalAlign.Start)
570
571                Blank()
572
573                Row()
574                  .width(12)
575                  .height(12)
576                  .margin({ right: 15 })
577                  .border({
578                    width: { top: 2, right: 2 },
579                    color: 0xcccccc
580                  })
581                  .rotate({ angle: 45 })
582              }
583              .borderRadius(15)
584              .shadow({ radius: 100, color: '#ededed' })
585              .width('90%')
586              .alignItems(VerticalAlign.Center)
587              .padding({ left: 15, top: 15, bottom: 15 })
588              .backgroundColor(Color.White)
589            }
590            .width('100%')
591            .onClick(() => {
592              // 第五步:改变状态变量,显示模态页面
593              if(item.slice(-2) === '共享'){
594                this.shareFunc();
595              }
596            })
597          }, (item: string): string => item)
598        }
599        .width('100%')
600      }
601      .width('100%')
602      .height('100%')
603      .backgroundColor(0xfefefe)
604
605      // 第三步:在if中定义模态页面,显示在最上层,通过if控制模态页面出现消失
606      if(this.isShowShare){
607        Column() {
608          Column() {
609            Row() {
610              Row() {
611                Row()
612                  .width(16)
613                  .height(16)
614                  .border({
615                    width: { left: 2, top: 2 },
616                    color: 0x333333
617                  })
618                  .rotate({ angle: -45 })
619              }
620              .padding({ left: 15, right: 10 })
621              .onClick(() => {
622                this.shareFunc();
623              })
624              Text('连接与共享')
625                .fontSize(28)
626                .fontColor(0x333333)
627            }
628            .padding({ top: 30 })
629          }
630          .width('90%')
631          .padding({bottom: 15})
632          .alignItems(HorizontalAlign.Start)
633
634          List({ space: 12, initialIndex: 0 }) {
635            ForEach(this.shareArr, (item: string) => {
636              ListItem() {
637                Row() {
638                  Row() {
639                    Text(`${item.slice(0, 1)}`)
640                      .fontColor(Color.White)
641                      .fontSize(14)
642                      .fontWeight(FontWeight.Bold)
643                  }
644                  .width(30)
645                  .height(30)
646                  .backgroundColor('#a8a8a8')
647                  .margin({ right: 12 })
648                  .borderRadius(20)
649                  .justifyContent(FlexAlign.Center)
650
651                  Column() {
652                    Text(item)
653                      .fontSize(16)
654                      .fontWeight(FontWeight.Medium)
655                  }
656                  .alignItems(HorizontalAlign.Start)
657
658                  Blank()
659
660                  Row()
661                    .width(12)
662                    .height(12)
663                    .margin({ right: 15 })
664                    .border({
665                      width: { top: 2, right: 2 },
666                      color: 0xcccccc
667                    })
668                    .rotate({ angle: 45 })
669                }
670                .borderRadius(15)
671                .shadow({ radius: 100, color: '#ededed' })
672                .width('90%')
673                .alignItems(VerticalAlign.Center)
674                .padding({ left: 15, top: 15, bottom: 15 })
675                .backgroundColor(Color.White)
676              }
677              .width('100%')
678            }, (item: string): string => item)
679          }
680          .width('100%')
681        }
682        .width('100%')
683        .height('100%')
684        .backgroundColor(0xffffff)
685        // 第四步:定义模态页面出现消失转场方式
686        .transition(TransitionEffect.OPACITY
687          .combine(TransitionEffect.translate({ x: '100%' }))
688          .combine(TransitionEffect.scale({ x: 0.95, y: 0.95 })))
689      }
690    }
691  }
692}
693```
694
695![zh-cn_image_0000001597792146](figures/zh-cn_image_0000001597792146.gif)
696