• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# 添加动画效果
2
3动画主要包含了组件动画和页面间动画,并提供了[插值计算](../reference/apis/js-apis-curve.md)和[矩阵变换](../reference/apis/js-apis-matrix4.md)的动画能力接口,让开发者极大程度的自主设计动画效果。
4
5在本节主要完成两个动画效果:
6
71. 启动页的闪屏动画,即Logo图标的渐出和放大效果;
82. 食物列表页和食物详情页的共享元素转场动画效果。
9
10## animateTo实现闪屏动画
11
12组件动画包括属性动画和animateTo显式动画:
13
141. 属性动画:设置组件通用属性变化的动画效果。
152. 显式动画:设置组件从状态A到状态B的变化动画效果,包括样式、位置信息和节点的增加删除等,开发者无需关注变化过程,只需指定起点和终点的状态。animateTo还提供播放状态的回调接口,是对属性动画的增强与封装。
16
17闪屏页面的动画效果是Logo图标的渐出和放大,动画结束后跳转到食物分类列表页面。接下来,我们就使用animateTo来实现启动页动画的闪屏效果。
18
191. 动画效果自动播放。闪屏动画的预期效果是,进入Logo页面后,animateTo动画效果自动开始播放,可以借助于组件显隐事件的回调接口来实现。调用Shape的onAppear方法,设置其显式动画。
20
21   ```ts
22   Shape() {
23     ...
24   }
25   .onAppear(() => {
26      animateTo()
27   })
28   ```
29
302. 创建opacity和scale数值的成员变量,用装饰器@State修饰。表示其为有状态的数据,即改变会触发页面的刷新。
31
32   ```ts
33   @Entry
34   @Component
35   struct Logo {
36     @State private opacityValue: number = 0
37     @State private scaleValue: number = 0
38     build() {
39       Shape() {
40         ...
41       }
42      .scale({ x: this.scaleValue, y: this.scaleValue })
43      .opacity(this.opacityValue)
44      .onAppear(() => {
45        animateTo()
46       })
47     }
48   }
49   ```
50
513. 设置animateTo的动画曲线curve。Logo的加速曲线为先慢后快,使用贝塞尔曲线cubicBezier,cubicBezier(0.4, 0, 1, 1)。
52
53   需要使用动画能力接口中的插值计算,首先要导入curves模块。
54
55   ```ts
56   import Curves from '@ohos.curves'
57   ```
58
59   @ohos.curves模块提供了线性Curve. Linear、阶梯step、三阶贝塞尔(cubicBezier)和弹簧(spring)插值曲线的初始化函数,可以根据入参创建一个插值曲线对象。
60
61   ```ts
62   @Entry
63   @Component
64   struct Logo {
65     @State private opacityValue: number = 0
66     @State private scaleValue: number = 0
67     private curve1 = Curves.cubicBezier(0.4, 0, 1, 1)
68
69     build() {
70       Shape() {
71         ...
72       }
73      .scale({ x: this.scaleValue, y: this.scaleValue })
74      .opacity(this.opacityValue)
75      .onAppear(() => {
76        animateTo({
77           curve: this.curve1
78        })
79       })
80     }
81   }
82   ```
83
844. 设置动画时长为1s,延时0.1s开始播放,设置显示动效event的闭包函数,即起点状态到终点状态为透明度opacityValue和大小scaleValue从0到1,实现Logo的渐出和放大效果。
85
86   ```ts
87   @Entry
88   @Component
89   struct Logo {
90     @State private opacityValue: number = 0
91     @State private scaleValue: number = 0
92     private curve1 = Curves.cubicBezier(0.4, 0, 1, 1)
93
94     build() {
95       Shape() {
96         ...
97       }
98      .scale({ x: this.scaleValue, y: this.scaleValue })
99      .opacity(this.opacityValue)
100      .onAppear(() => {
101        animateTo({
102         duration: 1000,
103         curve: this.curve1,
104         delay: 100,
105        }, () => {
106          this.opacityValue = 1
107          this.scaleValue = 1
108         })
109       })
110     }
111   }
112   ```
113
1145. 闪屏动画播放结束后定格1s,进入FoodCategoryList页面。设置animateTo的onFinish回调接口,调用定时器Timer的setTimeout接口延时1s后,调用router.replace,显示FoodCategoryList页面。
115
116   ```ts
117   import router from '@ohos.router'
118
119   @Entry
120   @Component
121   struct Logo {
122     @State private opacityValue: number = 0
123     @State private scaleValue: number = 0
124     private curve1 = Curves.cubicBezier(0.4, 0, 1, 1)
125
126     build() {
127       Shape() {
128         ...
129       }
130      .scale({ x: this.scaleValue, y: this.scaleValue })
131      .opacity(this.opacityValue)
132      .onAppear(() => {
133
134        animateTo({
135         duration: 1000,
136          curve: this.curve1,
137          delay: 100,
138          onFinish: () => {
139            setTimeout(() => {
140              router.replaceUrl({ url: "pages/FoodCategoryList" })
141            }, 1000);
142          }
143        }, () => {
144          this.opacityValue = 1
145          this.scaleValue = 1
146         })
147       })
148     }
149   }
150   ```
151
152   整体代码如下。
153
154   ```ts
155   import Curves from '@ohos.curves'
156   import router from '@ohos.router'
157
158   @Entry
159   @Component
160   struct Logo {
161     @State private opacityValue: number = 0
162     @State private scaleValue: number = 0
163     private curve1 = Curves.cubicBezier(0.4, 0, 1, 1)
164     private pathCommands1: string = 'M319.5 128.1 c103.5 0 187.5 84 187.5 187.5 v15 a172.5 172.5 0 0 3 -172.5 172.5 H198 a36 36 0 0 3 -13.8 -1 207 207 0 0 0 87 -372 h48.3 z'
165     private pathCommands2: string = 'M270.6 128.1 h48.6 c51.6 0 98.4 21 132.3 54.6 a411 411 0 0 3 -45.6 123 c-25.2 45.6 -56.4 84 -87.6 110.4 a206.1 206.1 0 0 0 -47.7 -288 z'
166
167     build() {
168       Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) {
169         Shape() {
170           Path()
171             .commands('M162 128.7 a222 222 0 0 1 100.8 374.4 H198 a36 36 0 0 3 -36 -36')
172             .fill(Color.White)
173             .stroke(Color.Transparent)
174           Path()
175             .commands(this.pathCommands1)
176             .fill('none')
177             .stroke(Color.Transparent)
178             .linearGradient(
179             {
180               angle: 30,
181               colors: [["#C4FFA0", 0], ["#ffffff", 1]]
182             })
183             .clip(new Path().commands(this.pathCommands1))
184
185           Path()
186             .commands(this.pathCommands2)
187             .fill('none')
188             .stroke(Color.Transparent)
189             .linearGradient(
190             {
191               angle: 50,
192               colors: [['#8CC36A', 0.1], ["#B3EB90", 0.4], ["#ffffff", 0.7]]
193             })
194             .clip(new Path().commands(this.pathCommands2))
195         }
196         .height('630px')
197         .width('630px')
198         .scale({ x: this.scaleValue, y: this.scaleValue })
199         .opacity(this.opacityValue)
200         .onAppear(() => {
201           animateTo({
202             duration: 1000,
203             curve: this.curve1,
204             delay: 100,
205             onFinish: () => {
206               setTimeout(() => {
207                 router.replaceUrl({ url: "pages/FoodCategoryList" })
208               }, 1000);
209             }
210           }, () => {
211             this.opacityValue = 1
212             this.scaleValue = 1
213           })
214         })
215
216         Text('Healthy Diet')
217           .fontSize(26)
218           .fontColor(Color.White)
219           .margin({ top: 300 })
220
221         Text('Healthy life comes from a balanced diet')
222           .fontSize(17)
223           .fontColor(Color.White)
224           .margin({ top: 4 })
225       }
226       .width('100%')
227       .height('100%')
228       .linearGradient(
229         {
230           angle: 180,
231           colors: [['#BDE895', 0.1], ["#95DE7F", 0.6], ["#7AB967", 1]]
232         })
233    }
234   }
235   ```
236
237   ![animation-feature](figures/animation-feature.gif)
238
239## 页面转场动画
240
241食物分类列表页和食物详情页之间的共享元素转场,即点击FoodListItem/FoodGridItem后,食物缩略图会放大,随着页面跳转,到食物详情页的大图。
242
2431. 设置FoodListItem和FoodGridItem的Image组件的共享元素转场方法(sharedTransition)。转场id为foodItem.id,转场动画时长为1s,延时0.1s播放,变化曲线为贝塞尔曲线Curves.cubicBezier(0.2, 0.2, 0.1, 1.0) ,需引入curves模块。
244
245   共享转场时会携带当前元素的被设置的属性,所以创建Row组件,使其作为Image的父组件,设置背景颜色在Row上。
246
247   在FoodListItem的Image组件上设置autoResize为false,因为image组件默认会根据最终展示的区域,去调整图源的大小,以优化图片渲染性能。在转场动画中,图片在放大的过程中会被重新加载,所以为了转场动画的流畅,autoResize设置为false。
248
249   ```ts
250   // FoodList.ets
251   import Curves from '@ohos.curves'
252
253   @Component
254   struct FoodListItem {
255     private foodItem: FoodData
256     build() {
257       Navigator({ target: 'pages/FoodDetail' }) {
258         Flex({ justifyContent: FlexAlign.Start, alignItems: ItemAlign.Center }) {
259           Row() {
260             Image(this.foodItem.image)
261               .objectFit(ImageFit.Contain)
262               .autoResize(false)
263               .height(40)
264               .width(40)
265               .sharedTransition(this.foodItem.id, { duration: 1000, curve: Curves.cubicBezier(0.2, 0.2, 0.1, 1.0), delay: 100 })
266           }
267
268           .margin({ right: 16 })
269           Text(this.foodItem.name)
270             .fontSize(14)
271             .flexGrow(1)
272           Text(this.foodItem.calories + ' kcal')
273             .fontSize(14)
274         }
275         .height(64)
276       }
277       .params({ foodData: this.foodItem })
278       .margin({ right: 24, left:32 })
279     }
280   }
281
282   @Component
283   struct FoodGridItem {
284     private foodItem: FoodData
285     build() {
286       Column() {
287         Row() {
288           Image(this.foodItem.image)
289             .objectFit(ImageFit.Contain)
290             .autoResize(false)
291             .height(152)
292             .width('100%')
293             .sharedTransition(this.foodItem.id, { duration: 1000, curve: Curves.cubicBezier(0.2, 0.2, 0.1, 1.0), delay: 100 })
294         }
295         Flex({ justifyContent: FlexAlign.Start, alignItems: ItemAlign.Center }) {
296           Text(this.foodItem.name)
297             .fontSize(14)
298             .flexGrow(1)
299             .padding({ left: 8 })
300           Text(this.foodItem.calories + 'kcal')
301             .fontSize(14)
302             .margin({ right: 6 })
303         }
304         .height(32)
305         .width('100%')
306         .backgroundColor('#FFe5e5e5')
307       }
308       .height(184)
309       .width('100%')
310       .onClick(() => {
311         router.pushUrl({ url: 'pages/FoodDetail', params: { foodData: this.foodItem } })
312       })
313     }
314   }
315
316
317   ```
318
3192. 设置FoodDetail页面的FoodImageDisplay的Image组件的共享元素转场方法(sharedTransition)。设置方法同上。
320
321   ```ts
322   import Curves from '@ohos.curves'
323
324   @Component
325   struct FoodImageDisplay {
326     private foodItem: FoodData
327     build() {
328       Stack({ alignContent: Alignment.BottomStart }) {
329         Image(this.foodItem.image)
330           .objectFit(ImageFit.Contain)
331           .sharedTransition(this.foodItem.id, { duration: 1000, curve: Curves.cubicBezier(0.2, 0.2, 0.1, 1.0), delay: 100 })
332         Text(this.foodItem.name)
333           .fontSize(26)
334           .fontWeight(500)
335           .margin({ left: 26, bottom: 17.4 })
336       }
337       .height(357)
338     }
339   }
340   ```
341
342   ![animation-feature1](figures/animation-feature1.gif)
343
344   通过对绘制组件和动画的学习,我们已完成了启动Logo的绘制、启动页动画和页面间的转场动画,声明式UI框架提供了丰富的动效接口,合理地应用和组合可以让应用更具有设计感。
345
346
347