1# 自定义属性动画 2<!--Kit: ArkUI--> 3<!--Subsystem: ArkUI--> 4<!--Owner: @CCFFWW--> 5<!--Designer: @yangfan229--> 6<!--Tester: @lxl007--> 7<!--Adviser: @HelloCrease--> 8 9 10属性动画是可动画属性的参数值发生变化时,引起UI上产生的连续视觉效果。当参数值发生连续变化,且设置到可以引起UI发生变化的属性接口上时,就可以实现属性动画。 11 12 13ArkUI提供[@AnimatableExtend装饰器](../ui/state-management/arkts-animatable-extend.md),用于自定义可动画属性接口。由于参数的数据类型必须具备一定程度的连续性,自定义可动画属性接口的参数类型仅支持number类型和实现[AnimatableArithmetic\<T>接口](../ui/state-management/arkts-animatable-extend.md#animatablearithmetict接口说明)的自定义类型。通过自定义可动画属性接口和可动画数据类型,在使用animateTo或animation执行动画时,通过逐帧回调函数修改不可动画属性接口的值,能够让不可动画属性接口实现动画效果。也可通过逐帧回调函数每帧修改可动画属性的值,实现逐帧布局的效果。 14 15 16## 使用number数据类型和\@AnimatableExtend装饰器改变Text组件宽度实现逐帧布局的效果 17 18 19```ts 20// 第一步:使用@AnimatableExtend装饰器,自定义可动画属性接口 21@AnimatableExtend(Text) 22function animatableWidth(width: number) { 23 .width(width) // 调用系统属性接口,逐帧回调函数每帧修改可动画属性的值,实现逐帧布局的效果。 24} 25 26@Entry 27@Component 28struct AnimatablePropertyExample { 29 @State textWidth: number = 80; 30 31 build() { 32 Column() { 33 Text("AnimatableProperty") 34 .animatableWidth(this.textWidth)// 第二步:将自定义可动画属性接口设置到组件上 35 .animation({ duration: 2000, curve: Curve.Ease }) // 第三步:为自定义可动画属性接口绑定动画 36 Button("Play") 37 .onClick(() => { 38 this.textWidth = this.textWidth == 80 ? 160 : 80; // 第四步:改变自定义可动画属性的参数,产生动画 39 }) 40 }.width("100%") 41 .padding(10) 42 } 43} 44``` 45 46 47 48 49 50 51## 使用自定义数据类型和\@AnimatableExtend装饰器改变图形形状 52 53 54```ts 55declare type Point = number[]; 56 57// 定义可动画属性接口的参数类型,实现AnimatableArithmetic<T>接口中加法、减法、乘法和判断相等函数 58class PointClass extends Array<number> { 59 constructor(value: Point) { 60 super(value[0], value[1]) 61 } 62 63 add(rhs: PointClass): PointClass { 64 let result: Point = new Array<number>() as Point; 65 for (let i = 0; i < 2; i++) { 66 result.push(rhs[i] + this[i]) 67 } 68 return new PointClass(result); 69 } 70 71 subtract(rhs: PointClass): PointClass { 72 let result: Point = new Array<number>() as Point; 73 for (let i = 0; i < 2; i++) { 74 result.push(this[i] - rhs[i]); 75 } 76 return new PointClass(result); 77 } 78 79 multiply(scale: number): PointClass { 80 let result: Point = new Array<number>() as Point; 81 for (let i = 0; i < 2; i++) { 82 result.push(this[i] * scale) 83 } 84 return new PointClass(result); 85 } 86} 87 88// 定义可动画属性接口的参数类型,实现AnimatableArithmetic<T>接口中加法、减法、乘法和判断相等函数 89// 模板T支持嵌套实现AnimatableArithmetic<T>的类型 90class PointVector extends Array<PointClass> implements AnimatableArithmetic<Array<Point>> { 91 constructor(initialValue: Array<Point>) { 92 super(); 93 if (initialValue.length) { 94 initialValue.forEach((p: Point) => this.push(new PointClass(p))) 95 } 96 } 97 98 // implement the IAnimatableArithmetic interface 99 plus(rhs: PointVector): PointVector { 100 let result = new PointVector([]); 101 const len = Math.min(this.length, rhs.length) 102 for (let i = 0; i < len; i++) { 103 result.push(this[i].add(rhs[i])) 104 } 105 return result; 106 } 107 108 subtract(rhs: PointVector): PointVector { 109 let result = new PointVector([]); 110 const len = Math.min(this.length, rhs.length) 111 for (let i = 0; i < len; i++) { 112 result.push(this[i].subtract(rhs[i])) 113 } 114 return result; 115 } 116 117 multiply(scale: number): PointVector { 118 let result = new PointVector([]); 119 for (let i = 0; i < this.length; i++) { 120 result.push(this[i].multiply(scale)) 121 } 122 return result; 123 } 124 125 equals(rhs: PointVector): boolean { 126 if (this.length !== rhs.length) { 127 return false; 128 } 129 for (let index = 0, size = this.length; index < size; ++index) { 130 if (this[index][0] !== rhs[index][0] || this[index][1] !== rhs[index][1]) { 131 return false; 132 } 133 } 134 return true; 135 } 136} 137 138// 自定义可动画属性接口 139@AnimatableExtend(Polyline) 140function animatablePoints(points: PointVector) { 141 .points(points) 142} 143 144@Entry 145@Component 146struct AnimatedShape { 147 squareStartPointX: number = 75; 148 squareStartPointY: number = 25; 149 squareWidth: number = 150; 150 squareEndTranslateX: number = 50; 151 squareEndTranslateY: number = 50; 152 @State pointVec1: PointVector = new PointVector([ 153 [this.squareStartPointX, this.squareStartPointY], 154 [this.squareStartPointX + this.squareWidth, this.squareStartPointY], 155 [this.squareStartPointX + this.squareWidth, this.squareStartPointY + this.squareWidth], 156 [this.squareStartPointX, this.squareStartPointY + this.squareWidth] 157 ]); 158 @State pointVec2: PointVector = new PointVector([ 159 [this.squareStartPointX + this.squareEndTranslateX, this.squareStartPointY + this.squareStartPointY], 160 [this.squareStartPointX + this.squareWidth + this.squareEndTranslateX, 161 this.squareStartPointY + this.squareStartPointY], 162 [this.squareStartPointX + this.squareWidth, this.squareStartPointY + this.squareWidth], 163 [this.squareStartPointX, this.squareStartPointY + this.squareWidth] 164 ]); 165 @State color: Color = Color.Green; 166 @State fontSize: number = 20.0; 167 @State polyline1Vec: PointVector = this.pointVec1; 168 @State polyline2Vec: PointVector = this.pointVec2; 169 170 build() { 171 Row() { 172 Polyline() 173 .width(300) 174 .height(200) 175 .backgroundColor("#0C000000") 176 .fill('#317AF7') 177 .animatablePoints(this.polyline1Vec) 178 .animation({ duration: 2000, delay: 0, curve: Curve.Ease }) 179 .onClick(() => { 180 181 if (this.polyline1Vec.equals(this.pointVec1)) { 182 this.polyline1Vec = this.pointVec2; 183 } else { 184 this.polyline1Vec = this.pointVec1; 185 } 186 }) 187 } 188 .width('100%').height('100%').justifyContent(FlexAlign.Center) 189 } 190} 191``` 192 193 194 195 196## 相关实例 197 198针对自定义属性动画开发,有以下相关实例可供参考: 199 200- [自定义下拉刷新动画(ArkTS)(API9)](https://gitee.com/openharmony/codelabs/tree/master/ETSUI/AnimateRefresh)