DEV Community

Cover image for HarmonyOS Development: Why is there no time for translation animation?
程序员一鸣
程序员一鸣

Posted on

HarmonyOS Development: Why is there no time for translation animation?

Foreword 

this article is based on Api13 

in the past two days, I have been engaged in animation. There is a very simple translation animation, but it has stuck me for a period of time. Due to the twists and turns of the matter, all friends listen to me. What I said is that there is a component that has set the translate attribute to translate from left to right. In order to be able to translate smoothly, the translation time needs to be added. The code is as follows:

 

@Entry
@Component
struct Index {
  @State translateX: number = 0

  build() {
    Column() {

      Text("动画" )
        .width(50)
        .height(50)
        .backgroundColor(Color.Pink)
        .textAlign(TextAlign.Center)
        .margin({ top: 10 })
        .fontColor(Color.White)
        .translate({ x: this.translateX })
        .animation({ duration: 500 })

      Button("点击")
        .margin({ top: 10 })
        .onClick(() => {
          this.translateX = 200
        })
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

the above is a very simple code. After clicking the button, the component pans the 200 from left to right for 500 milliseconds. 

Let's look at the actual running effect: 

Image description

as you can see, the above code, everything is fine and executes according to normal functions. However, I am not moving one component, but multiple components, so I created another component.

 

Column() {

      Text("动画1")
        .width(50)
        .height(50)
        .backgroundColor(Color.Pink)
        .textAlign(TextAlign.Center)
        .margin({ top: 10 })
        .fontColor(Color.White)
        .translate({ x: this.translateX })
        .animation({ duration: 500 })

      Text("动画2")
        .width(50)
        .height(50)
        .backgroundColor(Color.Pink)
        .textAlign(TextAlign.Center)
        .margin({ top: 10 })
        .fontColor(Color.White)
        .translate({ x: this.translateX })
        .animation({ duration: 500 })

      Button("点击")
        .margin({ top: 10 })
        .onClick(() => {
          this.translateX = 200
        })
    }
Enter fullscreen mode Exit fullscreen mode

After the above code runs, there is no problem. If there are N components, copying one by one is not the way. Therefore, I will use ForEach to traverse, thus directly controlling the data source. The code is changed to the following:

 

Column() {

      ForEach([1, 2], (item: number, index: number) => {
        Text("动画"+index)
          .width(50)
          .height(50)
          .backgroundColor(Color.Pink)
          .textAlign(TextAlign.Center)
          .margin({ top: 10 })
          .fontColor(Color.White)
          .translate({ x: this.translateX })
          .animation({ duration: 500 })
      })

      Button("点击")
        .margin({ top: 10 })
        .onClick(() => {
          this.translateX = 200
        })
    }
Enter fullscreen mode Exit fullscreen mode

the effect is the same as before. After clicking the button, the two components can also be translated synchronously. Some friends said, what is your problem? tell me why? looking my eyes! 

Dear friends, take it easy, the problem will come soon. 

Because it is not only the problem of multi-component movement, the subsequent functions and the moving distance of each component are different, so I redefined the data source, and the moving distance is obtained from the data source:

 

ForEach(this.translateArray, (item: number,index:number) => {
        Text("动画"+index)
          .width(50)
          .height(50)
          .backgroundColor(Color.Pink)
          .textAlign(TextAlign.Center)
          .margin({ top: 10 })
          .fontColor(Color.White)
          .translate({ x: item })
          .animation({ duration: 500 })
      })
Enter fullscreen mode Exit fullscreen mode

the above code can realize the dynamic setting of the translation distance of each component. If you want that component to move, you can let that component move. You only need to control the data source. It seems that the code is perfect. At that time, I felt the same way, so I ran the program.

Have the first component translate the 200 and the second component translate the 300.

 

Button("点击")
        .margin({ top: 10 })
        .onClick(() => {
          this.translateArray[0] = 200
          this.translateArray[1] = 300
        })
Enter fullscreen mode Exit fullscreen mode

Look at the effect after running: 

Image description

move over? Ah, it did move over, but the time for panning animation is gone! It was just a very stiff translation. A ForEach lost the animation time. At this time, my brain was already a string of question marks. 

Pursuing the problem 

from the predecessors, it can be seen that ForEach was used at the beginning, but the data source was fixed and the common translation attribute was used. After the code was run at that time, everything was normal until the data source was switched to a dynamic data source, and the animation time at this time would not take effect. In view of this problem, we will verify again to distinguish the data source from the changed translation data, and we will look at the actual effect again.

 

@Entry
@Component
struct Index {
  @State dataArray: number[] = [0, 0]
  @State translateArray: number[] = [0, 0]

  build() {
    Column() {

      ForEach(this.dataArray, (_: number,index:number) => {
        Text("动画"+index)
          .width(50)
          .height(50)
          .backgroundColor(Color.Pink)
          .textAlign(TextAlign.Center)
          .margin({ top: 10 })
          .fontColor(Color.White)
          .translate({ x: this.translateArray[index] })
          .animation({ duration: 500 })
      })

      Button("点击")
        .margin({ top: 10 })
        .onClick(() => {
          this.translateArray[0] = 200
          this.translateArray[1] = 300
        })
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

The code is still extremely simple. Two data sources are defined, one for data loading and the other for translation distance setting. After running, we will see the effect: 

Image description

we can see that the animation time is in effect again, and basically, we can draw a conclusion for the time being, in ForEach property animation, if the data source changes, the animation time is not effective.

Why is this? 

What does the data source change have to do with animation time? This is simply the wind cattle horse has nothing to do, so, I checked the official website ForEach introduction, saw such a passage: 

Image description

it seems that when the key value in ForEach changes, the ArkUI framework will consider that the array element has been replaced or modified and will create a new component based on the new key value. 

Looking at the previous code, since there is no setting, the default key value is used uniformly, that is(item: Object, index: number) => { return index + '__' + JSON.stringify(item); }, the log was printed and the changes did occur. 

Image description

However, one thing is still unknown. The creation of your component has something to do with my animation time. Then I checked the official pair. Introduction to animation.

 

如果内部组件还未创建,动画时机过早,动画属性没有初值无法对组件产生动画。
Enter fullscreen mode Exit fullscreen mode

Through the above, we can intuitively understand the cause of the problem. First, due to the change of key value, the component is recreated. Second, due to the re-creation of the component, the animation timing is too early, resulting in the attribute not taking effect. 

Related Summary 

this is a series of problems caused by attribute animation, which reflects ForEach's key-value knowledge points. Therefore, friends, don't panic when encountering problems. It is still official. However, this problem still teaches yourself a lesson. The basic knowledge is not firm, which is the biggest pain point! 

Some friends said that although the use of two data sources solved the above problems, I would like to use one. In actual development, I can use the form of an array of objects to change the attributes that need to be changed to avoid recreating new components. 

Specific cases are as follows:

 

@Observed
class TranslateBean {
  name?: string
  translate?: number

  constructor(name: string, translate: number) {
    this.name = name
    this.translate = translate
  }
}

@Component
struct TextView {
  @ObjectLink item: TranslateBean

  build() {
    Text(this.item.name)
      .width(50)
      .height(50)
      .backgroundColor(Color.Pink)
      .textAlign(TextAlign.Center)
      .margin({ top: 10 })
      .fontColor(Color.White)
      .translate({ x: this.item.translate })
      .animation({ duration: 500 })
  }
}


@Entry
@Component
struct Index {
  @State dataArray: TranslateBean[] = [new TranslateBean("动画一", 0), new TranslateBean("动画二", 0)]
  @State translateArray: number[] = [200, 200]

  build() {
    Column() {

      ForEach(this.dataArray, (item: TranslateBean, index: number) => {
        TextView({ item: item })
      })

      Button("点击")
        .margin({ top: 10 })
        .onClick(() => {
          this.dataArray[0].translate = 200
          this.dataArray[1].translate = 300
        })
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

this article tags: HarmonyOS/ArkUI/property animation

Top comments (0)