1# 属性动画
2
3## 场景介绍
4在日常开发过程中,通常会出现因为状态变化导致组件属性值发生变化,如:
5- 数字键盘按键时,对应数字按钮背景色加深,呈现被点击效果;放开按键时,呈现取消选中效果,
6- UI中图标按下时,图标出现弹性缩放效果,
7- 在做数据统计时,随着数据值的变化,统计曲线随之变化等动画效果,
8本例将为大家介绍下如何通过属性动画实现上述场景。
9
10## 效果呈现
11效果图如下:
12
13| 场景         | 效果图                   |
14|------------|-----------------------|
15| 场景1:属性动画   | ![属性动画](figures/attribute_animation.gif) |
16| 场景2:弹性动态 | ![弹性动态](figures/Elastic_animation.gif) |
17| 场景3:曲线动画 | ![时间曲线](figures/time_curve.gif) |
18
19## 运行环境
20本例基于以下环境开发,开发者也可以基于其他适配的版本进行开发
21- IDE: DevEco Studio 3.1 Release
22- SDK: Ohos_sdk_public 3.2.12.5(API Version 9 Release)
23
24## 场景1:属性动画
25### 实现思路
26本实例涉及到的主要特性及其实现方案如下:
27* 通过Column、Grid、button、Text等关键组件以及visibility属性搭建UI局框架,以及渲染数字按钮。
28* 设置状态变量flag,用来控制属性状态的变化,同时向Button组件添加onTouch事件,更新按钮的当前状态,从而可以根据监听到的按钮状态加载对应的动画效果。
29  * 默认状态为按钮放开状态(flag为false)。
30  * 当按钮按下时,更新按钮的状态(flag:false -> true)。
31  * 当按钮放开时,更新按钮的状态(flag:true -> false)。
32* 根据按钮组件的按下/放开状态,通过三元运算判断,使用属性动画更新按钮的选中/取消效果。
33  * 当按钮按下时,加载属性动画:按钮被选中效果。
34  * 当按钮放开时,加载属性动画:按钮取消选中效果。
35### 开发步骤
36针对实现思路中所提到的内容,具体关键开发步骤如下:
371. 通过Column、Grid、button、Text等关键组件以及visibility属性搭建UI框架,以及渲染数字按钮。
38   具体代码如下:
39
40   ```ts
41
42     private numGrid: number[] = [1, 2, 3, 4, 5, 6, 7, 8, 9, -1, 0, -1]
43
44     build() {
45       Row() {
46         Column() {
47           Grid() {
48             //通过ForEach循环遍历,显示数字数字按钮
49             ForEach(this.numGrid, (item, index) => {
50               GridItem() {
51                 Button() {
52                   Text(`${item}`)
53                     .fontSize(30)
54                 }
55                 .backgroundColor('#fff')
56                 .width(100)
57                 .height(100)
58                 // item为-1时,数字按钮不显示
59                 .visibility(item == -1 ? Visibility.Hidden : Visibility.Visible)
60               }
61             }, item => item)
62           }
63           .columnsTemplate('1fr 1fr 1fr')
64           .rowsTemplate('1fr 1fr 1fr 1fr')
65           .columnsGap(10)
66           .rowsGap(10)
67           .width(330)
68           .height(440)
69         }
70         .width('100%')
71         .height('100%')
72       }
73     }
74
75   ```
76
772. 设置状态变量flag,用来控制属性状态的变化,同时向Button组件添加onTouch事件,更新按钮的当前状态,从而可以根据监听到的按钮状态加载对应的动画效果。
78
79    具体代码如下:
80
81    ```ts
82    //状态变量flag,用于监听按钮按下和放开的状态,默认至为false(放开状态)
83    @State flag: boolean = false;
84    ...
85    .onTouch((event:TouchEvent) => {
86        //当按钮按下时,更新按钮的状态(flag:false -> true)
87        if (event.type == TouchType.Down) {
88            this.flag = true
89            this.currIndex = index
90        }
91        //当按钮放开时,更新按钮的状态(flag:true -> false)
92        if (event.type == TouchType.Up) {
93            this.flag = false
94        }
95    })
96    ```
97
98
993. 根据按钮组件的按下/放开状态,通过三元运算判断并更新按钮背景色。
100
101    具体代码如下:
102
103    ```ts
104    Button(){
105        ...
106        // 当按钮被按下(flag变更为true)时,当前按钮backgroundColor属性变更("#fff" -> "#D2C3D5" )
107        // 当按钮被放开(flag变更为false)时,当前按钮backgroundColor属性变更("#D2C3D5" -> "#FFF" )
108        .backgroundColor(this.flag && this.currIndex == index ? '#D2C3D5' : '#fff')
109        .animation({
110            duration: 200
111        })
112    }
113    ```
114
115
116### 完整代码
117```ts
118@Entry
119@Component
120struct Index {
121  private currIndex: number = -1
122  private numGrid: number[] = [1, 2, 3, 4, 5, 6, 7, 8, 9, -1, 0, -1]
123  //状态变量flag,用于监听按钮按下和放开的状态,默认至为false(放开状态)
124  @State flag: boolean = false;
125
126  build() {
127    Row() {
128      Column() {
129        Grid() {
130          ForEach(this.numGrid, (item, index) => {
131            GridItem() {
132              Button() {
133                Text(`${item}`)
134                  .fontSize(30)
135              }
136               // 当按钮被按下(flag变更为true)时,当前按钮backgroundColor属性变更("#fff" -> "#D2C3D5" )
137			   // 当按钮被放开(flag变更为false)时,当前按钮backgroundColor属性变更("#D2C3D5" -> "#FFF" )
138              .backgroundColor(this.flag && this.currIndex == index ? '#D2C3D5' : '#fff')
139              .animation({
140                duration: 200
141              })
142              .width(100)
143              .height(100)
144              .visibility(item == -1 ? Visibility.Hidden : Visibility.Visible)
145              .onTouch((event:TouchEvent) => {
146                //当按钮按下时,更新按钮的状态(flag:false -> true)
147                if (event.type == TouchType.Down) {
148                  this.flag = true
149                  this.currIndex = index
150                }
151                //当按钮放开时,更新按钮的状态(flag:true -> false)
152                if (event.type == TouchType.Up) {
153                  this.flag = false
154                }
155              })
156            }
157          }, item => item)
158        }
159        .columnsTemplate('1fr 1fr 1fr')
160        .rowsTemplate('1fr 1fr 1fr 1fr')
161        .columnsGap(10)
162        .rowsGap(10)
163        .width(330)
164        .height(440)
165      }
166      .width('100%')
167      .height('100%')
168    }
169  }
170}
171```
172
173## 场景2:弹性动效
174### 实现思路
175针对弹性动效,涉及到的主要特征分为两部分:
176* 组件描绘:先通过List、ListItem、Image等组件将控件表描绘出来。
177* 组件状态变化:设置状态变量downFlag,控制按钮的当前状态;同时向Column组件添加onTouch事件,监听组件的当前状态。
178    * 组件默认状态为放开状态(downFlag:false)。
179    * 当按下Image组件时,更新组件的状态为true。
180    * 当放开Image组件时,更新组件的状态为false。
181* 动画播放:使用属性动画绘制Image组件不同状态下的曲线动画。
182    * 组件按下,触发第一阶段缩小动效,370ms内将组件横纵尺寸均缩小到原尺寸的80%。
183    * 组件放开,触发第二阶段放大动效,在370ms内将控件从当前尺寸恢复到原尺寸。
184### 开发步骤
185针对实现思路中所提到的内容,具体关键开发步骤如下:
1861. 先通过List、ListItem、Image等组件将控件表描绘出来。
187
188   具体代码如下:
189
190   ```ts
191   private arr: number[]=[1,2,3,4,5]
192
193   ...
194   List({space:10}){
195         ForEach(this.arr,(item,index)=>{
196           ListItem(){
197               Image($r("app.media.app_icon"))
198                 .width(80)
199                 .height(80)
200           }
201         })
202       }
203       .margin({top:20})
204       .padding({left:20})
205       .listDirection(Axis.Horizontal)
206   ```
207
2082. 设置状态变量downFlag,控制Image组件的当前状态,同时向Image组件添加onTouch事件,获取并更新组件的当前状态,从而可以根据监听到的组件状态加载对应的动画效果。
209  具体代码如下:
210
211    ```ts
212    ...
213    // 状态变量downFlag,用于监听Image组件按下和放开的状态
214    @State downFlag: boolean = false;
215    ...
216    // 添加onTouch事件,监听状态
217    .onTouch((event: TouchEvent) => {
218        // 当Column组件按下时,组件的状态更新为true
219        if (event.type == TouchType.Down) {
220          this.downFlag = true
221        // 当Column组件按下时,组件的状态更新为false
222        } else if (event.type == TouchType.Up) {
223          this.downFlag = false
224        }
225      })
226    ```
227
2283. 根据Image组件的按下/放开状态,呈现不同的弹性效果(按下时组件缩小,动画以阻尼曲线的形式缩小至0.8倍,放开时组件动画以阻尼曲线的形式恢复至初始大小)。
229  具体代码如下:
230
231    ```ts
232   ···
233   Image($r("app.media.app_icon"))
234     // 当downFlag状态为按下(true)时,组件在370ms内缩小至0.5倍;当downFlag状态为放开(false)时,组件在370ms内恢复至初始大小;
235    .scale(this.downFlag ? { x: 0.8, y: 0.8 } : { x: 1, y: 1 })
236    .animation({
237       duration: 370,
238       curve: Curve.Friction
239   })
240    ```
241
242### 完整代码
243```ts
244@Entry
245@Component
246struct Index {
247  // 状态变量downFlag,用于监听Image组件按下和放开的状态
248  @State downFlag: boolean = false;
249  private arr: number[]=[1,2,3,4,5]
250  private curIndex : number = -1
251
252  build() {
253    List({space:10}){
254      ForEach(this.arr,(item,index)=>{
255        ListItem(){
256            Image($r("app.media.app_icon"))
257              // 当downFlag状态为按下(true)时,组件在370ms内缩小至0.8倍;当downFlag状态为放开(false)时,组件在370ms内恢复至初始大小;
258              .scale(this.downFlag && this.curIndex == index ? { x: 0.8, y: 0.8 } : { x: 1, y: 1 })
259              .animation({
260                duration: 370,
261                curve: Curve.Friction
262              })
263              .width(80)
264              .height(80)
265                // 添加onTouch事件,监听状态
266              .onTouch((event: TouchEvent) => {
267                // 当Image组件按下时,组件的状态更新为true
268                if (event.type == TouchType.Down) {
269                  this.downFlag = true
270                  this.curIndex = index
271                  // 当Image组件按下时,组件的状态更新为false
272                } else if (event.type == TouchType.Up) {
273                  this.downFlag = false
274                }
275              })
276        }
277      })
278    }
279    .margin({top:20})
280    .padding({left:20})
281     // 列表排列方向水平
282    .listDirection(Axis.Horizontal)
283  }
284}
285```
286
287## 场景3:曲线动画
288曲线动画可以使用不同的曲线属性,呈现出不同的动画。
289ArkUI中,曲线动画有两种使用方式,一种是直接使用Curve类型的枚举,一种是导入ohos.curve模块并使用模块内定义的接口/属性。
290
291### 实现思路
292* 设置自定义变量item,用于存放不同类型的曲线,本案例中主要有以下类型曲线:
293  1. Curve类型的枚举:
294     * Linear:表示动画从头到尾的速度都是相同的。
295     * EaseInOut:表示动画以低速开始和结束,CubicBezier(0.42, 0.0, 0.58, 1.0)。
296     * Smooth:平滑曲线,cubic-bezier(0.4, 0.0, 0.4, 1.0)。
297  2. ohos.curve导入:
298     * springCurve类的弹簧曲线。
299* 通过Flex、Column、button组件将UI布局以及框架搭建。
300* Column组件内部通过ForEach,遍历展示每条曲线。
301* 设置状态变量flag,监听当前button的状态,同时向button组件添加onClick事件,更新按钮的状态,从而可以根据监听到的按钮状态加载对应的动画效果。
302* 向Text组件添加width属性,根据按钮的状态,在同一周期内,通过width的变化呈现曲线的不同动画。
303### 开发步骤
3041. 设置自定义变量item,用于存放不同类型的曲线。
305
306    具体代码如下:
307
308    ```ts
309    private items: object[] = [
310        {
311          color: '#a320bf',
312          title: '线性曲线',
313          // curve类型曲线:表示动画从头到尾的速度都是相同的。
314          curve: Curve.Linear,
315        },
316        {
317          color: '#17d4be',
318          title: '低速结束曲线',
319          // curve类型曲线:表示动画以低速结束
320          curve: Curve.EaseInOut,
321        },
322        {
323          color: '#0e2d8c',
324          title: '平滑曲线',
325          // curve类型曲线:平滑曲线
326          curve: Curve.Smooth,
327        },
328        {
329          color: '#d4bb19',
330          title: 'Curves.spring',
331          // ohos.curve导入的弹簧曲线,以初速度为20的弹簧进行平移
332          curve: Curves.springCurve(20, 1, 1, 1.2),
333        },
334    ```
335
3362. 通过Flex、Column、button组件将UI布局以及框架搭建。
337
338    具体代码如下:
339
340    ```ts
341     Flex({ direction: FlexDirection.Column, justifyContent: FlexAlign.Start, alignItems: ItemAlign.Center }) {
342         Column({ space: 50 }) {
343            ...
344         }
345         .alignItems(HorizontalAlign.Start)
346         .width('100%')
347         .padding({ left: 12, right: 12, top: 22, bottom: 22 })
348
349         Button(){
350            Text("Go!")
351            .fontSize(20)
352            .fontColor('#fff')
353        }
354        .borderRadius(20)
355        .fontColor('#FFFFFF')
356        .fontSize('12fp')
357        .fontWeight(FontWeight.Bolder)
358        .backgroundColor('#15587c')
359        .padding(25)
360        .margin({top:50})
361      }
362      .height('100%')
363      .backgroundColor('#F1F3F5')
364      .padding({ left: '3%', right: '3%' })
365    ```
366
3673. Column组件内部通过ForEach,遍历展示每条曲线。
368
369    具体代码如下:
370
371    ```ts
372    ForEach(this.items, (item, index) => {
373      Text(item['title'])
374        .fontSize(20)
375        .fontColor('#FFFFFF')
376        .height(80)
377        .textAlign(TextAlign.Start)
378        .backgroundColor(item['color'])
379        .borderRadius(20)
380        .padding({left:10})
381    }, item => JSON.stringify(item))
382
383    ```
384
3854. 设置状态变量flag,监听当前button的状态,同时向button组件添加onClick事件,更新按钮的状态,从而可以根据监听到的按钮状态加载对应的动画效果。
386
387    具体代码如下:
388
389    ```ts
390    //状态变量flag,用于监听按钮按下状态
391    @State flag: boolean = true
392    ...
393    Button(){
394    }
395    // 通过点击事件,反转flag的值
396    .onClick(() => {
397          this.flag = !this.flag
398        })
399    ```
400
4015. 向Text组件添加width属性,根据按钮的状态,在同一周期内,呈现不同效果的曲线动画。
402
403    具体代码如下:
404
405    ```ts
406    ForEach(this.items, (item, index) => {
407      Text(item['title'])
408         ...
409        // 当flag为true时,width为15%;当flag为false时,width为95%
410        .width(this.flag ? '15%': '95%')
411        .animation({ duration: 2000, curve: item['curve'] })
412    }, item => JSON.stringify(item))
413    ```
414
415### 完整代码
416```ts
417import Curves from '@ohos.curves';
418
419@Entry
420@Component
421struct Index {
422  // 状态变量flag,用于监听按钮按下状态
423  @State flag: boolean = true
424  private items: object[] = [
425    {
426      color: '#a320bf',
427      title: '线性曲线',
428      // curve类型曲线:表示动画从头到尾的速度都是相同的。
429      curve: Curve.Linear,
430    },
431    {
432      color: '#17d4be',
433      title: '低速结束曲线',
434      // curve类型曲线:表示动画以低速结束
435      curve: Curve.EaseInOut,
436    },
437    {
438      color: '#0e2d8c',
439      title: '平滑曲线',
440      // curve类型曲线:平滑曲线
441      curve: Curve.Smooth,
442    },
443    {
444      color: '#d4bb19',
445      title: '弹簧曲线',
446      // ohos.curve导入的弹簧曲线动画,以初速度为20的弹簧进行平移
447      curve: Curves.springCurve(20, 1, 1, 1.2),
448    },
449  ]
450
451  build() {
452    Flex({
453      direction: FlexDirection.Column,
454      justifyContent: FlexAlign.Start,
455      alignItems: ItemAlign.Center
456    }) {
457      Column({ space: 50 }) {
458        ForEach(this.items, (item, index) => {
459          Text(item['title'])
460            .fontSize(20)
461            .fontColor('#FFFFFF')
462            // 当flag为true时,width为15%;当flag为false时,width为95%;
463            .width(this.flag ? '15%': '95%')
464            .height(80)
465            .textAlign(TextAlign.Start)
466            .backgroundColor(item['color'])
467            // 2s之内完成动画展示,每条动画曲线按照item['curve']去展示
468            .animation({ duration: 2000, curve: item['curve'] })
469            .borderRadius(20)
470            .padding({left:10})
471        }, item => JSON.stringify(item))
472      }
473      .alignItems(HorizontalAlign.Start)
474      .width('100%')
475      .padding({ left: 12, right: 12, top: 22, bottom: 22 })
476
477      Button(){
478        Text("Go!")
479          .fontSize(20)
480          .fontColor('#fff')
481      }
482      .borderRadius(20)
483      .fontColor('#FFFFFF')
484      .fontSize('12fp')
485      .fontWeight(FontWeight.Bolder)
486      .backgroundColor('#15587c')
487      .padding(25)
488      .margin({top:50})
489      // 通过点击事件,反转flag的值
490      .onClick(() => {
491        this.flag = !this.flag
492      })
493    }
494    .height('100%')
495    .backgroundColor('#F1F3F5')
496    .padding({ left: '3%', right: '3%' })
497  }
498}
499```
500
501
502503
504## 参考
505
506[属性动画](../application-dev/reference/apis-arkui/arkui-ts/ts-animatorproperty.md#属性动画)
507
508[Curve](../application-dev/reference/apis-arkui/arkui-ts/ts-appendix-enums.md#curve)
509
510[弹簧曲线动画](../application-dev/ui/arkts-spring-curve.md)
511
512[Flex布局](../application-dev/reference/apis-arkui/arkui-ts/ts-universal-attributes-flex-layout.md)
513
514[Listitem](../application-dev/reference/apis-arkui/arkui-ts/ts-container-listitem.md)
515
516[List](../application-dev/reference/apis-arkui/arkui-ts/ts-container-list.md)