1# \@AnimatableExtend装饰器:定义可动画属性
2
3@AnimatableExtend装饰器用于自定义可动画的属性方法,在这个属性方法中修改组件不可动画的属性。在动画执行过程时,通过逐帧回调函数修改不可动画属性值,让不可动画属性也能实现动画效果。也可通过逐帧回调函数修改可动画属性的值,实现逐帧布局的效果。
4
5- 可动画属性:如果一个属性方法在animation属性前调用,改变这个属性的值可以使animation属性的动画效果生效,这个属性称为可动画属性。比如height、width、backgroundColor、translate属性,和Text组件的fontSize属性等。
6
7- 不可动画属性:如果一个属性方法在animation属性前调用,改变这个属性的值不能使animation属性的动画效果生效,这个属性称为不可动画属性。比如Polyline组件的points属性等。
8
9>  **说明:**
10>
11>  该装饰器从API Version 10开始支持。后续版本如有新增内容,则采用上角标单独标记该内容的起始版本。
12>
13> 从API version 11开始,该装饰器支持在原子化服务中使用。
14
15## 装饰器使用说明
16
17
18### 语法
19
20
21```ts
22@AnimatableExtend(UIComponentName) function functionName(value: typeName) {
23  .propertyName(value)
24}
25```
26
27- \@AnimatableExtend仅支持定义在全局,不支持在组件内部定义。
28- \@AnimatableExtend定义的函数参数类型必须为number类型或者实现 AnimatableArithmetic\<T\>接口的自定义类型。
29- \@AnimatableExtend定义的函数体内只能调用\@AnimatableExtend括号内组件的属性方法。
30
31### AnimatableArithmetic\<T\>接口说明
32该接口定义非number数据类型的动画运算规则。对非number类型的数据(如数组、结构体、颜色等)做动画,需要实现AnimatableArithmetic\<T\>接口中加法、减法、乘法和判断相等函数,
33使得该数据能参与动画的插值运算和识别该数据是否发生改变。即定义它们为实现了AnimatableArithmetic\<T\>接口的类型。
34| 名称 | 入参类型 | 返回值类型 | 说明
35| -------- | -------- |-------- |-------- |
36| plus | AnimatableArithmetic\<T\> | AnimatableArithmetic\<T\> | 定义该数据类型的加法运算规则 |
37| subtract | AnimatableArithmetic\<T\> | AnimatableArithmetic\<T\> | 定义该数据类型的减法运算规则 |
38| multiply | number | AnimatableArithmetic\<T\> | 定义该数据类型的乘法运算规则 |
39| equals | AnimatableArithmetic\<T\> | boolean | 定义该数据类型的相等判断规则|
40
41## 使用场景
42
43以下示例通过改变Text组件宽度实现逐帧布局的效果。
44
45
46```ts
47@AnimatableExtend(Text)
48function animatableWidth(width: number) {
49  .width(width)
50}
51
52@Entry
53@Component
54struct AnimatablePropertyExample {
55  @State textWidth: number = 80;
56
57  build() {
58    Column() {
59      Text("AnimatableProperty")
60        .animatableWidth(this.textWidth)
61        .animation({ duration: 2000, curve: Curve.Ease })
62      Button("Play")
63        .onClick(() => {
64          this.textWidth = this.textWidth == 80 ? 160 : 80;
65        })
66    }.width("100%")
67    .padding(10)
68  }
69}
70```
71![image](figures/AnimatableProperty.gif)
72
73
74以下示例实现折线的动画效果。
75
76
77```ts
78class Point {
79  x: number
80  y: number
81
82  constructor(x: number, y: number) {
83    this.x = x
84    this.y = y
85  }
86  plus(rhs: Point): Point {
87    return new Point(this.x + rhs.x, this.y + rhs.y)
88  }
89  subtract(rhs: Point): Point {
90    return new Point(this.x - rhs.x, this.y - rhs.y)
91  }
92  multiply(scale: number): Point {
93    return new Point(this.x * scale, this.y * scale)
94  }
95  equals(rhs: Point): boolean {
96    return this.x === rhs.x && this.y === rhs.y
97  }
98}
99
100// PointVector实现了AnimatableArithmetic<T>接口
101class PointVector extends Array<Point> implements AnimatableArithmetic<PointVector> {
102  constructor(value: Array<Point>) {
103    super();
104    value.forEach(p => this.push(p))
105  }
106  plus(rhs: PointVector): PointVector {
107    let result = new PointVector([])
108    const len = Math.min(this.length, rhs.length)
109    for (let i = 0; i < len; i++) {
110      result.push((this as Array<Point>)[i].plus((rhs as Array<Point>)[i]))
111    }
112    return result
113  }
114  subtract(rhs: PointVector): PointVector {
115    let result = new PointVector([])
116    const len = Math.min(this.length, rhs.length)
117    for (let i = 0; i < len; i++) {
118      result.push((this as Array<Point>)[i].subtract((rhs as Array<Point>)[i]))
119    }
120    return result
121  }
122  multiply(scale: number): PointVector {
123    let result = new PointVector([])
124    for (let i = 0; i < this.length; i++) {
125      result.push((this as Array<Point>)[i].multiply(scale))
126    }
127    return result
128  }
129  equals(rhs: PointVector): boolean {
130    if (this.length != rhs.length) {
131      return false
132    }
133    for (let i = 0; i < this.length; i++) {
134      if (!(this as Array<Point>)[i].equals((rhs as Array<Point>)[i])) {
135        return false
136      }
137    }
138    return true
139  }
140  get(): Array<Object[]> {
141    let result: Array<Object[]> = []
142    this.forEach(p => result.push([p.x, p.y]))
143    return result
144  }
145}
146
147@AnimatableExtend(Polyline) function animatablePoints(points: PointVector) {
148  .points(points.get())
149}
150
151@Entry
152@Component
153struct AnimatablePropertyExample {
154  @State points: PointVector = new PointVector([
155    new Point(50, Math.random() * 200),
156    new Point(100, Math.random() * 200),
157    new Point(150, Math.random() * 200),
158    new Point(200, Math.random() * 200),
159    new Point(250, Math.random() * 200),
160  ])
161  build() {
162    Column() {
163      Polyline()
164        .animatablePoints(this.points)
165        .animation({duration: 1000, curve: Curve.Ease}) // 设置动画参数
166        .size({height:220, width:300})
167        .fill(Color.Green)
168        .stroke(Color.Red)
169        .backgroundColor('#eeaacc')
170      Button("Play")
171        .onClick(() => {
172          // points是实现了可动画协议的数据类型,points在动画过程中可按照定义的运算规则、动画参数从之前的PointVector变为新的PointVector数据,产生每一帧的PointVector数据,进而产生动画
173          this.points = new PointVector([
174            new Point(50, Math.random() * 200),
175            new Point(100, Math.random() * 200),
176            new Point(150, Math.random() * 200),
177            new Point(200, Math.random() * 200),
178            new Point(250, Math.random() * 200),
179          ])
180        })
181    }.width("100%")
182    .padding(10)
183  }
184}
185```
186![image](figures/animatable-points.gif)