【鸿蒙实战开发】ArkUI转动动画模态转场实现

200 篇文章 0 订阅
200 篇文章 4 订阅

模态转场是新的界面覆盖在旧的界面上,旧的界面不消失的一种转场方式。

表1 模态转场接口

接口说明使用场景
bindContentCover弹出全屏的模态组件。用于自定义全屏的模态展示界面,结合转场动画和共享元素动画可实现复杂转场动画效果,如缩略图片点击后查看大图。
bindSheet弹出半模态组件。用于半模态展示界面,如分享框。
bindMenu弹出菜单,点击组件后弹出。需要Menu菜单的场景,如一般应用的“+”号键。
bindContextMenu弹出菜单,长按或者右键点击后弹出。长按浮起效果,一般结合拖拽框架使用,如桌面图标长按浮起。
bindPopup弹出Popup弹框。Popup弹框场景,如点击后对某个组件进行临时说明。
if通过if新增或删除组件。用来在某个状态下临时显示一个界面,这种方式的返回导航需要由开发者监听接口实现。

使用bindContentCover构建全屏模态转场效果

bindContentCover接口用于为组件绑定全屏模态页面,在组件出现和消失时可通过设置转场参数ModalTransition添加过渡动效。

  1. 定义全屏模态转场效果bindContentCover

  2. 定义模态展示界面。


    1.  // 通过@Builder构建模态展示界面
    2.  @Builder MyBuilder() {
    3.  Column() {
    4.  Text('my model view')
    5.  }
    6.  // 通过转场动画实现出现消失转场动画效果,transition需要加在builder下的第一个组件 
    7.  .transition(TransitionEffect.translate({ y: 1000 }).animation({ curve: curves.springMotion(0.6, 0.8) }))
    8.  }
  1. 通过模态接口调起模态展示界面,通过转场动画或者共享元素动画去实现对应的动画效果。

    1.  // 模态转场控制变量
    2.  @State isPresent: boolean = false;

    4.  Button('Click to present model view')
    5.  // 通过选定的模态接口,绑定模态展示界面,ModalTransition是内置的ContentCover转场动画类型,这里选择None代表系统不加默认动画,通过onDisappear控制状态变量变换
    6.  .bindContentCover(this.isPresent, this.MyBuilder(), {
    7.  modalTransition: ModalTransition.NONE,
    8.  onDisappear: () => {
    9.  this.isPresent = !this.isPresent;
    10.  }
    11.  })
    12.  .onClick(() => {
    13.  // 改变状态变量,显示模态界面
    14.  this.isPresent = !this.isPresent;
    15.  })

完整示例代码和效果如下。


1.  import { curves } from '@kit.ArkUI';

3.  interface PersonList {
4.  name: string,
5.  cardnum: string
6.  }

8.  @Entry
9.  @Component
10.  struct BindContentCoverDemo {
11.  private personList: Array<PersonList> = [
12.  { name: '王**', cardnum: '1234***********789' },
13.  { name: '宋*', cardnum: '2345***********789' },
14.  { name: '许**', cardnum: '3456***********789' },
15.  { name: '唐*', cardnum: '4567***********789' }
16.  ];
17.  // 第一步:定义全屏模态转场效果bindContentCover
18.  // 模态转场控制变量
19.  @State isPresent: boolean = false;

21.  // 第二步:定义模态展示界面
22.  // 通过@Builder构建模态展示界面
23.  @Builder
24.  MyBuilder() {
25.  Column() {
26.  Row() {
27.  Text('选择乘车人')
28.  .fontSize(20)
29.  .fontColor(Color.White)
30.  .width('100%')
31.  .textAlign(TextAlign.Center)
32.  .padding({ top: 30, bottom: 15 })
33.  }
34.  .backgroundColor(0x007dfe)

36.  Row() {
37.  Text('+ 添加乘车人')
38.  .fontSize(16)
39.  .fontColor(0x333333)
40.  .margin({ top: 10 })
41.  .padding({ top: 20, bottom: 20 })
42.  .width('92%')
43.  .borderRadius(10)
44.  .textAlign(TextAlign.Center)
45.  .backgroundColor(Color.White)
46.  }

48.  Column() {
49.  ForEach(this.personList, (item: PersonList, index: number) => {
50.  Row() {
51.  Column() {
52.  if (index % 2 == 0) {
53.  Column()
54.  .width(20)
55.  .height(20)
56.  .border({ width: 1, color: 0x007dfe })
57.  .backgroundColor(0x007dfe)
58.  } else {
59.  Column()
60.  .width(20)
61.  .height(20)
62.  .border({ width: 1, color: 0x007dfe })
63.  }
64.  }
65.  .width('20%')

67.  Column() {
68.  Text(item.name)
69.  .fontColor(0x333333)
70.  .fontSize(18)
71.  Text(item.cardnum)
72.  .fontColor(0x666666)
73.  .fontSize(14)
74.  }
75.  .width('60%')
76.  .alignItems(HorizontalAlign.Start)

78.  Column() {
79.  Text('编辑')
80.  .fontColor(0x007dfe)
81.  .fontSize(16)
82.  }
83.  .width('20%')
84.  }
85.  .padding({ top: 10, bottom: 10 })
86.  .border({ width: { bottom: 1 }, color: 0xf1f1f1 })
87.  .width('92%')
88.  .backgroundColor(Color.White)
89.  })
90.  }
91.  .padding({ top: 20, bottom: 20 })

93.  Text('确认')
94.  .width('90%')
95.  .height(40)
96.  .textAlign(TextAlign.Center)
97.  .borderRadius(10)
98.  .fontColor(Color.White)
99.  .backgroundColor(0x007dfe)
100.  .onClick(() => {
101.  this.isPresent = !this.isPresent;
102.  })
103.  }
104.  .size({ width: '100%', height: '100%' })
105.  .backgroundColor(0xf5f5f5)
106.  // 通过转场动画实现出现消失转场动画效果
107.  .transition(TransitionEffect.translate({ y: 1000 }).animation({ curve: curves.springMotion(0.6, 0.8) }))
108.  }

110.  build() {
111.  Column() {
112.  Row() {
113.  Text('确认订单')
114.  .fontSize(20)
115.  .fontColor(Color.White)
116.  .width('100%')
117.  .textAlign(TextAlign.Center)
118.  .padding({ top: 30, bottom: 60 })
119.  }
120.  .backgroundColor(0x007dfe)

122.  Column() {
123.  Row() {
124.  Column() {
125.  Text('00:25')
126.  Text('始发站')
127.  }
128.  .width('30%')

130.  Column() {
131.  Text('G1234')
132.  Text('8时1分')
133.  }
134.  .width('30%')

136.  Column() {
137.  Text('08:26')
138.  Text('终点站')
139.  }
140.  .width('30%')
141.  }
142.  }
143.  .width('92%')
144.  .padding(15)
145.  .margin({ top: -30 })
146.  .backgroundColor(Color.White)
147.  .shadow({ radius: 30, color: '#aaaaaa' })
148.  .borderRadius(10)

150.  Column() {
151.  Text('+ 选择乘车人')
152.  .fontSize(18)
153.  .fontColor(Color.Orange)
154.  .fontWeight(FontWeight.Bold)
155.  .padding({ top: 10, bottom: 10 })
156.  .width('60%')
157.  .textAlign(TextAlign.Center)
158.  .borderRadius(15)// 通过选定的模态接口,绑定模态展示界面,ModalTransition是内置的ContentCover转场动画类型,这里选择DEFAULT代表设置上下切换动画效果,通过onDisappear控制状态变量变换。
159.  .bindContentCover(this.isPresent, this.MyBuilder(), {
160.  modalTransition: ModalTransition.DEFAULT,
161.  onDisappear: () => {
162.  this.isPresent = !this.isPresent;
163.  }
164.  })
165.  .onClick(() => {
166.  // 第三步:通过模态接口调起模态展示界面,通过转场动画或者共享元素动画去实现对应的动画效果
167.  // 改变状态变量,显示模态界面
168.  this.isPresent = !this.isPresent;
169.  })
170.  }
171.  .padding({ top: 60 })
172.  }
173.  }
174.  }

0000000000011111111.20240807205230.29418123141089375199646564248164.gif

使用bindSheet构建半模态转场效果

bindSheet属性可为组件绑定半模态页面,在组件出现时可通过设置自定义或默认的内置高度确定半模态大小。构建半模态转场动效的步骤基本与使用bindContentCover构建全屏模态转场动效相同。

完整示例和效果如下。

1.  @Entry
2.  @Component
3.  struct BindSheetDemo {
4.  // 半模态转场显示隐藏控制
5.  @State isShowSheet: boolean = false;
6.  private menuList: string[] = ['不要辣', '少放辣', '多放辣', '不要香菜', '不要香葱', '不要一次性餐具', '需要一次性餐具'];

8.  // 通过@Builder构建半模态展示界面
9.  @Builder
10.  mySheet() {
11.  Column() {
12.  Flex({ direction: FlexDirection.Row, wrap: FlexWrap.Wrap }) {
13.  ForEach(this.menuList, (item: string) => {
14.  Text(item)
15.  .fontSize(16)
16.  .fontColor(0x333333)
17.  .backgroundColor(0xf1f1f1)
18.  .borderRadius(8)
19.  .margin(10)
20.  .padding(10)
21.  })
22.  }
23.  .padding({ top: 18 })
24.  }
25.  .width('100%')
26.  .height('100%')
27.  .backgroundColor(Color.White)
28.  }

30.  build() {
31.  Column() {
32.  Text('口味与餐具')
33.  .fontSize(28)
34.  .padding({ top: 30, bottom: 30 })
35.  Column() {
36.  Row() {
37.  Row()
38.  .width(10)
39.  .height(10)
40.  .backgroundColor('#a8a8a8')
41.  .margin({ right: 12 })
42.  .borderRadius(20)

44.  Column() {
45.  Text('选择点餐口味和餐具')
46.  .fontSize(16)
47.  .fontWeight(FontWeight.Medium)
48.  }
49.  .alignItems(HorizontalAlign.Start)

51.  Blank()

53.  Row()
54.  .width(12)
55.  .height(12)
56.  .margin({ right: 15 })
57.  .border({
58.  width: { top: 2, right: 2 },
59.  color: 0xcccccc
60.  })
61.  .rotate({ angle: 45 })
62.  }
63.  .borderRadius(15)
64.  .shadow({ radius: 100, color: '#ededed' })
65.  .width('90%')
66.  .alignItems(VerticalAlign.Center)
67.  .padding({ left: 15, top: 15, bottom: 15 })
68.  .backgroundColor(Color.White)
69.  // 通过选定的半模态接口,绑定模态展示界面,style中包含两个参数,一个是设置半模态的高度,不设置时默认高度是Large,一个是是否显示控制条DragBar,默认是true显示控制条,通过onDisappear控制状态变量变换。
70.  .bindSheet(this.isShowSheet, this.mySheet(), {
71.  height: 300,
72.  dragBar: false,
73.  onDisappear: () => {
74.  this.isShowSheet = !this.isShowSheet;
75.  }
76.  })
77.  .onClick(() => {
78.  this.isShowSheet = !this.isShowSheet;
79.  })
80.  }
81.  .width('100%')
82.  }
83.  .width('100%')
84.  .height('100%')
85.  .backgroundColor(0xf1f1f1)
86.  }
87.  }

0000000000011111111.20240807205230.06034180397041278300482259116988.gif

使用bindMenu实现菜单弹出效果

bindMenu为组件绑定弹出式菜单,通过点击触发。完整示例和效果如下。

1.  class BMD{
2.  value:ResourceStr = ''
3.  action:() => void = () => {}
4.  }
5.  @Entry
6.  @Component
7.  struct BindMenuDemo {

9.  // 第一步: 定义一组数据用来表示菜单按钮项
10.  @State items:BMD[] = [
11.  {
12.  value: '菜单项1',
13.  action: () => {
14.  console.info('handle Menu1 select')
15.  }
16.  },
17.  {
18.  value: '菜单项2',
19.  action: () => {
20.  console.info('handle Menu2 select')
21.  }
22.  },
23.  ]

25.  build() {
26.  Column() {
27.  Button('click')
28.  .backgroundColor(0x409eff)
29.  .borderRadius(5)
30.  // 第二步: 通过bindMenu接口将菜单数据绑定给元素
31.  .bindMenu(this.items)
32.  }
33.  .justifyContent(FlexAlign.Center)
34.  .width('100%')
35.  .height(437)
36.  }
37.  }

0000000000011111111.20240807205230.55917400166987884754415174262566.gif

使用bindContextMenu实现菜单弹出效果

bindContextMenu为组件绑定弹出式菜单,通过长按或右键点击触发。完整示例和效果如下。

完整示例和效果如下。

1.  @Entry
2.  @Component
3.  struct BindContextMenuDemo {
4.  private menu: string[] = ['保存图片', '收藏', '搜一搜'];
5.  private pics: Resource[] = [$r('app.media.icon_1'), $r('app.media.icon_2')];

7.  // 通过@Builder构建自定义菜单项
8.  @Builder myMenu() {
9.  Column() {
10.  ForEach(this.menu, (item: string) => {
11.  Row() {
12.  Text(item)
13.  .fontSize(18)
14.  .width('100%')
15.  .textAlign(TextAlign.Center)
16.  }
17.  .padding(15)
18.  .border({ width: { bottom: 1 }, color: 0xcccccc })
19.  })
20.  }
21.  .width(140)
22.  .borderRadius(15)
23.  .shadow({ radius: 15, color: 0xf1f1f1 })
24.  .backgroundColor(0xf1f1f1)
25.  }

27.  build() {
28.  Column() {
29.  Row() {
30.  Text('查看图片')
31.  .fontSize(20)
32.  .fontColor(Color.White)
33.  .width('100%')
34.  .textAlign(TextAlign.Center)
35.  .padding({ top: 20, bottom: 20 })
36.  }
37.  .backgroundColor(0x007dfe)

39.  Column() {
40.  ForEach(this.pics, (item: Resource) => {
41.  Row(){
42.  Image(item)
43.  .width('100%')
44.  .draggable(false)
45.  }
46.  .padding({ top: 20, bottom: 20, left: 10, right: 10 })
47.  .bindContextMenu(this.myMenu, ResponseType.LongPress)
48.  })
49.  }
50.  }
51.  .width('100%')
52.  .alignItems(HorizontalAlign.Center)
53.  }
54.  }

0000000000011111111.20240807205230.02185702073051524860068115632218.gif

使用bindPopUp实现气泡弹窗效果

bindpopup属性可为组件绑定弹窗,并设置弹窗内容,交互逻辑和显示状态。

完整示例和代码如下。

1.  @Entry
2.  @Component
3.  struct BindPopupDemo {

5.  // 第一步:定义变量控制弹窗显示
6.  @State customPopup: boolean = false;

8.  // 第二步:popup构造器定义弹框内容
9.  @Builder popupBuilder() {
10.  Column({ space: 2 }) {
11.  Row().width(64)
12.  .height(64)
13.  .backgroundColor(0x409eff)
14.  Text('Popup')
15.  .fontSize(10)
16.  .fontColor(Color.White)
17.  }
18.  .justifyContent(FlexAlign.SpaceAround)
19.  .width(100)
20.  .height(100)
21.  .padding(5)
22.  }

24.  build() {
25.  Column() {

27.  Button('click')
28.  // 第四步:创建点击事件,控制弹窗显隐
29.  .onClick(() => {
30.  this.customPopup = !this.customPopup;
31.  })
32.  .backgroundColor(0xf56c6c)
33.  // 第三步:使用bindPopup接口将弹窗内容绑定给元素
34.  .bindPopup(this.customPopup, {
35.  builder: this.popupBuilder,
36.  placement: Placement.Top,
37.  maskColor: 0x33000000,
38.  popupColor: 0xf56c6c,
39.  enableArrow: true,
40.  onStateChange: (e) => {
41.  if (!e.isVisible) {
42.  this.customPopup = false;
43.  }
44.  }
45.  })
46.  }
47.  .justifyContent(FlexAlign.Center)
48.  .width('100%')
49.  .height(437)
50.  }
51.  }

0000000000011111111.20240807205230.75168038808377306601020713646628.gif

使用if实现模态转场

上述模态转场接口需要绑定到其他组件上,通过监听状态变量改变调起模态界面。同时,也可以通过if范式,通过新增/删除组件实现模态转场效果。

完整示例和代码如下。


1.  @Entry
2.  @Component
3.  struct ModalTransitionWithIf {
4.  private listArr: string[] = ['WLAN', '蓝牙', '个人热点', '连接与共享'];
5.  private shareArr: string[] = ['投屏', '打印', 'VPN', '私人DNS', 'NFC'];
6.  // 第一步:定义状态变量控制页面显示
7.  @State isShowShare: boolean = false;
8.  private shareFunc(): void {
9.  animateTo({ duration: 500 }, () => {
10.  this.isShowShare = !this.isShowShare;
11.  })
12.  }

14.  build(){
15.  // 第二步:定义Stack布局显示当前页面和模态页面
16.  Stack() {
17.  Column() {
18.  Column() {
19.  Text('设置')
20.  .fontSize(28)
21.  .fontColor(0x333333)
22.  }
23.  .width('90%')
24.  .padding({ top: 30, bottom: 15 })
25.  .alignItems(HorizontalAlign.Start)

27.  TextInput({ placeholder: '输入关键字搜索' })
28.  .width('90%')
29.  .height(40)
30.  .margin({ bottom: 10 })
31.  .focusable(false)

33.  List({ space: 12, initialIndex: 0 }) {
34.  ForEach(this.listArr, (item: string, index: number) => {
35.  ListItem() {
36.  Row() {
37.  Row() {
38.  Text(`${item.slice(0, 1)}`)
39.  .fontColor(Color.White)
40.  .fontSize(14)
41.  .fontWeight(FontWeight.Bold)
42.  }
43.  .width(30)
44.  .height(30)
45.  .backgroundColor('#a8a8a8')
46.  .margin({ right: 12 })
47.  .borderRadius(20)
48.  .justifyContent(FlexAlign.Center)

50.  Column() {
51.  Text(item)
52.  .fontSize(16)
53.  .fontWeight(FontWeight.Medium)
54.  }
55.  .alignItems(HorizontalAlign.Start)

57.  Blank()

59.  Row()
60.  .width(12)
61.  .height(12)
62.  .margin({ right: 15 })
63.  .border({
64.  width: { top: 2, right: 2 },
65.  color: 0xcccccc
66.  })
67.  .rotate({ angle: 45 })
68.  }
69.  .borderRadius(15)
70.  .shadow({ radius: 100, color: '#ededed' })
71.  .width('90%')
72.  .alignItems(VerticalAlign.Center)
73.  .padding({ left: 15, top: 15, bottom: 15 })
74.  .backgroundColor(Color.White)
75.  }
76.  .width('100%')
77.  .onClick(() => {
78.  // 第五步:改变状态变量,显示模态页面
79.  if(item.slice(-2) === '共享'){
80.  this.shareFunc();
81.  }
82.  })
83.  }, (item: string): string => item)
84.  }
85.  .width('100%')
86.  }
87.  .width('100%')
88.  .height('100%')
89.  .backgroundColor(0xfefefe)

91.  // 第三步:在if中定义模态页面,显示在最上层,通过if控制模态页面出现消失
92.  if(this.isShowShare){
93.  Column() {
94.  Column() {
95.  Row() {
96.  Row() {
97.  Row()
98.  .width(16)
99.  .height(16)
100.  .border({
101.  width: { left: 2, top: 2 },
102.  color: 0x333333
103.  })
104.  .rotate({ angle: -45 })
105.  }
106.  .padding({ left: 15, right: 10 })
107.  .onClick(() => {
108.  this.shareFunc();
109.  })
110.  Text('连接与共享')
111.  .fontSize(28)
112.  .fontColor(0x333333)
113.  }
114.  .padding({ top: 30 })
115.  }
116.  .width('90%')
117.  .padding({bottom: 15})
118.  .alignItems(HorizontalAlign.Start)

120.  List({ space: 12, initialIndex: 0 }) {
121.  ForEach(this.shareArr, (item: string) => {
122.  ListItem() {
123.  Row() {
124.  Row() {
125.  Text(`${item.slice(0, 1)}`)
126.  .fontColor(Color.White)
127.  .fontSize(14)
128.  .fontWeight(FontWeight.Bold)
129.  }
130.  .width(30)
131.  .height(30)
132.  .backgroundColor('#a8a8a8')
133.  .margin({ right: 12 })
134.  .borderRadius(20)
135.  .justifyContent(FlexAlign.Center)

137.  Column() {
138.  Text(item)
139.  .fontSize(16)
140.  .fontWeight(FontWeight.Medium)
141.  }
142.  .alignItems(HorizontalAlign.Start)

144.  Blank()

146.  Row()
147.  .width(12)
148.  .height(12)
149.  .margin({ right: 15 })
150.  .border({
151.  width: { top: 2, right: 2 },
152.  color: 0xcccccc
153.  })
154.  .rotate({ angle: 45 })
155.  }
156.  .borderRadius(15)
157.  .shadow({ radius: 100, color: '#ededed' })
158.  .width('90%')
159.  .alignItems(VerticalAlign.Center)
160.  .padding({ left: 15, top: 15, bottom: 15 })
161.  .backgroundColor(Color.White)
162.  }
163.  .width('100%')
164.  }, (item: string): string => item)
165.  }
166.  .width('100%')
167.  }
168.  .width('100%')
169.  .height('100%')
170.  .backgroundColor(0xffffff)
171.  // 第四步:定义模态页面出现消失转场方式
172.  .transition(TransitionEffect.OPACITY
173.  .combine(TransitionEffect.translate({ x: '100%' }))
174.  .combine(TransitionEffect.scale({ x: 0.95, y: 0.95 })))
175.  }
176.  }
177.  }
178.  }

0000000000011111111.20240807205231.74517129024687233745155623353414.gif

写在最后

●如果你觉得这篇内容对你还蛮有帮助,我想邀请你帮我三个小忙:
●点赞,转发,有你们的 『点赞和评论』,才是我创造的动力。
●关注小编,同时可以期待后续文章ing🚀,不定期分享原创知识。
●更多鸿蒙最新技术知识点,请移步前往小编:https://gitee.com/

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值