1# 合理使用动画
2
3本文列举了部分用于优化动画时延的正反案例,帮助开发者在遇到相似场景时进行优化,解决构建页面动画时遇到动画时延较长的问题。
4
5## 减少动画丢帧
6
7在播放动画或者生成动画时,画面产生停滞而导致帧率过低的现象,称为动画丢帧。
8
9播放动画时,系统需要在一个刷新周期内完成动画变化曲线的计算,完成组件布局绘制等操作。建议使用系统提供的动画接口,只需设置曲线类型、终点位置、时长等信息,就能够满足常用的动画功能,减少UI主线程的负载。
10
11反例:应用使用了自定义动画,动画曲线计算过程很容易引起UI线程高负载,易导致丢帧。
12
13```typescript
14@Entry
15@Component
16struct AttrAnimationExample0 {
17  @State widthSize: number = 200
18  @State heightSize: number = 100
19  @State flag: boolean = true
20
21  computeSize() {
22    let duration = 2000
23    let period = 16
24    let widthSizeEnd = 0
25    let heightSizeEnd = 0
26    if (this.flag) {
27      widthSizeEnd = 100
28      heightSizeEnd = 50
29    } else {
30      widthSizeEnd = 200
31      heightSizeEnd = 100
32    }
33    let doTimes = duration / period
34    let deltaHeight = (heightSizeEnd - this.heightSize) / doTimes
35    let deltaWeight = (widthSizeEnd - this.widthSize) / doTimes
36    for (let i = 1; i <= doTimes; i++) {
37      let t = period * (i);
38      setTimeout(() => {
39        this.heightSize = this.heightSize + deltaHeight
40        this.widthSize = this.widthSize + deltaWeight
41      }, t)
42    }
43    this.flag = !this.flag
44  }
45
46  build() {
47    Column() {
48      Button('click me')
49        .onClick(() => {
50          let delay = 500
51          setTimeout(() => { this.computeSize() }, delay)
52        })
53        .width(this.widthSize).height(this.heightSize).backgroundColor(0x317aff)
54    }.width('100%').margin({ top: 5 })
55  }
56}
57```
58
59![img](./figures/reasonable-using-animation-1.png)
60
61### 使用系统提供的属性动效API
62
63建议:通过系统提供的属性动效API实现上述动效功能。
64
65```typescript
66@Entry
67@Component
68struct AttrAnimationExample1 {
69  @State widthSize: number = 200
70  @State heightSize: number = 100
71  @State flag: boolean = true
72
73  build() {
74    Column() {
75      Button('click me')
76        .onClick((event?: ClickEvent | undefined) => {
77          if (this.flag) {
78            this.widthSize = 100
79            this.heightSize = 50
80          } else {
81            this.widthSize = 200
82            this.heightSize = 100
83          }
84          this.flag = !this.flag
85        })
86        .width(this.widthSize).height(this.heightSize).backgroundColor(0x317aff)
87        .animation({
88          duration: 2000, // 动画时长
89          curve: Curve.Linear, // 动画曲线
90          delay: 500, // 动画延迟
91          iterations: 1, // 播放次数
92          playMode: PlayMode.Normal // 动画模式
93        }) // 对Button组件的宽高属性进行动画配置
94    }.width('100%').margin({ top: 5 })
95  }
96}
97```
98
99![img](./figures/reasonable-using-animation-2.png)
100
101更详细的API文档请参考:[属性动画](../reference/apis-arkui/arkui-ts/ts-animatorproperty.md)。
102
103### 使用系统提供的显式动效API
104
105建议:通过系统提供的显式动效API实现上述动效功能。
106
107```typescript
108@Entry
109@Component
110struct AnimateToExample2 {
111  @State widthSize: number = 200;
112  @State heightSize: number = 100;
113  @State flag: boolean = true;
114
115  build() {
116    Column() {
117      Button('click me')
118        .onClick((event?: ClickEvent | undefined) => {
119          if (this.flag) {
120            animateTo({
121              duration: 2000, // 动画时长
122              curve: Curve.Linear, // 动画曲线
123              delay: 500, // 动画延迟
124              iterations: 1, // 播放次数
125              playMode: PlayMode.Normal // 动画模式
126            }, () => {
127              this.widthSize = 100;
128              this.heightSize = 50;
129            })
130          } else {
131            animateTo({
132              duration: 2000, // 动画时长
133              curve: Curve.Linear, // 动画曲线
134              delay: 500, // 动画延迟
135              iterations: 1, // 播放次数
136              playMode: PlayMode.Normal // 动画模式
137            }, () => {
138              this.widthSize = 200;
139              this.heightSize = 100;
140            })
141          }
142          this.flag = !this.flag;
143        })
144        .width(this.widthSize).height(this.heightSize).backgroundColor(0x317aff)
145    }.width('100%').margin({ top: 5 })
146  }
147}
148```
149
150![img](./figures/reasonable-using-animation-3.png)
151
152更详细的API文档请参考:[显式动画](../reference/apis-arkui/arkui-ts/ts-explicit-animation.md)。
153
154### 优化效果
155
156相比于自定义动画,使用系统提供的动效API可提高动画帧数,提高应用性能。
157
158| 动画实现方式  | 帧数(fps) |
159|---------|---------|
160| 自定义动画   | 60      |
161| 属性动效API | 120     |
162| 显式动效API | 120     |
163
164## 合理设置隐式动画
165
166Tabs组件在不为BottomTabBarStyle样式时,切换页面时默认加载300ms的隐式动画,如果开发场景不需要该动画效果,会因默认加载导致页面跳转完成时延变长,此时可手动设置`animationDuration`减少动画完成时延。下述正反示例分别为100ms和1000ms的动画时延:
167
168### 反例:
169
170```javascript
171@Entry
172@Component
173struct TabsExample {
174  // ...
175  private controller: TabsController = new TabsController();
176
177  build() {
178    Column() {
179      Tabs({ barPosition: BarPosition.Start, index: this.currentIndex, controller: this.controller }) {
180        TabContent()
181        TabContent()
182        // ...
183      }
184      // ...
185      // 设置Tabs页面跳转的动画时长为1000ms
186      .animationDuration(1000)
187    }
188    .width('100%')
189  }
190}
191```
192
193![img](./figures/reasonable-using-animation-4.PNG)
194
195### 正例:
196
197```javascript
198@Entry
199@Component
200struct TabsExample {
201  // ...
202  private controller: TabsController = new TabsController();
203
204  build() {
205    Column() {
206      Tabs({ barPosition: BarPosition.Start, index: this.currentIndex, controller: this.controller }) {
207        TabContent()
208        TabContent()
209        // ...
210      }
211      // ...
212      // 设置Tabs页面跳转的动画时长为100ms
213      .animationDuration(100)
214    }
215    .width('100%')
216  }
217}
218```
219
220![img](./figures/reasonable-using-animation-5.PNG)
221
222### 优化效果
223
224| 优化前 1000ms                                         | 优化后 100ms                                          |
225|----------------------------------------------------|----------------------------------------------------|
226| ![img](./figures/reasonable-using-animation-6.gif) | ![img](./figures/reasonable-using-animation-7.gif) |
227
228上述示例通过减少`animationDuration`数值,减少Tabs切换完成时延。当数值设置为0且TabBar不为BottomTabBarStyle样式时,隐式动效延时为默认的300ms。开发者可根据实际场景适当减少隐式动效时延,如果应用没有特殊的动效要求时,建议设置数值为1,减少阻塞主线程,提高应用性能。
229
230更详细的API文档请参考:[Tabs-animationduration](../reference/apis-arkui/arkui-ts/ts-container-tabs.md#animationduration)。
231
232## 合理设置动效时长
233
234滚动类组件可使用**fling**方法按传入的初始速度进行惯性滚动,不合理的滚动速度设置可能导致动效时长过长,此时应通过加快滚动速度减少动效时长。下述正反示例通过改变List组件惯性滚动速度减少动效时长:
235
236### 反例:
237
238```javascript
239@Entry
240@Component
241struct ListExample {
242  scrollerForList: Scroller = new Scroller();
243
244  build() {
245    Column() {
246      Button('Fling100')
247        .onClick(() => {
248          // 设置当前滚动初始速度为100vp/s
249          this.scrollerForList.fling(100);
250        })
251      List({ space: 20, initialIndex: 0, scroller: this.scrollerForList }) {
252        // ...
253      }
254    }
255  }
256}
257```
258
259### 正例:
260
261```javascript
262@Entry
263@Component
264struct ListExample {
265  scrollerForList: Scroller = new Scroller();
266
267  build() {
268    Column() {
269      Button('Fling100')
270        .onClick(() => {
271          // 设置当前滚动初始速度为10000vp/s
272          this.scrollerForList.fling(10000);
273        })
274      List({ space: 20, initialIndex: 0, scroller: this.scrollerForList }) {
275        // ...
276      }
277    }
278  }
279}
280```
281
282### 优化效果
283
284*100vp/s:*
285
286![img](./figures/reasonable-using-animation-8.PNG)
287
288*10000vp/s:*
289
290![img](./figures/reasonable-using-animation-9.PNG)
291
292| 示例  | 动效耗时(ms) |
293|-----|----------|
294| 优化前 | 392      |
295| 优化后 | 200      |
296
297上述示例在提高滚动速度到10000vp/s后,相比100vp/s减少了200ms的动画时延。开发者可根据实际场景适当增加滚动速度,在不影响页面效果的情况下减少页面完成时延,提高应用性能。