• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# 布局更新动画
2
3
4[显式动画](../reference/arkui-ts/ts-explicit-animation.md)(animateTo)和[属性动画](../reference/arkui-ts/ts-animatorproperty.md)(animation)是ArkUI提供的最基础和常用的动画功能。在布局属性(如[尺寸属性](../reference/arkui-ts/ts-universal-attributes-size.md)、[位置属性](../reference/arkui-ts/ts-universal-attributes-location.md))发生变化时,可以通过属性动画或显式动画,按照动画参数过渡到新的布局参数状态。
5
6
7| 动画类型 | 特点                                       |
8| ---- | ---------------------------------------- |
9| 显式动画 | 闭包内的变化均会触发动画,包括由数据变化引起的组件的增删、组件属性的变化等,可以做较为复杂的动画。 | 较复杂的动画场景 |
10| 属性动画 | 动画设置简单,属性变化时自动触发动画。                      |
11
12
13## 使用显式动画产生布局更新动画
14
15显式动画的接口为:
16
17
18```ts
19animateTo(value: AnimateParam, event: () => void): void
20```
21
22第一个参数指定动画参数,第二个参数为动画的闭包函数。
23
24以下是使用显式动画产生布局更新动画的示例。示例中,当Column组件的alignItems属性改变后,其子组件的布局位置结果发生变化。只要该属性是在animateTo的闭包函数中修改的,那么由其引起的所有变化都会按照animateTo的动画参数执行动画过渡到终点值。
25
26
27```ts
28@Entry
29@Component
30struct LayoutChange {
31  // 用于控制Column的alignItems属性
32  @State itemAlign: HorizontalAlign = HorizontalAlign.Start;
33  allAlign: HorizontalAlign[] = [HorizontalAlign.Start, HorizontalAlign.Center, HorizontalAlign.End];
34  alignIndex: number = 0;
35
36  build() {
37    Column() {
38      Column({ space: 10 }) {
39        Button("1").width(100).height(50)
40        Button("2").width(100).height(50)
41        Button("3").width(100).height(50)
42      }
43      .margin(20)
44      .alignItems(this.itemAlign)
45      .borderWidth(2)
46      .width("90%")
47      .height(200)
48
49      Button("click").onClick(() => {
50        // 动画时长为1000ms,曲线为EaseInOut
51        animateTo({ duration: 1000, curve: Curve.EaseInOut }, () => {
52          this.alignIndex = (this.alignIndex + 1) % this.allAlign.length;
53          // 在闭包函数中修改this.itemAlign参数,使Column容器内部孩子的布局方式变化,使用动画过渡到新位置
54          this.itemAlign = this.allAlign[this.alignIndex];
55        });
56      })
57    }
58    .width("100%")
59    .height("100%")
60  }
61}
62```
63
64
65![layoutChange1](figures/layoutChange1.gif)
66
67
68除直接改变布局方式外,也可直接修改组件的宽、高、位置。
69
70
71
72```ts
73@Entry
74@Component
75struct LayoutChange2 {
76  @State myWidth: number = 100;
77  @State myHeight: number = 50;
78  // 标志位,true和false分别对应一组myWidth、myHeight值
79  @State flag: boolean = false;
80
81  build() {
82    Column({ space: 10 }) {
83      Button("text")
84        .type(ButtonType.Normal)
85        .width(this.myWidth)
86        .height(this.myHeight)
87        .margin(20)
88      Button("area: click me")
89        .fontSize(12)
90        .margin(20)
91        .onClick(() => {
92          animateTo({ duration: 1000, curve: Curve.Ease }, () => {
93            // 动画闭包中根据标志位改变控制第一个Button宽高的状态变量,使第一个Button做宽高动画
94            if (this.flag) {
95              this.myWidth = 100;
96              this.myHeight = 50;
97            } else {
98              this.myWidth = 200;
99              this.myHeight = 100;
100            }
101            this.flag = !this.flag;
102          });
103        })
104    }
105    .width("100%")
106    .height("100%")
107  }
108}
109```
110
111
112在第二个Button的点击事件中,使用animateTo函数,在闭包中修改this.myWidththis.myHeight状态变量,而这两个状态变量分别为第一个Button的宽、高属性值,所以第一个Button做了宽高动画。效果如下图。
113
114
115![layoutChange2_animateTo](figures/layoutChange2_animateTo.gif)
116
117
118与此同时,第二个Button也产生了一个位置动画。这是由于第一个Button的宽高变化后,引起了Column内部其他组件的布局结果也发生了变化,第二个Button的布局发生变化也是由于闭包内改变第一个Button的宽高造成的。
119
120
121如果不希望第二个Button有动画效果,有两种方式可以实现。一种是给做第一个Button外面再加一个容器,使其动画前后的大小都在容器的范围内,这样第二个Button的位置不会被第一个Button的位置所影响。修改后的核心代码如下。
122
123
124
125```ts
126Column({ space: 10 }) {
127  Column() {
128    // Button放在足够大的容器内,使其不影响更外层的组件位置
129    Button("text")
130      .type(ButtonType.Normal)
131      .width(this.myWidth)
132      .height(this.myHeight)
133  }
134  .margin(20)
135  .width(200)
136  .height(100)
137
138  Button("area: click me")
139    .fontSize(12)
140    .onClick(() => {
141      animateTo({ duration: 1000, curve: Curve.Ease }, () => {
142        // 动画闭包中根据标志位改变控制第一个Button宽高的状态变量,使第一个Button做宽高动画
143        if (this.flag) {
144          this.myWidth = 100;
145          this.myHeight = 50;
146        } else {
147          this.myWidth = 200;
148          this.myHeight = 100;
149        }
150        this.flag = !this.flag;
151      });
152    })
153}
154.width("100%")
155.height("100%")
156```
157
158
159![layoutChange2_animateTo_change](figures/layoutChange2_animateTo_change.gif)
160
161
162另一种方式是给第二个Button添加布局约束,如position的位置约束,使其位置不被第一个Button的宽高影响。核心代码如下:
163
164
165
166```ts
167Column({ space: 10 }) {
168  Button("text")
169    .type(ButtonType.Normal)
170    .width(this.myWidth)
171    .height(this.myHeight)
172    .margin(20)
173
174  Button("area: click me")
175    .fontSize(12)
176    // 配置position属性固定,使自己的布局位置不被第一个Button的宽高影响
177    .position({ x: "30%", y: 200 })
178    .onClick(() => {
179      animateTo({ duration: 1000, curve: Curve.Ease }, () => {
180        // 动画闭包中根据标志位改变控制第一个Button宽高的状态变量,使第一个Button做宽高动画
181        if (this.flag) {
182          this.myWidth = 100;
183          this.myHeight = 50;
184        } else {
185          this.myWidth = 200;
186          this.myHeight = 100;
187        }
188        this.flag = !this.flag;
189      });
190    })
191}
192.width("100%")
193.height("100%")
194```
195
196
197## 使用属性动画产生布局更新动画
198
199显式动画把要执行动画的属性的修改放在闭包函数中触发动画,而属性动画则无需使用闭包,把animation属性加在要做属性动画的组件的属性后即可。
200
201属性动画的接口为:
202
203
204```ts
205animation(value: AnimateParam)
206```
207
208其入参为动画参数。想要组件随某个属性值的变化而产生动画,此属性需要加在animation属性之前。有的属性变化不希望通过animation产生属性动画,可以放在animation之后。上面显式动画的示例很容易改为用属性动画实现。例如:
209
210
211
212```ts
213@Entry
214@Component
215struct LayoutChange2 {
216  @State myWidth: number = 100;
217  @State myHeight: number = 50;
218  @State flag: boolean = false;
219  @State myColor: Color = Color.Blue;
220
221  build() {
222    Column({ space: 10 }) {
223      Button("text")
224        .type(ButtonType.Normal)
225        .width(this.myWidth)
226        .height(this.myHeight)
227        // animation只对其上面的type、width、height属性生效,时长为1000ms,曲线为Ease
228        .animation({ duration: 1000, curve: Curve.Ease })
229        // animation对下面的backgroundColor、margin属性不生效
230        .backgroundColor(this.myColor)
231        .margin(20)
232
233      Button("area: click me")
234        .fontSize(12)
235        .onClick(() => {
236          // 改变属性值,配置了属性动画的属性会进行动画过渡
237          if (this.flag) {
238            this.myWidth = 100;
239            this.myHeight = 50;
240            this.myColor = Color.Blue;
241          } else {
242            this.myWidth = 200;
243            this.myHeight = 100;
244            this.myColor = Color.Pink;
245          }
246          this.flag = !this.flag;
247        })
248    }
249  }
250}
251```
252
253
254上述示例中,第一个button上的animation属性,只对写在animation之前的type、width、height属性生效,而对写在animation之后的backgroundColor、margin属性无效。运行结果是width、height属性会按照animation的动画参数执行动画,而backgroundColor会直接跳变,不会产生动画。效果如下图:
255
256
257
258
259
260
261![size-change-animation](figures/size-change-animation.gif)
262
263
264>**说明:**
265>
266>    1. 使用属性动画时,会按照指定的属性动画参数执行动画。每个组件可为自己的属性配置不同参数的属性动画。
267>
268>    2. 显式动画会对动画闭包前后造成的所有界面差异执行动画,且使用同一动画参数,适用于统一执行的场景。此外,显式动画也可以用于一些非属性变量造成的动画,如if/else的条件,ForEach使用的数组元素的删减。
269>
270>    3. 如果一个属性配置了属性动画,且在显式动画闭包中改变该属性值,属性动画优先生效,会使用属性动画的动画参数。
271