1# 自定义属性动画 2 3 4属性动画是可动画属性的参数值发生变化时,引起UI上产生的连续视觉效果。当参数值发生连续变化,且设置到可以引起UI发生变化的属性接口上时,就可以实现属性动画。 5 6 7ArkUI提供[@AnimatableExtend](../quick-start/arkts-animatable-extend.md)装饰器,用于自定义可动画属性接口。由于参数的数据类型必须具备一定程度的连续性,自定义可动画属性接口的参数类型仅支持number类型和实现[AnimtableArithmetic\<T>](../quick-start/arkts-animatable-extend.md)接口的自定义类型。通过自定义可动画属性接口和可动画数据类型,在使用animateTo或animation执行动画时,通过逐帧回调函数修改不可动画属性接口的值,能够让不可动画属性接口实现动画效果。 8 9 10## 使用number数据类型和\@AnimatableExtend装饰器改变字体大小 11 12 13```ts 14// 第一步:使用@AnimatableExtend装饰器,自定义可动画属性接口 15@AnimatableExtend(Text) function animatableFontSize(size: number) { 16 .fontSize(size) // 调用系统属性接口 17} 18 19@Entry 20@Component 21struct AnimatablePropertyExample { 22 @State fontSize: number = 20; 23 24 build() { 25 Column() { 26 Text("AnimatableProperty") 27 .animatableFontSize(this.fontSize) // 第二步:将自定义可动画属性接口设置到组件上 28 .animation({ duration: 1000, curve: "ease" }) // 第三步:为自定义可动画属性接口绑定动画 29 Button("Play") 30 .onClick(() => { 31 this.fontSize = this.fontSize == 20 ? 36 : 20; // 第四步:改变自定义可动画属性的参数,产生动画 32 }) 33 }.width("100%") 34 .padding(10) 35 } 36} 37``` 38 39 40 41 42 43 44## 使用自定义数据类型和\@AnimatableExtend装饰器改变折线 45 46 47```ts 48declare type Point = number[]; 49 50// 定义可动画属性接口的参数类型,实现AnimtableArithmetic<T>接口中加法、减法、乘法和判断相等函数 51class PointClass extends Array<number> { 52 constructor(value: Point) { 53 super(value[0], value[1]) 54 } 55 56 add(rhs: PointClass): PointClass { 57 let result: Point = new Array<number>() as Point; 58 for (let i = 0; i < 2; i++) { 59 result.push(rhs[i] + this[i]) 60 } 61 return new PointClass(result); 62 } 63 64 subtract(rhs: PointClass): PointClass { 65 let result: Point = new Array<number>() as Point; 66 for (let i = 0; i < 2; i++) { 67 result.push(this[i] - rhs[i]); 68 } 69 return new PointClass(result); 70 } 71 72 multiply(scale: number): PointClass { 73 let result: Point = new Array<number>() as Point; 74 for (let i = 0; i < 2; i++) { 75 result.push(this[i] * scale) 76 } 77 return new PointClass(result); 78 } 79} 80 81// 定义可动画属性接口的参数类型,实现AnimtableArithmetic<T>接口中加法、减法、乘法和判断相等函数 82// 模板T支持嵌套实现AnimtableArithmetic<T>的类型 83class PointVector extends Array<PointClass> implements AnimatableArithmetic<Array<Point>> { 84 constructor(initialValue: Array<Point>) { 85 super(); 86 if (initialValue.length) { 87 initialValue.forEach((p:Point) => this.push(new PointClass(p))) 88 } 89 } 90 91 // implement the IAnimatableArithmetic interface 92 plus(rhs: PointVector): PointVector { 93 let result = new PointVector([]); 94 const len = Math.min(this.length, rhs.length) 95 for (let i = 0; i < len; i++) { 96 result.push(this[i].add(rhs[i])) 97 } 98 return result; 99 } 100 101 subtract(rhs: PointVector): PointVector { 102 let result = new PointVector([]); 103 const len = Math.min(this.length, rhs.length) 104 for (let i = 0; i < len; i++) { 105 result.push(this[i].subtract(rhs[i])) 106 } 107 return result; 108 } 109 110 multiply(scale: number): PointVector { 111 let result = new PointVector([]); 112 for (let i = 0; i < this.length; i++) { 113 result.push(this[i].multiply(scale)) 114 } 115 return result; 116 } 117 118 equals(rhs: PointVector): boolean { 119 if (this.length !== rhs.length) { 120 return false; 121 } 122 for (let index = 0, size = this.length; index < size; ++index) { 123 if (this[index][0] !== rhs[index][0] || this[index][1] !== rhs[index][1]) { 124 return false; 125 } 126 } 127 return true; 128 } 129} 130 131function randomInt(min:number, max:number) { 132 return Math.floor(Math.random() * (max - min) + min); 133} 134 135// 自定义可动画属性接口 136@AnimatableExtend(Polyline) function animatablePoints(points: PointVector) { 137 .points(points) 138} 139 140// 自定义可动画属性接口 141@AnimatableExtend(Text) function animatableFontSize(size: number) { 142 .fontSize(size) 143} 144 145@Entry 146@Component 147struct AnimatedShape { 148 @State pointVec1: PointVector = new PointVector([ 149 [50, randomInt(0, 200)], 150 [100, randomInt(0, 200)], 151 [150, randomInt(0, 200)], 152 [250, randomInt(0, 200)], 153 [350, randomInt(0, 200)] 154 ]); 155 @State pointVec2: PointVector = new PointVector([ 156 [70, randomInt(0, 200)], 157 [120, randomInt(0, 200)], 158 [180, randomInt(0, 200)], 159 [220, randomInt(0, 200)], 160 [320, randomInt(0, 200)] 161 ]); 162 @State color: Color = Color.Green; 163 @State fontSize: number = 20.0; 164 @State polyline1Vec: PointVector = this.pointVec1; 165 @State polyline2Vec: PointVector = this.pointVec2; 166 167 build() { 168 Column() { 169 Text("AnimatableExtend test") 170 .width(400) 171 .height(30) 172 .margin(1) 173 .fontSize(25) 174 .textAlign(TextAlign.Center) 175 .backgroundColor("#ffee44") 176 .border({ width: '1vp', color: "#88ff00", radius: 20, style: BorderStyle.Solid }) 177 178 Polyline() 179 .width(400) 180 .height(240) 181 .backgroundColor("#eeaacc") 182 .fill(this.color) 183 .stroke(Color.Red) 184 .animatablePoints(this.polyline1Vec) 185 .animation({ duration: 2000, delay: 0, curve: Curve.Ease }) 186 187 Polyline() 188 .width(400) 189 .height(240) 190 .backgroundColor("#bbffcc") 191 .fill(this.color) 192 .stroke(Color.Red) 193 .animatablePoints(this.polyline2Vec) 194 .animation({ duration: 2000, delay: 0, curve: Curve.Ease }) 195 196 Text("Animatable Fontsize") 197 198 .animatableFontSize(this.fontSize) 199 .animation({ duration: 2000, delay: 0, curve: Curve.Ease }) 200 .width(400) 201 .height(150) 202 .margin(5) 203 .textAlign(TextAlign.Center) 204 .backgroundColor("#ffddcc") 205 .border({ width: '2vp', color: "#88ff00", radius: 20, style: BorderStyle.Solid }) 206 .onClick(() => { 207 console.log("Text onClick()") 208 }) 209 210 Row() { 211 Button("Polyline1 default") 212 .width(100).height(60) 213 .margin({ left: 5, right: 5 }) 214 .padding(10) 215 .onClick(() => { 216 217 if (this.polyline1Vec.equals(this.pointVec1)) { 218 this.polyline1Vec = this.pointVec2; 219 } else { 220 this.polyline1Vec = this.pointVec1; 221 } 222 }) 223 224 Button("Polyline2 ANIM") 225 .width(100).height(60) 226 .onClick(() => { 227 if (this.polyline2Vec.equals(this.pointVec1)) { 228 this.polyline2Vec = this.pointVec2; 229 } else { 230 this.polyline2Vec = this.pointVec1; 231 } 232 }) 233 234 Button("FontSize") 235 .width(100).height(60) 236 .margin({ left: 5, right: 5 }) 237 .onClick(() => { 238 this.fontSize = (this.fontSize == 20.0) ? 40.0 : 20.0; 239 }) 240 } 241 .alignItems(VerticalAlign.Center) 242 .margin(5) 243 244 } 245 .width('100%') 246 .alignItems(HorizontalAlign.Center) 247 } 248} 249``` 250 251 252 253 254## 相关实例 255 256针对自定义属性动画开发,有以下相关实例可供参考: 257 258- [自定义下拉刷新动画(ArkTS)(API9)](https://gitee.com/openharmony/codelabs/tree/master/ETSUI/AnimateRefresh)