• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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![en-us_image_0000001600119626](figures/en-us_image_0000001600119626.gif)
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![en-us_image_0000001592669598](figures/en-us_image_0000001592669598.gif)
253