1e41f4b71Sopenharmony_ci# Custom Property Animation 2e41f4b71Sopenharmony_ci 3e41f4b71Sopenharmony_ci 4e41f4b71Sopenharmony_ciThe 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. 5e41f4b71Sopenharmony_ci 6e41f4b71Sopenharmony_ci 7e41f4b71Sopenharmony_ciArkUI provides the [@AnimatableExtend](../quick-start/arkts-animatable-extend.md) decorator for customizing animatable property APIs. Since the data type of the parameters must have a certain degree of continuity, the parameter types for custom animatable property APIs only support the number type and custom types that implement the [AnimtableArithmetic\<T>](../quick-start/arkts-animatable-extend.md) API. With custom animatable property APIs and animatable data types, you can achieve animation effects on non-animatable property APIs by modifying their values through a per-frame callback function when using **animateTo** or **animation**. Additionally, you can implement frame-by-frame layout effects by modifying the values of animatable properties in the per-frame callback function. 8e41f4b71Sopenharmony_ci 9e41f4b71Sopenharmony_ci 10e41f4b71Sopenharmony_ci## Implementing Frame-by-Frame Layout Effects for Text Component Width Using the number Data Type and @AnimatableExtend Decorator 11e41f4b71Sopenharmony_ci 12e41f4b71Sopenharmony_ci 13e41f4b71Sopenharmony_ci```ts 14e41f4b71Sopenharmony_ci// Step 1: Use the @AnimatableExtend decorator to customize an animatable property API. 15e41f4b71Sopenharmony_ci@AnimatableExtend(Text) 16e41f4b71Sopenharmony_cifunction animatableWidth(width: number) { 17e41f4b71Sopenharmony_ci .width(width)// Call the system property API. The per-frame callback function modifies the value of the animatable property on each frame, achieving the effect of frame-by-frame layout. 18e41f4b71Sopenharmony_ci} 19e41f4b71Sopenharmony_ci 20e41f4b71Sopenharmony_ci@Entry 21e41f4b71Sopenharmony_ci@Component 22e41f4b71Sopenharmony_cistruct AnimatablePropertyExample { 23e41f4b71Sopenharmony_ci @State textWidth: number = 80; 24e41f4b71Sopenharmony_ci 25e41f4b71Sopenharmony_ci build() { 26e41f4b71Sopenharmony_ci Column() { 27e41f4b71Sopenharmony_ci Text("AnimatableProperty") 28e41f4b71Sopenharmony_ci .animatableWidth(this.textWidth)// Step 2: Set the custom animatable property API on the component. 29e41f4b71Sopenharmony_ci .animation({ duration: 2000, curve: Curve.Ease })// Step 3: Bind an animation to the custom animatable property API. 30e41f4b71Sopenharmony_ci Button("Play") 31e41f4b71Sopenharmony_ci .onClick(() => { 32e41f4b71Sopenharmony_ci this.textWidth = this.textWidth == 80 ? 160 : 80;// Change the parameters of the custom animatable property to create an animation. 33e41f4b71Sopenharmony_ci }) 34e41f4b71Sopenharmony_ci }.width("100%") 35e41f4b71Sopenharmony_ci .padding(10) 36e41f4b71Sopenharmony_ci } 37e41f4b71Sopenharmony_ci} 38e41f4b71Sopenharmony_ci``` 39e41f4b71Sopenharmony_ci 40e41f4b71Sopenharmony_ci 41e41f4b71Sopenharmony_ci 42e41f4b71Sopenharmony_ci 43e41f4b71Sopenharmony_ci 44e41f4b71Sopenharmony_ci 45e41f4b71Sopenharmony_ci## Changing Graphic Shapes Using Custom Data Types and \@AnimatableExtend Decorator 46e41f4b71Sopenharmony_ci 47e41f4b71Sopenharmony_ci 48e41f4b71Sopenharmony_ci```ts 49e41f4b71Sopenharmony_cideclare type Point = number[]; 50e41f4b71Sopenharmony_ci 51e41f4b71Sopenharmony_ci// Define the parameter type of the animatable property API and implement the addition, subtraction, multiplication, and equivalence judgment functions in the AnimtableArithmetic<T> API. 52e41f4b71Sopenharmony_ciclass PointClass extends Array<number> { 53e41f4b71Sopenharmony_ci constructor(value: Point) { 54e41f4b71Sopenharmony_ci super(value[0], value[1]) 55e41f4b71Sopenharmony_ci } 56e41f4b71Sopenharmony_ci 57e41f4b71Sopenharmony_ci add(rhs: PointClass): PointClass { 58e41f4b71Sopenharmony_ci let result: Point = new Array<number>() as Point; 59e41f4b71Sopenharmony_ci for (let i = 0; i < 2; i++) { 60e41f4b71Sopenharmony_ci result.push(rhs[i] + this[i]) 61e41f4b71Sopenharmony_ci } 62e41f4b71Sopenharmony_ci return new PointClass(result); 63e41f4b71Sopenharmony_ci } 64e41f4b71Sopenharmony_ci 65e41f4b71Sopenharmony_ci subtract(rhs: PointClass): PointClass { 66e41f4b71Sopenharmony_ci let result: Point = new Array<number>() as Point; 67e41f4b71Sopenharmony_ci for (let i = 0; i < 2; i++) { 68e41f4b71Sopenharmony_ci result.push(this[i] - rhs[i]); 69e41f4b71Sopenharmony_ci } 70e41f4b71Sopenharmony_ci return new PointClass(result); 71e41f4b71Sopenharmony_ci } 72e41f4b71Sopenharmony_ci 73e41f4b71Sopenharmony_ci multiply(scale: number): PointClass { 74e41f4b71Sopenharmony_ci let result: Point = new Array<number>() as Point; 75e41f4b71Sopenharmony_ci for (let i = 0; i < 2; i++) { 76e41f4b71Sopenharmony_ci result.push(this[i] * scale) 77e41f4b71Sopenharmony_ci } 78e41f4b71Sopenharmony_ci return new PointClass(result); 79e41f4b71Sopenharmony_ci } 80e41f4b71Sopenharmony_ci} 81e41f4b71Sopenharmony_ci 82e41f4b71Sopenharmony_ci// Define the parameter type of the animatable property API and implement the addition, subtraction, multiplication, and equivalence judgment functions in the AnimtableArithmetic<T> API. 83e41f4b71Sopenharmony_ci// Template T supports nested implementation of the AnimtableArithmetic<T> type. 84e41f4b71Sopenharmony_ciclass PointVector extends Array<PointClass> implements AnimatableArithmetic<Array<Point>> { 85e41f4b71Sopenharmony_ci constructor(initialValue: Array<Point>) { 86e41f4b71Sopenharmony_ci super(); 87e41f4b71Sopenharmony_ci if (initialValue.length) { 88e41f4b71Sopenharmony_ci initialValue.forEach((p: Point) => this.push(new PointClass(p))) 89e41f4b71Sopenharmony_ci } 90e41f4b71Sopenharmony_ci } 91e41f4b71Sopenharmony_ci 92e41f4b71Sopenharmony_ci // implement the IAnimatableArithmetic interface 93e41f4b71Sopenharmony_ci plus(rhs: PointVector): PointVector { 94e41f4b71Sopenharmony_ci let result = new PointVector([]); 95e41f4b71Sopenharmony_ci const len = Math.min(this.length, rhs.length) 96e41f4b71Sopenharmony_ci for (let i = 0; i < len; i++) { 97e41f4b71Sopenharmony_ci result.push(this[i].add(rhs[i])) 98e41f4b71Sopenharmony_ci } 99e41f4b71Sopenharmony_ci return result; 100e41f4b71Sopenharmony_ci } 101e41f4b71Sopenharmony_ci 102e41f4b71Sopenharmony_ci subtract(rhs: PointVector): PointVector { 103e41f4b71Sopenharmony_ci let result = new PointVector([]); 104e41f4b71Sopenharmony_ci const len = Math.min(this.length, rhs.length) 105e41f4b71Sopenharmony_ci for (let i = 0; i < len; i++) { 106e41f4b71Sopenharmony_ci result.push(this[i].subtract(rhs[i])) 107e41f4b71Sopenharmony_ci } 108e41f4b71Sopenharmony_ci return result; 109e41f4b71Sopenharmony_ci } 110e41f4b71Sopenharmony_ci 111e41f4b71Sopenharmony_ci multiply(scale: number): PointVector { 112e41f4b71Sopenharmony_ci let result = new PointVector([]); 113e41f4b71Sopenharmony_ci for (let i = 0; i < this.length; i++) { 114e41f4b71Sopenharmony_ci result.push(this[i].multiply(scale)) 115e41f4b71Sopenharmony_ci } 116e41f4b71Sopenharmony_ci return result; 117e41f4b71Sopenharmony_ci } 118e41f4b71Sopenharmony_ci 119e41f4b71Sopenharmony_ci equals(rhs: PointVector): boolean { 120e41f4b71Sopenharmony_ci if (this.length !== rhs.length) { 121e41f4b71Sopenharmony_ci return false; 122e41f4b71Sopenharmony_ci } 123e41f4b71Sopenharmony_ci for (let index = 0, size = this.length; index < size; ++index) { 124e41f4b71Sopenharmony_ci if (this[index][0] !== rhs[index][0] || this[index][1] !== rhs[index][1]) { 125e41f4b71Sopenharmony_ci return false; 126e41f4b71Sopenharmony_ci } 127e41f4b71Sopenharmony_ci } 128e41f4b71Sopenharmony_ci return true; 129e41f4b71Sopenharmony_ci } 130e41f4b71Sopenharmony_ci} 131e41f4b71Sopenharmony_ci 132e41f4b71Sopenharmony_ci// Define a custom animatable property API. 133e41f4b71Sopenharmony_ci@AnimatableExtend(Polyline) 134e41f4b71Sopenharmony_cifunction animatablePoints(points: PointVector) { 135e41f4b71Sopenharmony_ci .points(points) 136e41f4b71Sopenharmony_ci} 137e41f4b71Sopenharmony_ci 138e41f4b71Sopenharmony_ci@Entry 139e41f4b71Sopenharmony_ci@Component 140e41f4b71Sopenharmony_cistruct AnimatedShape { 141e41f4b71Sopenharmony_ci squareStartPointX: number = 75; 142e41f4b71Sopenharmony_ci squareStartPointY: number = 25; 143e41f4b71Sopenharmony_ci squareWidth: number = 150; 144e41f4b71Sopenharmony_ci squareEndTranslateX: number = 50; 145e41f4b71Sopenharmony_ci squareEndTranslateY: number = 50; 146e41f4b71Sopenharmony_ci @State pointVec1: PointVector = new PointVector([ 147e41f4b71Sopenharmony_ci [this.squareStartPointX, this.squareStartPointY], 148e41f4b71Sopenharmony_ci [this.squareStartPointX + this.squareWidth, this.squareStartPointY], 149e41f4b71Sopenharmony_ci [this.squareStartPointX + this.squareWidth, this.squareStartPointY + this.squareWidth], 150e41f4b71Sopenharmony_ci [this.squareStartPointX, this.squareStartPointY + this.squareWidth] 151e41f4b71Sopenharmony_ci ]); 152e41f4b71Sopenharmony_ci @State pointVec2: PointVector = new PointVector([ 153e41f4b71Sopenharmony_ci [this.squareStartPointX + this.squareEndTranslateX, this.squareStartPointY + this.squareStartPointY], 154e41f4b71Sopenharmony_ci [this.squareStartPointX + this.squareWidth + this.squareEndTranslateX, this.squareStartPointY + this.squareStartPointY], 155e41f4b71Sopenharmony_ci [this.squareStartPointX + this.squareWidth, this.squareStartPointY + this.squareWidth], 156e41f4b71Sopenharmony_ci [this.squareStartPointX, this.squareStartPointY + this.squareWidth] 157e41f4b71Sopenharmony_ci ]); 158e41f4b71Sopenharmony_ci @State color: Color = Color.Green; 159e41f4b71Sopenharmony_ci @State fontSize: number = 20.0; 160e41f4b71Sopenharmony_ci @State polyline1Vec: PointVector = this.pointVec1; 161e41f4b71Sopenharmony_ci @State polyline2Vec: PointVector = this.pointVec2; 162e41f4b71Sopenharmony_ci 163e41f4b71Sopenharmony_ci build() { 164e41f4b71Sopenharmony_ci Row() { 165e41f4b71Sopenharmony_ci Polyline() 166e41f4b71Sopenharmony_ci .width(300) 167e41f4b71Sopenharmony_ci .height(200) 168e41f4b71Sopenharmony_ci .backgroundColor("#0C000000") 169e41f4b71Sopenharmony_ci .fill('#317AF7') 170e41f4b71Sopenharmony_ci .animatablePoints(this.polyline1Vec) 171e41f4b71Sopenharmony_ci .animation({ duration: 2000, delay: 0, curve: Curve.Ease }) 172e41f4b71Sopenharmony_ci .onClick(() => { 173e41f4b71Sopenharmony_ci 174e41f4b71Sopenharmony_ci if (this.polyline1Vec.equals(this.pointVec1)) { 175e41f4b71Sopenharmony_ci this.polyline1Vec = this.pointVec2; 176e41f4b71Sopenharmony_ci } else { 177e41f4b71Sopenharmony_ci this.polyline1Vec = this.pointVec1; 178e41f4b71Sopenharmony_ci } 179e41f4b71Sopenharmony_ci }) 180e41f4b71Sopenharmony_ci } 181e41f4b71Sopenharmony_ci .width('100%').height('100%').justifyContent(FlexAlign.Center) 182e41f4b71Sopenharmony_ci } 183e41f4b71Sopenharmony_ci} 184e41f4b71Sopenharmony_ci``` 185e41f4b71Sopenharmony_ci 186e41f4b71Sopenharmony_ci 187e41f4b71Sopenharmony_ci 188