1# 帧动画(ohos.animator2
3帧动画具备逐帧回调的特性,便于开发者在每一帧中处理需调整的属性。通过向应用提供onFrame逐帧回调,帧动画使开发者能够在应用的每一帧设置属性值,从而实现组件属性值变化的自然过渡,营造出动画效果。帧动画接口详情可参考[@ohos.animator (动画)](../reference/apis-arkui/js-apis-animator.md)。
4
5与属性动画相比,帧动画能让开发者实时感知动画进程,即时调整UI值,具备事件即时响应和可暂停的优势,但在性能上略逊于属性动画。当属性动画能满足需求时,建议优先采用属性动画接口实现。属性动画接口可参考[实现属性动画](./arkts-attribute-animation-apis.md)。
6
7| 名称 | 实现方式 | 事件响应方式 | 可暂停 | 性能 |
8| -------- | -------- | -------- | -------- |-------- |
9| 帧动画(ohos.animator) | 开发者可每帧修改UI侧属性值,UI侧属性实时更新 | 实时响应 | 是 | 较差 |
10| 属性动画 | UI侧只计算动画最终状态,动画过程为渲染值在改变,UI侧一直为动画最终状态,不感知实时渲染值 | 按最终状态响应 | 否 | 较好 |
11
12如图所示,帧动画在动画过程中即可实时响应,而属性动画按最终状态响应。
13
14![Alt text](figures/ohos.animator.gif)
15
16![Alt text](figures/animation.gif)
17
18## 使用帧动画实现动画效果
19
20使用如下步骤可以创建一个简单的animator,并且在每个帧回调中打印当前插值。
21
221. 引入相关依赖。
23
24   ```ts
25   import { AnimatorOptions, AnimatorResult } from '@kit.ArkUI';
26   ```
27
282. 创建执行动画的对象。
29
30   ```ts
31   // 创建动画的初始参数
32   let options: AnimatorOptions = {
33     duration: 1500,
34     easing: "friction",
35     delay: 0,
36     fill: "forwards",
37     direction: "normal",
38     iterations: 2,
39     // 动画onFrame 插值首帧值
40     begin: 200.0,
41     // 动画onFrame 插值尾帧值
42     end: 400.0
43   };
44   let result: AnimatorResult = this.getUIContext().createAnimator(options);
45   // 设置接收到帧时回调,动画播放过程中每帧会调用onFrame回调
46   result.onFrame = (value: number) => {
47     console.log("current value is :" + value);
48   }
49   ```
50
513. 播放动画。
52
53   ```ts
54   // 播放动画
55   result.play();
56   ```
57
584. 动画执行完成后手动释放AnimatorResult对象。
59
60   ```ts
61   // 释放动画对象
62   result = undefined;
63   ```
64
65## 使用帧动画实现小球抛物运动
66
671. 引入相关依赖。
68
69   ```ts
70   import { AnimatorOptions, AnimatorResult } from '@kit.ArkUI';
71   ```
72
732. 定义要做动画的组件。
74
75   ```ts
76   Button()
77     .width(60)
78     .height(60)
79     .translate({ x: this.translateX, y: this.translateY })
80   ```
81
823. 在onPageShow中创建AnimatorResult对象。
83
84   ```ts
85   onPageShow(): void {
86       //创建animatorResult对象
87       this.animatorOptions = this.getUIContext().createAnimator(options);
88       this.animatorOptions.onFrame = (progress: number) => {
89       this.translateX = progress;
90       if (progress > this.topWidth && this.translateY < this.bottomHeight) {
91          this.translateY = Math.pow(progress - this.topWidth, 2) * this.g;
92       }
93    }
94    //动画取消时执行方法
95    this.animatorOptions.onCancel = () => {
96       this.animatorStatus = '取消';
97    }
98    //动画完成时执行方法
99    this.animatorOptions.onFinish = () => {
100       this.animatorStatus = '完成';
101    }
102    //动画重复播放时执行方法
103    this.animatorOptions.onRepeat = () => {
104       console.log("动画重复播放");
105    }
106   }
107   ```
108
1094. 定义动画播放,重置,暂停的按钮。
110
111   ```ts
112   Button('播放').onClick(() => {
113     this.animatorOptions?.play();
114     this.animatorStatus = '播放中'
115   }).width(80).height(35)
116   Button("重置").onClick(() => {
117     this.translateX = 0;
118     this.translateY = 0;
119   }).width(80).height(35)
120   Button("暂停").onClick(() => {
121     this.animatorOptions?.pause();
122     this.animatorStatus = '暂停'
123   }).width(80).height(35)
124   ```
1255. 在页面隐藏或销毁的生命周期中释放动画对象,避免内存泄漏。
126   ```ts
127   onPageHide(): void {
128     this.animatorOptions = undefined;
129   }
130   ```
131
132完整示例如下。
133
134```ts
135import { AnimatorOptions, AnimatorResult } from '@kit.ArkUI';
136
137@Entry
138@Component
139struct Index {
140  @State animatorOptions: AnimatorResult | undefined = undefined;
141  @State animatorStatus: string = '创建';
142  begin: number = 0;
143  end: number = 300
144  topWidth: number = 150;
145  bottomHeight: number = 100;
146  g: number = 0.18
147  animatorOption: AnimatorOptions = {
148    duration: 4000,
149    delay: 0,
150    easing: 'linear',
151    iterations: 1,
152    fill: "forwards",
153    direction: 'normal',
154    begin: this.begin,
155    end: this.end
156  };
157  @State translateX: number = 0;
158  @State translateY: number = 0;
159
160  onPageShow(): void {
161    this.animatorOptions = this.getUIContext().createAnimator(this.animatorOption)
162    this.animatorOptions.onFrame = (progress: number) => {
163      this.translateX = progress;
164      if (progress > this.topWidth && this.translateY < this.bottomHeight) {
165        this.translateY = Math.pow(progress - this.topWidth, 2) * this.g;
166      }
167    }
168    this.animatorOptions.onCancel = () => {
169      this.animatorStatus = '取消';
170    }
171    this.animatorOptions.onFinish = () => {
172      this.animatorStatus = '完成';
173    }
174    this.animatorOptions.onRepeat = () => {
175      console.log("动画重复播放");
176    }
177  }
178
179  onPageHide(): void {
180    this.animatorOptions = undefined;
181  }
182
183  build() {
184    Column() {
185      Column({ space: 30 }) {
186        Button('播放').onClick(() => {
187          this.animatorOptions?.play();
188          this.animatorStatus = '播放中';
189        }).width(80).height(35)
190        Button("重置").onClick(() => {
191          this.translateX = 0;
192          this.translateY = 0;
193        }).width(80).height(35)
194        Button("暂停").onClick(() => {
195          this.animatorOptions?.pause();
196          this.animatorStatus = '暂停';
197        }).width(80).height(35)
198      }.width("100%").height('25%')
199
200      Stack() {
201        Button()
202          .width(60)
203          .height(60)
204          .translate({ x: this.translateX, y: this.translateY })
205      }
206      .width("100%")
207      .height('45%')
208      .align(Alignment.Start)
209
210      Text("当前动画状态为:" + this.animatorStatus)
211    }.width("100%").height('100%')
212  }
213}
214```
215
216![zh-cn_image_0000001599958466](figures/animatorSimple.gif)