Implementing Property Animation

Continuous visual effects on the UI resulting from changes to animatable properties are called property animations. As the most fundamental and intuitive type of animation, property animations form the core of UI animation systems. ArkUI provides three animation APIs to create these effects: animateTo, animation, and keyframeAnimateTo.

NOTE

The property animation discussed in this topic refers to the method of generating animation by specifying the target value of animatable properties, not just the specific property animation API.

Animation API Scope Principle Use Scenario
animateTo UI changes caused by property changes in closures. This API is a common function. It animates the differences between the UIs before and after state variables in the closure change.
This API supports multiple calls and nesting.
A single set of animation parameters is used to animate multiple properties.
Animations need to be nested.
Note: To achieve multiple animation cycles, it is recommended that you set the playMode and iterations properties of AnimateParam or use keyframeAnimateTo.
animation UI changes caused by property changes bound to components through property APIs. This API automatically detects changes to animatable properties and applies animations.
As the API call sequence of the component is from bottom to top, this API applies only to the properties declared above it in the component chain.
In a component, you can set animation for individual properties based on the API call sequence.
Different animation parameters are used for different properties.
keyframeAnimateTo Segmented property animation caused by property changes in multiple closures. This API is a common function. It animates the difference between state variables in each closure and the previous state.
This API supports multiple calls, but nesting is not recommended.
Multiple animations are applied to the same property sequentially.

animateTo

animateTo(value: AnimateParam, event: () => void): void

In the animateTo API, value specifies the AnimateParam (including duration and curve). event is the closure function of the animation. The attribute animation generated due to variable changes in the closure follows the same animation parameters.

NOTE

Directly using animateTo can lead to the issue of ambiguous UI context. To avoid this, obtain the UIContext object using the getUIContext() API and then call the animateTo API through this object.

import { curves } from '@kit.ArkUI';
@Entry
@Component
struct attrAnimateToDemo2 {
  @State animate: boolean = false;
  // Step 1: Declare related state variables.
  @State rotateValue: number = 0; // Rotation angle of component 1.
  @State translateX: number = 0; // Offset of component 2
  @State opacityValue: number = 1; // Opacity of component 2.

  // Step 2: Set the declared state variables to the related animatable property APIs.
  build() {
    Row() {
      // Component 1
      Column() {
      }
      .rotate({ angle: this.rotateValue })
      .backgroundColor('#317AF7')
      .justifyContent(FlexAlign.Center)
      .width(100)
      .height(100)
      .borderRadius(30)
      .onClick(() => {
        this.getUIContext()?.animateTo({ curve: curves.springMotion() }, () => {
          this.animate = !this.animate;
          // Step 3: Change the state variables in the closure to update the UI.
          // You can write any logic that can change the UI, such as array adding and visibility control. The system detects the differences between the new UI and the previous UI and adds animations for the differences.
          // The rotate property of component 1 is changed. Therefore, a rotate animation is added to component 1.
          this.rotateValue = this.animate ? 90 : 0;
          // The opacity property of component 2 is changed. Therefore, an opacity animation is added to component 2.
          this.opacityValue = this.animate ? 0.6 : 1;
          // The translate property of component 2 is changed. Therefore, a translate animation is added to component 2.
          this.translateX = this.animate ? 50 : 0;
        })
      })

      // Component 2
      Column() {
      }
      .justifyContent(FlexAlign.Center)
      .width(100)
      .height(100)
      .backgroundColor('#D94838')
      .borderRadius(30)
      .opacity(this.opacityValue)
      .translate({ x: this.translateX })
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
  }
}

en-us_image_0000001599958466

animation

The animateTo API needs to encapsulate attribute changes into a closure for execution, while the animation API only needs to append attribute changes to the animatable attribute without requiring a closure. and it will automatically apply animations when bound properties change.

import { curves } from '@kit.ArkUI';
@Entry
@Component
struct attrAnimationDemo3 {
  @State animate: boolean = false;
  // Step 1: Declare related state variables.
  @State rotateValue: number = 0; // Rotation angle of component 1.
  @State translateX: number = 0; // Offset of component 2
  @State opacityValue: number = 1; // Opacity of component 2.

  // Step 2: Set the declared state variables to the related animatable property APIs.
  build() {
    Row() {
      // Component 1
      Column() {
      }
      .opacity(this.opacityValue)
      .rotate({ angle: this.rotateValue })
      // Step 3: Enable property animation.
      .animation({ curve: curves.springMotion() })
      .backgroundColor('#317AF7')
      .justifyContent(FlexAlign.Center)
      .width(100)
      .height(100)
      .borderRadius(30)
      .onClick(() => {
        this.animate = !this.animate;
        // Step 4: Change the state variables in the closure to update the UI.
        // You can write any logic that can change the UI, such as array adding and visibility control. The system detects the differences between the new UI and the previous UI and adds animations for the differences.
        // The rotate property of component 1 is changed. Therefore, a rotate animation is added to component 1.
        this.rotateValue = this.animate ? 90 : 0;
        // The translate property of component 2 is changed. Therefore, a translate animation is added to component 2.
        this.translateX = this.animate ? 50 : 0;
        // The opacity property of the parent component <Column> is changed, which results in an opacity change of its child components. Therefore, opacity animations are added to <Column> and its child components.
        this.opacityValue = this.animate ? 0.6 : 1;
      })

      // Component 2
      Column() {
      }
      .justifyContent(FlexAlign.Center)
      .width(100)
      .height(100)
      .backgroundColor('#D94838')
      .borderRadius(30)
      .opacity(this.opacityValue)
      .translate({ x: this.translateX })
      .animation({ curve: curves.springMotion() })
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
  }
}

en-us_image_0000001649279705

keyframeAnimateTo

keyframeAnimateTo(param: KeyframeAnimateParam, keyframes: Array<KeyframeState>): void

In the keyframeAnimateTo API, the first parameter KeyframeAnimateParam is the overall parameter of the keyframe animation (including the delay, iterations, onFinish, and expectedFrameRateRange). The second parameter is an array. Each item indicates the animation behavior in a keyframe. The animation parameters (including duration and curve) of each animation can be controlled separately.

If there are multiple animations for the same attribute, you can create a new animation in the end callback. However, compilation is complex, and it takes time to create a new animation each time, which may cause frame freezing. Keyframe animations are more suitable for this scenario.

This example demonstrates how to set the keyframe animation using keyframeAnimateTo.

@Entry
@Component
struct KeyframeAnimateToDemo {
  // Step 1: Declare related state variables.
  @State rotateValue: number = 0; // Rotation angle of component 1.
  @State translateX: number = 0; // Offset of component 2
  @State opacityValue: number = 1; // Opacity of component 2.
  // Step 2: Set the declared state variables to the related animatable property APIs.
  build() {
    Row() {
      // Component 1
      Column() {
      }
      .rotate({ angle: this.rotateValue })
      .backgroundColor('#317AF7')
      .justifyContent(FlexAlign.Center)
      .width(100)
      .height(100)
      .borderRadius(30)
      .onClick(() => {
        // Step 3: Call the keyframeAnimateTo API.
        this.getUIContext()?.keyframeAnimateTo({
          iterations: 1
        }, [
          {
            // The first keyframe animation has a duration of 800 ms: Component 1 rotates 90 degrees clockwise. Component 2's opacity changes from 1 to 0.6, and its translation (translate) changes from 0 to 50.
            duration: 800,
            event: () => {
              this.rotateValue = 90;
              this.opacityValue = 0.6;
              this.translateX = 50;
            }
          },
          {
            // The second keyframe animation has a duration of 500 ms. Component 1 rotates 90 degrees counterclockwise to return to 0 degrees. Component 2's opacity changes from 0.6 back to 1, and its translation (translate) changes from 50 back to 0.
            duration: 500,
            event: () => {
              this.rotateValue = 0;
              this.opacityValue = 1;
              this.translateX = 0;
            }
          }
        ]);
      })
      // Component 2
      Column() {
      }
      .justifyContent(FlexAlign.Center)
      .width(100)
      .height(100)
      .backgroundColor('#D94838')
      .borderRadius(30)
      .opacity(this.opacityValue)
      .translate({ x: this.translateX })
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
  }
}

keyframeAnimateTo1

NOTE

  • When an animation is applied to the position or size change of a component, as layout measurement is involved, performance overheads are high. To reduce performance overheads, use the scale attribute instead, whose value change does not involve layout re-measurement. This practice is applicable where the component position and size change continuously, for example, where the component size changes as a response to gestures.

  • Apply property animations only to consistently visible components. For those components whose visibility may change, use the transition animation.

  • Avoid using end callbacks with property animations. Property animations are applied to states that have occurred. As such, you do not need to process the end logic. If end callbacks are needed, be sure to correctly handle the data management for continuous operations.

  • If transition animations are disabled in Developer options, or if the UIAbility switches from the foreground to the background, the end callback will be triggered immediately. It is recommended that you verify these scenarios and avoid placing timing-sensitive logic in this callback.