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.myWidth和this.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