1# Application Animation Practice 2 3## Introduction 4Animations add a lively, delightful touch to your application. Yet, they must be handled carefully to avoid performance issues. This topic describes how to make smart use of animations in applications, and examines how less frequent layout and property changes and avoidance of redundant re-renders can reduce performance overhead. 5Specifically, the following modes are recommended in implementing animations: 6 7- Using **transition** for component transitions 8- Using transform properties for component layout changes 9- Using the same **animateTo** for properties with same animation settings. 10- Updating state variables for multiple **animateTo** calls at once 11 12## Smart Use of Animations 13 14### Using transition for Component Transitions 15To apply an animation to a component when it appears or disappears, two common methods are available: 16 17- Use the **animateTo** API and add logic processing to the animation end callback. 18- Use the **transition** API. 19 20**transition** is preferred over **animateTo**, primarily for two reasons: 21 22- **transition** leads to higher performance, in that it involves only one property change with a conditional statement, while **animateTo** involves two property changes, one before the animation and one after. 23- **transition** is easier to implement, without the need for complex logic processing in the animation end callback. 24 25Avoid: Change a component's opacity property from 1 to 0 to hide the component, and control the disappearance of the component in the animation end callback. 26 27```typescript 28@Entry 29@Component 30struct MyComponent { 31 @State mOpacity: number = 1; 32 @State show: boolean = true; 33 count: number = 0; 34 35 build() { 36 Column() { 37 Row() { 38 if (this.show) { 39 Text('value') 40 .opacity(this.mOpacity) 41 } 42 } 43 .width('100%') 44 .height(100) 45 .justifyContent(FlexAlign.Center) 46 Text('toggle state') 47 .onClick(() => { 48 this.count++; 49 const thisCount: number = this.count; 50 this.show = true; 51 // Hide or hide the <Text> component by changing its opacity property. 52 animateTo({ duration: 1000, onFinish: () => { 53 // In the last animation, hide the <Text> component, and then change the conditional statement to make the component disappear. 54 if (thisCount === this.count && this.mOpacity === 0) { 55 this.show = false; 56 } 57 } }, () => { 58 this.mOpacity = this.mOpacity === 1 ? 0 : 1; 59 }) 60 }) 61 } 62 } 63} 64``` 65 66Preferable: Directly use the **transition** API to animate the opacity when the **Text** component appears or disappears. 67 68```typescript 69@Entry 70@Component 71struct MyComponent { 72 @State show: boolean = true; 73 74 build() { 75 Column() { 76 Row() { 77 if (this.show) { 78 Text('value') 79 // Set the ID so that the transition can be interrupted. 80 .id('myText') 81 .transition(TransitionEffect.OPACITY.animation({ duration: 1000 })) 82 } 83 }.width('100%') 84 .height(100) 85 .justifyContent(FlexAlign.Center) 86 Text('toggle state') 87 .onClick(() => { 88 // Use transition to animate the opacity when the component appears or disappears. 89 this.show = !this.show; 90 }) 91 } 92 } 93} 94``` 95 96### Using Transform Properties for Component Layout Changes 97You can change the layout of a component in either of the following methods: 98 99- Modify [layout properties](../ui/arkts-attribute-animation-overview.md), which will cause a UI re-layout. Common layout properties include **width**, **height**, and **layoutWeight**. 100- Modify [transform properties](../reference/apis-arkui/arkui-ts/ts-universal-attributes-transformation.md), which will cause the component to translate, rotate, or scale. 101 102As modifying transform properties does not involve the time-consuming UI re-layout, it is more time efficient than modifying layout properties, and is therefore recommended. The following examples use the preceding methods to scale up a component by 10 times. 103 104Avoid: Scale up a component by modifying its **width** and **height** properties. 105 106```typescript 107@Entry 108@Component 109struct MyComponent { 110 @State textWidth: number = 10; 111 @State textHeight: number = 10; 112 113 build() { 114 Column() { 115 Text() 116 .backgroundColor(Color.Blue) 117 .fontColor(Color.White) 118 .fontSize(20) 119 .width(this.textWidth) 120 .height(this.textHeight) 121 122 Button ('Layout Properties') 123 .backgroundColor(Color.Blue) 124 .fontColor(Color.White) 125 .fontSize(20) 126 .margin({ top: 30 }) 127 .borderRadius(30) 128 .padding(10) 129 .onClick(() => { 130 animateTo({ duration: 1000 }, () => { 131 this.textWidth = 100; 132 this.textHeight = 100; 133 }) 134 }) 135 } 136} 137} 138``` 139 140Because animating the location and size properties of a component involves new UI measurement and layout, performance overhead is high. If a component's location or size changes continuously, as in a pinch gesture, using the **scale** property is a better choice for the animation, as in the example below. 141 142Preferable: Scale up a component by modifying the **scale** property. 143 144```typescript 145@Entry 146@Component 147struct MyComponent { 148 @State textScaleX: number = 1; 149 @State textScaleY: number = 1; 150 151 build() { 152 Column() { 153 Text() 154 .backgroundColor(Color.Blue) 155 .fontColor(Color.White) 156 .fontSize(20) 157 .width(10) 158 .height(10) 159 .scale({ x: this.textScaleX, y: this.textScaleY }) 160 .margin({ top: 100 }) 161 162 Button ('Transform Properties') 163 .backgroundColor(Color.Blue) 164 .fontColor(Color.White) 165 .fontSize(20) 166 .margin({ top: 60 }) 167 .borderRadius(30) 168 .padding(10) 169 .onClick(() => { 170 animateTo({ duration: 1000 }, () => { 171 this.textScaleX = 10; 172 this.textScaleY = 10; 173 }) 174 }) 175 } 176} 177} 178``` 179 180### Using Same animateTo for Properties with Same Animation Settings 181Each time **animateTo** is called, a before-after comparison is required. Less **animateTo** calls can contribute to fewer component re-renders and thereby higher performance. 182In light of this, if properties share the same animation settings, consider placing them in the same animation closure. 183 184Avoid: Place state variables with the same animation settings in different animation closures. 185 186```typescript 187@Entry 188@Component 189struct MyComponent { 190 @State textWidth: number = 200; 191 @State color: Color = Color.Red; 192 193 func1() { 194 animateTo({ curve: Curve.Sharp, duration: 1000 }, () => { 195 this.textWidth = (this.textWidth === 100 ? 200 : 100); 196 }); 197 } 198 199 func2() { 200 animateTo({ curve: Curve.Sharp, duration: 1000 }, () => { 201 this.color = (this.color === Color.Yellow ? Color.Red : Color.Yellow); 202 }); 203 } 204 205 build() { 206 Column() { 207 Row() 208 .width(this.textWidth) 209 .height(10) 210 .backgroundColor(this.color) 211 Text('click') 212 .onClick(() => { 213 this.func1(); 214 this.func2(); 215 }) 216 } 217 .width('100%') 218 .height('100%') 219 } 220} 221``` 222 223Preferable: Combine animations with the same animation settings into one animation closure. 224 225```typescript 226@Entry 227@Component 228struct MyComponent { 229 @State textWidth: number = 200; 230 @State color: Color = Color.Red; 231 232 func() { 233 animateTo({ curve: Curve.Sharp, duration: 1000 }, () => { 234 this.textWidth = (this.textWidth === 100 ? 200 : 100); 235 this.color = (this.color === Color.Yellow ? Color.Red : Color.Yellow); 236 }); 237 } 238 239 build() { 240 Column() { 241 Row() 242 .width(this.textWidth) 243 .height(10) 244 .backgroundColor(this.color) 245 Text('click') 246 .onClick(() => { 247 this.func(); 248 }) 249 } 250 .width('100%') 251 .height('100%') 252 } 253} 254``` 255 256### Updating State Variables for Multiple animateTo Calls At Once 257**animateTo** compares the states before and after the animation closure is executed and animates the differences. For comparison, all changed state variables and dirty nodes are re-rendered before the animation closure of **animateTo** is executed. 258If state variables are re-rendered between **animateTo** calls, there may exist dirty nodes that need to be re-rendered before the next **animateTo** call, which may cause redundant re-renders. 259 260Avoid: Re-render state variables between **animateTo** calls. 261 262 263 264The following code updates other states of a component between two **animateTo** calls. 265 266```typescript 267@Entry 268@Component 269struct MyComponent { 270 @State textWidth: number = 200; 271 @State textHeight: number = 50; 272 @State color: Color = Color.Red; 273 274 build() { 275 Column() { 276 Row() 277 .width(this.textWidth) 278 .height(10) 279 .backgroundColor(this.color) 280 Text('click') 281 .height(this.textHeight) 282 .onClick(() => { 283 this.textWidth = 100; 284 // textHeight is a non-animatable property. 285 this.textHeight = 100; 286 animateTo({ curve: Curve.Sharp, duration: 1000 }, () => { 287 this.textWidth = 200; 288 }); 289 this.color = Color.Yellow; 290 animateTo({ curve: Curve.Linear, duration: 2000 }, () => { 291 this.color = Color.Red; 292 }); 293 }) 294 } 295 .width('100%') 296 .height('100%') 297 } 298} 299``` 300 301Before the first **animateTo** call, the **textWidth** property is modified. Therefore, the **Row** component needs to be re-rendered. In the animation closure of the first **animateTo**, the **textWidth** property is modified. Therefore, the **Row** component needs to be re-rendered again and compared with the last rendering result to generate a width and height animation. Before the second **animateTo** call, the **color** property is modified. Therefore, the **Row** component needs to be re-rendered again. In the animation closure of the second **animateTo** call, the **color** property is modified. Therefore, the **Row** component needs to be re-rendered again to generate a background color animation. In sum, the **Row** component is re-rendered four times for its property changes. 302In this example, the state variable **textHeight** irrelevant to the animation is also modified. If the state change is not needed, it should be avoided to reduce redundant re-renders. 303 304Preferable: Update state variables in a unified manner. 305 306 or  307 308Preferable 1: Use the original state before **animateTo** to drive the animation from the original state to the target state. In this way, abrupt, unnatural changes can be avoided at the beginning of the animation. 309 310```typescript 311@Entry 312@Component 313struct MyComponent { 314 @State textWidth: number = 100; 315 @State textHeight: number = 50; 316 @State color: Color = Color.Yellow; 317 318 build() { 319 Column() { 320 Row() 321 .width(this.textWidth) 322 .height(10) 323 .backgroundColor(this.color) 324 Text('click') 325 .height(this.textHeight) 326 .onClick(() => { 327 animateTo({ curve: Curve.Sharp, duration: 1000 }, () => { 328 this.textWidth = (this.textWidth === 100 ? 200 : 100); 329 }); 330 animateTo({ curve: Curve.Linear, duration: 2000 }, () => { 331 this.color = (this.color === Color.Yellow ? Color.Red : Color.Yellow); 332 }); 333 }) 334 } 335 .width('100%') 336 .height('100%') 337 } 338} 339``` 340 341Before the first **animateTo** call, no dirty state variable or dirty node needs to be updated, and no re-render is required. In the animation closure of the first **animateTo**, the **textWidth** property is modified. Therefore, the **Row** component needs to be re-rendered and compared with the last rendering result to generate a width and height animation. Before the second **animateTo** call, because no additional statement is executed, there is no dirty state variable or dirty node that needs to be updated, and no re-render is required. In the animation closure of the second **animateTo** call, the **color** property is modified. Therefore, the **Row** component needs to be re-rendered again to generate a background color animation. In sum, the **Row** component is re-rendered twice for its property changes. 342 343Preferable 2: Explicitly specify the initial values of all properties that require animation before **animateTo**, update the values to the node, and then animate the properties. 344 345```typescript 346@Entry 347@Component 348struct MyComponent { 349 @State textWidth: number = 200; 350 @State textHeight: number = 50; 351 @State color: Color = Color.Red; 352 353 build() { 354 Column() { 355 Row() 356 .width(this.textWidth) 357 .height(10) 358 .backgroundColor(this.color) 359 Text('click') 360 .height(this.textHeight) 361 .onClick(() => { 362 this.textWidth = 100; 363 this.color = Color.Yellow; 364 animateTo({ curve: Curve.Sharp, duration: 1000 }, () => { 365 this.textWidth = 200; 366 }); 367 animateTo({ curve: Curve.Linear, duration: 2000 }, () => { 368 this.color = Color.Red; 369 }); 370 this.textHeight = 100; 371 }) 372 } 373 .width('100%') 374 .height('100%') 375 } 376} 377``` 378 379Before the first **animateTo** call, the **textWidth** and **color** properties are modified. Therefore, the **Row** component needs to be re-rendered. In the animation closure of the first **animateTo**, the **textWidth** property is modified. Therefore, the **Row** component needs to be re-rendered again and compared with the last rendering result to generate a width and height animation. Before the second **animateTo** call, because no additional statement is executed, there is no dirty state variable or dirty node that needs to be updated, and no re-render is required. In the animation closure of the second **animateTo** call, the **color** property is modified. Therefore, the **Row** component needs to be re-rendered again to generate a background color animation. In sum, the **Row** component is re-rendered three times for its property changes. 380