1# 实现属性动画 2 3 4通过可动画属性改变引起UI上产生的连续视觉效果,即为属性动画。属性动画是最基础易懂的动画,ArkUI提供三种属性动画接口[animateTo](../reference/apis-arkui/arkui-ts/ts-explicit-animation.md)、[animation](../reference/apis-arkui/arkui-ts/ts-animatorproperty.md)和[keyframeAnimateTo](../reference/apis-arkui/arkui-ts/ts-keyframeAnimateTo.md)驱动组件属性按照动画曲线等动画参数进行连续的变化,产生属性动画。 5 6 7| 属性动画接口 | 作用域 | 原理 | 使用场景 | 8| -------- | -------- | -------- | -------- | 9| animateTo | 闭包内改变属性引起的界面变化。<br/>作用于出现消失转场。 | 通用函数,对闭包前界面和闭包中的状态变量引起的界面之间的差异做动画。<br/>支持多次调用,支持嵌套。 | 适用对多个可动画属性配置相同动画参数的动画。<br/>需要嵌套使用动画的场景。 | 10| animation | 组件通过属性接口绑定的属性变化引起的界面变化。 | 识别组件的可动画属性变化,自动添加动画。<br/>组件的接口调用是从下往上执行,animation只会作用于在其之上的属性调用。<br/>组件可以根据调用顺序对多个属性设置不同的animation。 | 适用于对多个可动画属性配置不同参数动画的场景。 | 11| keyframeAnimateTo | 多个闭包内改变属性引起的分段属性动画。 | 通用函数,每一段闭包中的状态变量与前一次的差异做动画。<br/>支持多次调用,不推荐嵌套。 | 适用于同一属性需要做连续多个动画的场景。 | 12 13## 使用animateTo产生属性动画 14 15 16``` 17animateTo(value: AnimateParam, event: () => void): void 18``` 19 20[animateTo](../reference/apis-arkui/arkui-ts/ts-explicit-animation.md)接口参数中,value指定[AnimateParam对象](../reference/apis-arkui/arkui-ts/ts-explicit-animation.md#animateparam对象说明)(包括时长、[Curve](../reference/apis-arkui/js-apis-curve.md#curve)等)event为动画的闭包函数,闭包内变量改变产生的属性动画将遵循相同的动画参数。 21 22> **说明:** 23> 24> 直接使用animateTo可能导致实例不明确的问题,建议使用[getUIContext](../reference/apis-arkui/js-apis-window.md#getuicontext10)获取[UIContext](../reference/apis-arkui/js-apis-arkui-UIContext.md#uicontext)实例,并使用[animateTo](../reference/apis-arkui/js-apis-arkui-UIContext.md#animateto)调用绑定实例的animateTo。 25 26```ts 27import { curves } from '@kit.ArkUI'; 28 29@Entry 30@Component 31struct AnimateToDemo { 32 @State animate: boolean = false; 33 // 第一步: 声明相关状态变量 34 @State rotateValue: number = 0; // 组件一旋转角度 35 @State translateX: number = 0; // 组件二偏移量 36 @State opacityValue: number = 1; // 组件二透明度 37 38 // 第二步:将状态变量设置到相关可动画属性接口 39 build() { 40 Row() { 41 // 组件一 42 Column() { 43 } 44 .rotate({ angle: this.rotateValue }) 45 .backgroundColor('#317AF7') 46 .justifyContent(FlexAlign.Center) 47 .width(100) 48 .height(100) 49 .borderRadius(30) 50 .onClick(() => { 51 this.getUIContext()?.animateTo({ curve: curves.springMotion() }, () => { 52 this.animate = !this.animate; 53 // 第三步:闭包内通过状态变量改变UI界面 54 // 这里可以写任何能改变UI的逻辑比如数组添加,显隐控制,系统会检测改变后的UI界面与之前的UI界面的差异,对有差异的部分添加动画 55 // 组件一的rotate属性发生变化,所以会给组件一添加rotate旋转动画 56 this.rotateValue = this.animate ? 90 : 0; 57 // 组件二的透明度发生变化,所以会给组件二添加透明度的动画 58 this.opacityValue = this.animate ? 0.6 : 1; 59 // 组件二的translate属性发生变化,所以会给组件二添加translate偏移动画 60 this.translateX = this.animate ? 50 : 0; 61 }) 62 }) 63 64 // 组件二 65 Column() { 66 67 } 68 .justifyContent(FlexAlign.Center) 69 .width(100) 70 .height(100) 71 .backgroundColor('#D94838') 72 .borderRadius(30) 73 .opacity(this.opacityValue) 74 .translate({ x: this.translateX }) 75 } 76 .width('100%') 77 .height('100%') 78 .justifyContent(FlexAlign.Center) 79 } 80} 81``` 82 83 84 85 86## 使用animation产生属性动画 87 88相比于animateTo接口需要把要执行动画的属性的修改放在闭包中,[animation](../reference/apis-arkui/arkui-ts/ts-animatorproperty.md)接口无需使用闭包,把animation接口加在要做属性动画的可动画属性后即可。animation只要检测到其绑定的可动画属性发生变化,就会自动添加属性动画,animateTo则必须在动画闭包内改变可动画属性的值从而生成动画。 89 90 91```ts 92import { curves } from '@kit.ArkUI'; 93 94@Entry 95@Component 96struct AnimationDemo { 97 @State animate: boolean = false; 98 // 第一步: 声明相关状态变量 99 @State rotateValue: number = 0; // 组件一旋转角度 100 @State translateX: number = 0; // 组件二偏移量 101 @State opacityValue: number = 1; // 组件二透明度 102 103 // 第二步:将状态变量设置到相关可动画属性接口 104 build() { 105 Row() { 106 // 组件一 107 Column() { 108 } 109 .opacity(this.opacityValue) 110 .rotate({ angle: this.rotateValue }) 111 // 第三步:通过属性动画接口开启属性动画 112 .animation({ curve: curves.springMotion() }) 113 .backgroundColor('#317AF7') 114 .justifyContent(FlexAlign.Center) 115 .width(100) 116 .height(100) 117 .borderRadius(30) 118 .onClick(() => { 119 this.animate = !this.animate; 120 // 第四步:闭包内通过状态变量改变UI界面 121 // 这里可以写任何能改变UI的逻辑比如数组添加,显隐控制,系统会检测改变后的UI界面与之前的UI界面的差异,对有差异的部分添加动画 122 // 组件一的rotate属性发生变化,所以会给组件一添加rotate旋转动画 123 this.rotateValue = this.animate ? 90 : 0; 124 // 组件二的translate属性发生变化,所以会给组件二添加translate偏移动画 125 this.translateX = this.animate ? 50 : 0; 126 // 父组件column的opacity属性有变化,会导致其子节点的透明度也变化,所以这里会给column和其子节点的透明度属性都加动画 127 this.opacityValue = this.animate ? 0.6 : 1; 128 }) 129 130 // 组件二 131 Column() { 132 } 133 .justifyContent(FlexAlign.Center) 134 .width(100) 135 .height(100) 136 .backgroundColor('#D94838') 137 .borderRadius(30) 138 .opacity(this.opacityValue) 139 .translate({ x: this.translateX }) 140 .animation({ curve: curves.springMotion() }) 141 } 142 .width('100%') 143 .height('100%') 144 .justifyContent(FlexAlign.Center) 145 } 146} 147``` 148 149 150 151## 使用keyframeAnimateTo产生属性动画 152 153``` 154keyframeAnimateTo(param: KeyframeAnimateParam, keyframes: Array<KeyframeState>): void 155``` 156 157[keyframeAnimateTo](../reference/apis-arkui/arkui-ts/ts-keyframeAnimateTo.md)接口参数中,第一个参数[KeyframeAnimateParam](../reference/apis-arkui/arkui-ts/ts-keyframeAnimateTo.md#keyframeanimateparam对象说明)为关键帧动画的整体参数(包括延时、播放次数、结束回调、期望帧率),第二个参数是一个数组,每一项表示一个关键帧内的动画行为;每一段动画可单独控制动画参数(包括时长、[Curve](../reference/apis-arkui/js-apis-curve.md#curve)等)。 158在同一属性存在多段动画过程的场景,可通过在结束回调中再创建新动画实现,但写法更复杂,且每次创建新动画需要耗时,会有衔接卡顿现象。此场景更适宜用关键帧动画实现。 159 160以下示例主要演示如何通过keyframeAnimateTo来设置关键帧动画。 161 162```ts 163@Entry 164@Component 165struct KeyframeAnimateToDemo { 166 // 第一步: 声明相关状态变量 167 @State rotateValue: number = 0; // 组件一旋转角度 168 @State translateX: number = 0; // 组件二偏移量 169 @State opacityValue: number = 1; // 组件二透明度 170 // 第二步:将状态变量设置到相关可动画属性接口 171 build() { 172 Row() { 173 // 组件一 174 Column() { 175 } 176 .rotate({ angle: this.rotateValue }) 177 .backgroundColor('#317AF7') 178 .justifyContent(FlexAlign.Center) 179 .width(100) 180 .height(100) 181 .borderRadius(30) 182 .onClick(() => { 183 // 第三步:调用keyframeAnimateTo接口 184 this.getUIContext()?.keyframeAnimateTo({ 185 iterations: 1, 186 expectedFrameRateRange: { 187 min: 10, 188 max: 120, 189 expected: 60, 190 } 191 }, [ 192 { 193 // 第一段关键帧动画时长为800ms,组件一顺时针旋转90度,组件二的透明度变从1变为0.6,组件二的translate从0位移到50 194 duration: 800, 195 event: () => { 196 this.rotateValue = 90; 197 this.opacityValue = 0.6; 198 this.translateX = 50; 199 } 200 }, 201 { 202 // 第二段关键帧动画时长为500ms,组件一逆时针旋转90度恢复至0度,组件二的透明度变从0.6变为1,组件二的translate从50位移到0 203 duration: 500, 204 event: () => { 205 this.rotateValue = 0; 206 this.opacityValue = 1; 207 this.translateX = 0; 208 } 209 } 210 ]); 211 }) 212 // 组件二 213 Column() { 214 } 215 .justifyContent(FlexAlign.Center) 216 .width(100) 217 .height(100) 218 .backgroundColor('#D94838') 219 .borderRadius(30) 220 .opacity(this.opacityValue) 221 .translate({ x: this.translateX }) 222 } 223 .width('100%') 224 .height('100%') 225 .justifyContent(FlexAlign.Center) 226 } 227} 228``` 229 230 231 232> **说明:** 233> - 在对组件的位置大小的变化做动画的时候,由于布局属性的改变会触发测量布局,性能开销大。[scale](../reference/apis-arkui/arkui-ts/ts-universal-attributes-transformation.md#scale)属性的改变不会触发测量布局,性能开销小。因此,在组件位置大小持续发生变化的场景,如跟手触发组件大小变化的场景,推荐使用scale。 234> 235> - 属性动画应该作用于始终存在的组件,对于将要出现或者将要消失的组件的动画应该使用[转场动画](arkts-transition-overview.md)。 236> 237> - 尽量不要使用动画结束回调。属性动画是对已经发生的状态进行的动画,不需要开发者去处理结束的逻辑。如果要使用结束回调,一定要正确处理连续操作的数据管理。