1# Custom Property Animation 2 3 4The property animation is an illusion of movement created on the UI when the value of an animatable property changes over time. It is implemented by setting continuous value changes of a property to the property API that can cause the UI re-render. 5 6 7ArkUI provides the [@AnimatableExtend](../quick-start/arkts-animatable-extend.md) decorator for customizing animatable property APIs, which accepts only parameters of the number type or a custom type that implements [AnimtableArithmetic\<T>](../quick-start/arkts-animatable-extend.md) to deliver continuous value changes. During execution of **animateTo** or **animation** with a custom animatable property API and the animatable data type, the frame-by-frame callback is used to change the value of a non-animatable property so that an animation effect can be applied to the property. 8 9 10## Animating Font Size Changes with Number Data Type and \@AnimatableExtend Decorator 11 12 13```ts 14// Step 1: Use the @AnimatableExtend decorator to customize an animatable property API. 15@AnimatableExtend(Text) function animatableFontSize(size: number) { 16 .fontSize(size) // Invoke the system property API. 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) // Step 2: Set the custom animatable property API on the component. 28 .animation({ duration: 1000, curve: "ease" }) // Step 3: Bind an animation to the custom animatable property API. 29 Button("Play") 30 .onClick(() => { 31 this.fontSize = this.fontSize == 20 ? 36 : 20; // Step 4: Change the value of the custom animatable property to generate an animation. 32 }) 33 }.width("100%") 34 .padding(10) 35 } 36} 37``` 38 39 40 41 42 43 44## Animating Polyline Changes with Custom Data Type and \@AnimatableExtend Decorator 45 46 47```ts 48declare type Point = number[]; 49 50// Define the parameter type of the animatable property API and implement the addition, subtraction, multiplication, and equivalence judgment functions in the AnimtableArithmetic<T> API. 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// Define the parameter type of the animatable property API and implement the addition, subtraction, multiplication, and equivalence judgment functions in the AnimtableArithmetic<T> API. 82// Template T supports nested implementation of the AnimtableArithmetic<T> type. 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// Define a custom animatable property API. 136@AnimatableExtend(Polyline) function animatablePoints(points: PointVector) { 137 .points(points) 138} 139 140// Define a custom animatable property API. 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