DEV Community

HarmonyOS
HarmonyOS

Posted on

The List sub-component displays items in sequence with animations.

Read the original article:The List sub-component displays items in sequence with animations.

The List sub-component displays items in sequence with animations.

Problem Description

Lists (List) are usually displayed all at once. So, how can we achieve the animation of sub-components within the list to be displayed in a sequential chain order?

Background Knowledge

When certain common properties of a component change, a gradient transition effect can be achieved through the animation property.

Troubleshooting Process

  1. Problem Analysis The goal is to implement a chained animation effect for list items, where each item appears sequentially with a delay based on its position in the list.
  2. Key Points Verification
    • Delay Calculation: The index property is used to calculate the delay for each CardItem, ensuring the animation starts at increasing intervals.
    • Animation Trigger: The .onAppear() method automatically triggers the animation when the component is rendered.
    • State Management: The @State isAppear property controls the visibility of the animation, transitioning from hidden to visible.
  3. Potential Issues
    • Dynamic Data: If the this.data array changes, the order of items may shift, disrupting the chained animation sequence.
    • Performance: A large number of list items with significant delays could affect performance due to the sequential rendering and animation execution.

Analysis Conclusion

  1. Chained Animation Implementation By using the index property to set the animation delay (delay: index * 120), each CardItem is animated in sequence, creating a chained effect.
  2. Core Logic
    • The .onAppear() method activates the isAppear state, which controls the opacity and translation properties, initiating the animation.
    • The animation delay is directly tied to the index, ensuring each item appears in order.
  3. Applicable Scenarios
    • Suitable for static lists or scenarios where the data order remains unchanged.
    • For dynamic data, the order must align with the animation logic to maintain the chained effect.
  4. Optimization Recommendations
    • Avoid excessively long delays (e.g., index * 1000) to prevent user frustration.
    • For dynamic data, reset the animation state in .onAppear() to ensure the chained effect remains consistent.

Solution

1.Create a List based on the data source this.

   if (this.isShow) {
     List() {
       ForEach(this.data, (item: number) => {
         ListItem() {
           CardItem({ index: item })
         }
         .margin(10)
       }, (item: number) => item.toString())
     }
   }
Enter fullscreen mode Exit fullscreen mode

2.Create each sub-component CardItem of the List:

   @Prop index: number
   @State isAppear: boolean = false
   build() {
     Column() {
       Text(`${this.index}`).fontColor(Color.Blue).fontSize(16)
     }
     .justifyContent(FlexAlign.Center)
     .alignItems(HorizontalAlign.Center)
     .backgroundColor(Color.Green)
     .height(20)
     .width('100%')
     .opacity(this.isAppear ? 1 : 0)
     .translate({ x: this.isAppear ? 0 : 100 })
     .borderRadius(8)
   }
Enter fullscreen mode Exit fullscreen mode

3.The CardItem component can calculate an appropriate delay time based on its index in the list, thereby achieving a cascading animation effect where the next animation starts immediately after the previous one ends.

   .animation({
     duration: 500,
     delay: this.index * 120,
     curve: Curve.EaseOut
   })
Enter fullscreen mode Exit fullscreen mode

4.When the CardItem component first appears in the view (i.e., when the onAppear event is triggered), the entrance animation of the component will be initiated.

   .onAppear(() => {
         this.isAppear = true
    })
    .onDisAppear(() => {
         this.isAppear = false
    })
Enter fullscreen mode Exit fullscreen mode

5.The complete code is as follows:

   @Component
   struct CardItem {
     @Prop index: number
     @State isAppear: boolean = false
     build() {
       Column() {
         Text(`${this.index}`).fontColor(Color.Blue).fontSize(16)
       }
       .justifyContent(FlexAlign.Center)
       .alignItems(HorizontalAlign.Center)
       .backgroundColor(Color.Green)
       .height(20)
       .width('100%')
       .opacity(this.isAppear ? 1 : 0)
       .translate({ x: this.isAppear ? 0 : 100 })
       .borderRadius(8)
       .animation({
         duration: 500,
         delay: this.index * 120,
         curve: Curve.EaseOut
       })
       .onAppear(() => {
         this.isAppear = true
       })
       .onDisAppear(() => {
         this.isAppear = false
       })
     }
   }
   @Entry
   @Component
   struct ChainList {
     private data: number[] = [0, 1, 2, 3, 4, 5]
     @State isShow: boolean = false
     build() {
       Column() {
         Button('show list').onClick(() => {
           this.isShow = !this.isShow
         })
         if (this.isShow) {
           List() {
             ForEach(this.data, (item: number) => {
               ListItem() {
                 CardItem({ index: item })
               }
               .margin(10)
             }, (item: number) => item.toString())
           }
         }
       }.justifyContent(FlexAlign.Center)
       .alignItems(HorizontalAlign.Center)
       .width('100%')
       .padding(20)
     }
   }
Enter fullscreen mode Exit fullscreen mode

Verification Result

input9.gif

Written by Emrecan Karakas

Top comments (0)