• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# 自定义属性动画
2<!--Kit: ArkUI-->
3<!--Subsystem: ArkUI-->
4<!--Owner: @CCFFWW-->
5<!--Designer: @yangfan229-->
6<!--Tester: @lxl007-->
7<!--Adviser: @HelloCrease-->
8
9
10属性动画是可动画属性的参数值发生变化时,引起UI上产生的连续视觉效果。当参数值发生连续变化,且设置到可以引起UI发生变化的属性接口上时,就可以实现属性动画。
11
12
13ArkUI提供[@AnimatableExtend装饰器](../ui/state-management/arkts-animatable-extend.md),用于自定义可动画属性接口。由于参数的数据类型必须具备一定程度的连续性,自定义可动画属性接口的参数类型仅支持number类型和实现[AnimatableArithmetic\<T>接口](../ui/state-management/arkts-animatable-extend.md#animatablearithmetict接口说明)的自定义类型。通过自定义可动画属性接口和可动画数据类型,在使用animateTo或animation执行动画时,通过逐帧回调函数修改不可动画属性接口的值,能够让不可动画属性接口实现动画效果。也可通过逐帧回调函数每帧修改可动画属性的值,实现逐帧布局的效果。
14
15
16## 使用number数据类型和\@AnimatableExtend装饰器改变Text组件宽度实现逐帧布局的效果
17
18
19```ts
20// 第一步:使用@AnimatableExtend装饰器,自定义可动画属性接口
21@AnimatableExtend(Text)
22function animatableWidth(width: number) {
23  .width(width) // 调用系统属性接口,逐帧回调函数每帧修改可动画属性的值,实现逐帧布局的效果。
24}
25
26@Entry
27@Component
28struct AnimatablePropertyExample {
29  @State textWidth: number = 80;
30
31  build() {
32    Column() {
33      Text("AnimatableProperty")
34        .animatableWidth(this.textWidth)// 第二步:将自定义可动画属性接口设置到组件上
35        .animation({ duration: 2000, curve: Curve.Ease }) // 第三步:为自定义可动画属性接口绑定动画
36      Button("Play")
37        .onClick(() => {
38          this.textWidth = this.textWidth == 80 ? 160 : 80; // 第四步:改变自定义可动画属性的参数,产生动画
39        })
40    }.width("100%")
41    .padding(10)
42  }
43}
44```
45
46
47
48![zh-cn_image_0000001600119626](figures/zh-cn_image_0000001600119626.gif)
49
50
51## 使用自定义数据类型和\@AnimatableExtend装饰器改变图形形状
52
53
54```ts
55declare type Point = number[];
56
57// 定义可动画属性接口的参数类型,实现AnimatableArithmetic<T>接口中加法、减法、乘法和判断相等函数
58class PointClass extends Array<number> {
59  constructor(value: Point) {
60    super(value[0], value[1])
61  }
62
63  add(rhs: PointClass): PointClass {
64    let result: Point = new Array<number>() as Point;
65    for (let i = 0; i < 2; i++) {
66      result.push(rhs[i] + this[i])
67    }
68    return new PointClass(result);
69  }
70
71  subtract(rhs: PointClass): PointClass {
72    let result: Point = new Array<number>() as Point;
73    for (let i = 0; i < 2; i++) {
74      result.push(this[i] - rhs[i]);
75    }
76    return new PointClass(result);
77  }
78
79  multiply(scale: number): PointClass {
80    let result: Point = new Array<number>() as Point;
81    for (let i = 0; i < 2; i++) {
82      result.push(this[i] * scale)
83    }
84    return new PointClass(result);
85  }
86}
87
88// 定义可动画属性接口的参数类型,实现AnimatableArithmetic<T>接口中加法、减法、乘法和判断相等函数
89// 模板T支持嵌套实现AnimatableArithmetic<T>的类型
90class PointVector extends Array<PointClass> implements AnimatableArithmetic<Array<Point>> {
91  constructor(initialValue: Array<Point>) {
92    super();
93    if (initialValue.length) {
94      initialValue.forEach((p: Point) => this.push(new PointClass(p)))
95    }
96  }
97
98  // implement the IAnimatableArithmetic interface
99  plus(rhs: PointVector): PointVector {
100    let result = new PointVector([]);
101    const len = Math.min(this.length, rhs.length)
102    for (let i = 0; i < len; i++) {
103      result.push(this[i].add(rhs[i]))
104    }
105    return result;
106  }
107
108  subtract(rhs: PointVector): PointVector {
109    let result = new PointVector([]);
110    const len = Math.min(this.length, rhs.length)
111    for (let i = 0; i < len; i++) {
112      result.push(this[i].subtract(rhs[i]))
113    }
114    return result;
115  }
116
117  multiply(scale: number): PointVector {
118    let result = new PointVector([]);
119    for (let i = 0; i < this.length; i++) {
120      result.push(this[i].multiply(scale))
121    }
122    return result;
123  }
124
125  equals(rhs: PointVector): boolean {
126    if (this.length !== rhs.length) {
127      return false;
128    }
129    for (let index = 0, size = this.length; index < size; ++index) {
130      if (this[index][0] !== rhs[index][0] || this[index][1] !== rhs[index][1]) {
131        return false;
132      }
133    }
134    return true;
135  }
136}
137
138// 自定义可动画属性接口
139@AnimatableExtend(Polyline)
140function animatablePoints(points: PointVector) {
141  .points(points)
142}
143
144@Entry
145@Component
146struct AnimatedShape {
147  squareStartPointX: number = 75;
148  squareStartPointY: number = 25;
149  squareWidth: number = 150;
150  squareEndTranslateX: number = 50;
151  squareEndTranslateY: number = 50;
152  @State pointVec1: PointVector = new PointVector([
153    [this.squareStartPointX, this.squareStartPointY],
154    [this.squareStartPointX + this.squareWidth, this.squareStartPointY],
155    [this.squareStartPointX + this.squareWidth, this.squareStartPointY + this.squareWidth],
156    [this.squareStartPointX, this.squareStartPointY + this.squareWidth]
157  ]);
158  @State pointVec2: PointVector = new PointVector([
159    [this.squareStartPointX + this.squareEndTranslateX, this.squareStartPointY + this.squareStartPointY],
160    [this.squareStartPointX + this.squareWidth + this.squareEndTranslateX,
161      this.squareStartPointY + this.squareStartPointY],
162    [this.squareStartPointX + this.squareWidth, this.squareStartPointY + this.squareWidth],
163    [this.squareStartPointX, this.squareStartPointY + this.squareWidth]
164  ]);
165  @State color: Color = Color.Green;
166  @State fontSize: number = 20.0;
167  @State polyline1Vec: PointVector = this.pointVec1;
168  @State polyline2Vec: PointVector = this.pointVec2;
169
170  build() {
171    Row() {
172      Polyline()
173        .width(300)
174        .height(200)
175        .backgroundColor("#0C000000")
176        .fill('#317AF7')
177        .animatablePoints(this.polyline1Vec)
178        .animation({ duration: 2000, delay: 0, curve: Curve.Ease })
179        .onClick(() => {
180
181          if (this.polyline1Vec.equals(this.pointVec1)) {
182            this.polyline1Vec = this.pointVec2;
183          } else {
184            this.polyline1Vec = this.pointVec1;
185          }
186        })
187    }
188    .width('100%').height('100%').justifyContent(FlexAlign.Center)
189  }
190}
191```
192
193
194![zh-cn_image_0000001592669598](figures/zh-cn_image_0000001592669598.gif)
195
196## 相关实例
197
198针对自定义属性动画开发,有以下相关实例可供参考:
199
200- [自定义下拉刷新动画(ArkTS)(API9)](https://gitee.com/openharmony/codelabs/tree/master/ETSUI/AnimateRefresh)