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