• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Animation Smoothing
2
3
4When running animations, the UI is also interacting with users in real time. It must respond immediately to changes in user behavior. For example, if the user swipes up to exit in the midst of an application launch process, the UI should immediately transit from the startup animation to the exit animation, rather than finishing the startup animation before exiting. In the scenario where the animation triggered when the user lifts their fingers off the screen, the initial velocity of the animation must inherit the gesture speed, so as to avoid pauses caused by speed disconnection. For the preceding and similar scenarios, OpenHarmony provides efficient APIs for smoothing between animations and between animations and gestures.
5
6Assume that there is a running animation for an animatable attribute. If the end value of the attribute changes due to an operation on the UI, just change the attribute value in the **animateTo** closure or change the input parameter value of the **animation** API to create an animation. OpenHarmony then automatically connects the previous animation with the current animation – the created animation.
7
8
9```ts
10import curves from '@ohos.curves'
11class SetSlt{
12  scaleToggle:boolean = true
13  set():void{
14    this.scaleToggle = !this.scaleToggle;
15  }
16}
17let CurAn:Record<string,curves> = {'curve':curves.springMotion()}
18// Step 1: Declare the related state variable.
19@state scaleToggle: boolean = true;
20
21...
22Column() {
23  Button()
24    // Step 2: Set the declared state variable to the related animatable attribute API.
25    .scale(this.scaleToggle ? 1 : 0.5)
26    // Step 3: Change the state variable value through the click event, which then changes the attribute value.
27    .onclick(() => {
28      let sets = new SetSlt()
29      sets.set()
30    })
31    // Step 4: Enable the implicit animation. When the end value of the animation changes, the system automatically adds the smoothing animation.
32    .animation(CurAn)
33}
34...
35```
36
37A complete example is as follows: By clicking **click**, you change the **scale** attribute of the red square. When you click **click** repeatedly, the end value of the **scale** attribute changes continuously, and the current animation smoothly moves towards the new end value of the **scale** attribute.
38
39
40```ts
41import curves from '@ohos.curves';
42class SetSlt{
43  isAnimation:boolean = true
44  set():void{
45    this.isAnimation = !this.isAnimation;
46  }
47}
48@Entry
49@Component
50struct AnimationToAnimationDemo {
51  @State SetAnimation: SetSlt = new SetSlt();
52
53  build() {
54    Column() {
55      Text('ArkUI')
56        .fontWeight(FontWeight.Bold)
57        .fontSize(12)
58        .fontColor(Color.White)
59        .textAlign(TextAlign.Center)
60        .borderRadius(10)
61        .backgroundColor(0xf56c6c)
62        .width(100)
63        .height(100)
64        .scale({ x: this.SetAnimation.isAnimation ? 2 : 1, y: this.SetAnimation.isAnimation ? 2 : 1 })
65        .animation({ curve: curves.springMotion(0.4, 0.8) })
66
67      Button('Click')
68        .margin({ top: 200 })
69        .onClick(() => {
70          this.SetAnimation.set()
71        })
72    }
73    .width('100%')
74    .height('100%')
75    .justifyContent(FlexAlign.Center)
76  }
77}
78```
79
80![en-us_image_0000001599971890](figures/en-us_image_0000001599971890.gif)
81
82
83
84## Smoothing Between Gestures and Animations
85
86In scenarios where gestures are used, an attribute change is generally triggered when the user places or moves their finger (or fingers) on the screen, and continues after the user lifts their finger (or fingers) off the screen until the end value of the attributes is reached.
87
88The initial velocity of the attribute change after the user lifts their finger (or fingers) should be consistent with the velocity of the attribute change at the moment before the user lifts their finger (or fingers). If the former is **0**, it feels like a running car stops suddenly, an unusual abrupt change not welcomed by users.
89
90In cases where smoothing between gestures and animations is required, for example, when scrolling a list, you can apply a responsive spring curve for the attribute animation running when the user places or moves their finger (or fingers) on the screen; and apply a spring curve for the attribute animation running after the user lifts their finger (or fingers) off the screen. For the animation that uses the [springMotion](../reference/apis/js-apis-curve.md#curvesspringmotion9) curve, the attribute animation running when the user places or moves their finger (or fingers) on the screen automatically inherits the previous velocity and starts from where the previous animation leaves off.
91
92
93```ts
94import curves from '@ohos.curves'
95class SetOffset{
96  offsetX:number = 0;
97  offsetY:number = 0;
98  set(x:number,y:number):void{
99    this.offsetX = x;
100    this.offsetY = y;
101  }
102}
103// Step 1: Declare related state variables.
104@state offsetX: number = 0;
105@State offsetY: number = 0;
106targetOffsetX: number = 100;
107targetOffsetY: number = 100;
108...
109Column()
110  // Step 2: Set the declared state variables to the related animatable attribute APIs.
111  .translate({ x: this.offsetX, y: this.offsetY})
112  .gesture(
113    PanGesture({})
114      .onActionUpdate((event?: GestureEvent) => {
115        // Step 3: Change the state variable value for the time when the user places or moves their finger (or fingers) on the screen and use reponsiveSpringMotion for movement toward the new value.
116        animateTo({
117          curve: curves.responsiveSpringMotion()
118        }, () => {
119          if(event){
120            let setxy = new SetOffset();
121            setxy.set(event.offsetX,event.offsetY)
122          }
123        })
124      })
125      .onActionEnd(() => {
126        // Step 4: Set the end value of the state variable for after the user lifts their finger (or fingers), and use springMotion for movement toward the new value. The springMotion animation inherits the previous velocity.
127        animateTo({
128          curve: curves.SpringMotion()
129        }, () => {
130          let setxy = new SetOffset();
131          setxy.set(targetOffsetX,targetOffsetY)
132        })
133      })
134  )
135...
136```
137
138Below is the complete sample code and effect.
139
140
141```ts
142import curves from '@ohos.curves';
143
144@Entry
145@Component
146struct SpringMotionDemo {
147  @State positionX: number = 100;
148  @State positionY: number = 100;
149  diameter: number = 50;
150
151  build() {
152    Column() {
153      Row() {
154        Circle({ width: this.diameter, height: this.diameter })
155          .fill(Color.Blue)
156          .position({ x: this.positionX, y: this.positionY })
157          .onTouch((event?: TouchEvent) => {
158            if(event){
159              if (event.type === TouchType.Move) {
160                // When the user places or moves their finger on the screen, use the responsiveSpringMotion curve.
161                animateTo({ curve: curves.responsiveSpringMotion() }, () => {
162                  // Subtract the radius so that the center of the ball moves to where the finger is placed.
163                  this.positionX = event.touches[0].screenX - this.diameter / 2;
164                  this.positionY = event.touches[0].screenY - this.diameter / 2;
165                  console.info(`move, animateTo x:${this.positionX}, y:${this.positionY}`);
166                })
167              } else if (event.type === TouchType.Up) {
168                // After the user lifts their finger off the screen, use the springMotion curve.
169                animateTo({ curve: curves.springMotion() }, () => {
170                  this.positionX = 100;
171                  this.positionY = 100;
172                  console.info(`touchUp, animateTo x:100, y:100`);
173                })
174              }
175            }
176          })
177      }
178      .width("100%").height("80%")
179      .clip(true) // If the ball moves beyond the parent component, it is invisible.
180      .backgroundColor(Color.Orange)
181
182      Flex({ direction: FlexDirection.Row, alignItems: ItemAlign.Start, justifyContent: FlexAlign.Center }) {
183        Text("Drag the ball").fontSize(16)
184      }
185      .width("100%")
186
187      Row() {
188        Text('Click position: [x: ' + Math.round(this.positionX) + ', y:' + Math.round(this.positionY) + ']').fontSize(16)
189      }
190      .padding(10)
191      .width("100%")
192    }.height('100%').width('100%')
193  }
194}
195```
196
197![en-us_image_0000001647027001](figures/en-us_image_0000001647027001.gif)
198