DEV Community

HarmonyOS
HarmonyOS

Posted on

Tabs component preloading

Read the original article:Tabs component preloading

Problem Description

Can Tabs' subcomponents be preloaded in advance? What is the specific implementation method?

Background Knowledge

The preloadItems method of the Tabs component can control Tabs to preload specified child nodes.
The cachedCount of the Swiper component can set the number of preloaded subcomponents.

Troubleshooting Process

  • Identify the Preloading Requirement
  • Inspect Current Component Setup
  • Evaluate Performance Constraints
  • Check for Custom Implementations
  • Test and Iterate Solutions
  • Debug Common Issues

Solution

Solution 1: Calling the preloadItems API of the Tabs component will load all specified child nodes at once. For performance reasons, it is recommended to load child nodes in batches. For specific code examples, see Example 11 Preloading Child Nodes on the official website .

Solution 2: The Swiper component supports preloading. You can use the Swiper component to build custom tabs and implement the preloading function. Please refer to the following sample code:

@Entry
@ComponentV2
struct TabsPreLoadDemo {
  @Local tabNames: string[] = ['Airplane', 'Railway', 'Self-driving', 'Subway', 'Bus', 'Bicycle']
  @Local selectedTabIndex: number = 0
  @Local indicatorLeftOffset: number = 0
  @Local indicatorOffset: number = 0
  @Local firstWidth: number = -1
  @Local otherWidth: number = -1
  @Local swiperController: SwiperController = new SwiperController()
  @Local swiperWidth: number = 0

  build() {
    RelativeContainer() {
      Stack() {
        Rect()
          .height(30)
          .stroke(Color.Black)
          .radius(10)
          .width(this.firstWidth)
          .fill('#bff9f2')
          .position({
            left: this.indicatorLeftOffset + this.indicatorOffset,
            bottom: 0
          })
          .animation({ duration: 300, curve: Curve.LinearOutSlowIn })
      }
      .width('100%')
      .alignRules({
        center: { anchor: 'Tabs', align: VerticalAlign.Center }
      })

      Row() {
        ForEach(this.tabNames, (name: string, index: number) => {
          Row() {
            Text(name)
              .fontSize(16)
              .fontWeight(this.selectedTabIndex === index ? FontWeight.Bold : FontWeight.Normal)
              .textAlign(TextAlign.Center)
              .animation({ duration: 300 })
            Image($r('app.media.startIcon'))
              .width(14)
              .height(14)
              .margin({ left: 2 })
              .visibility(this.selectedTabIndex === index ? Visibility.Visible : Visibility.None)
              .animation({ duration: 300 })
          }
          .justifyContent(FlexAlign.Center)
          .layoutWeight(this.selectedTabIndex === index ? 1.5 : 1)
          .animation({ duration: 300 })
          .onClick(() => {
            this.selectedTabIndex = index;
            this.swiperController.changeIndex(index, false);
            this.getUIContext().animateTo({ duration: 500, curve: Curve.LinearOutSlowIn }, () => {
              this.indicatorLeftOffset = this.otherWidth * index;
            })
          })
        })
      }
      .width('100%')
      .height(30)
      .id('Tabs')
      .onAreaChange((oldValue: Area, newValue: Area) => {
        let tabWidth = newValue.width.valueOf() as number;
        this.firstWidth = 1.5 * tabWidth / (this.tabNames.length + 0.5);
        this.otherWidth = tabWidth / (this.tabNames.length + 0.5);
      })

      Swiper(this.swiperController) {
        ForEach(this.tabNames, (name: string, index: number) => {
          Column() {
            Text(`${name} - ${index}`)
              .fontSize(24)
              .fontWeight(FontWeight.Bold)
          }
          .alignItems(HorizontalAlign.Center)
          .justifyContent(FlexAlign.Center)
          .height('100%')
          .width('100%')
        })
      }
      .cachedCount(3)
      .onAnimationStart((index: number, targetIndex: number, extraInfo: SwiperAnimationEvent) => {
        if (targetIndex > index) {
          this.indicatorLeftOffset += this.otherWidth;
        } else if (targetIndex < index) {
          this.indicatorLeftOffset -= this.otherWidth;
        }
        this.indicatorOffset = 0
        this.selectedTabIndex = targetIndex
      })
      .onAnimationEnd((index: number, extraInfo: SwiperAnimationEvent) => {
        this.indicatorOffset = 0
      })
      .onGestureSwipe((index: number, extraInfo: SwiperAnimationEvent) => {
        let move: number = this.GetOffset(extraInfo.currentOffset);
        if ((this.selectedTabIndex === 0 && extraInfo.currentOffset > 0) ||
          (this.selectedTabIndex === this.tabNames.length - 1 && extraInfo.currentOffset < 0)) {
          return;
        }
        this.indicatorOffset = extraInfo.currentOffset < 0 ? move : -move;
      })
      .onAreaChange((oldValue: Area, newValue: Area) => {
        let width = newValue.width.valueOf() as number;
        this.swiperWidth = width;
      })
      .curve(Curve.LinearOutSlowIn)
      .loop(false)
      .indicator(false)
      .width('100%')
      .id('MainContext')
      .alignRules({
        top: { anchor: 'Tabs', align: VerticalAlign.Bottom },
        bottom: { anchor: '__container__', align: VerticalAlign.Bottom }
      })
    }
    .height('100%')
    .width('100%')
    .padding(10)
  }

  GetOffset(swiperOffset: number): number {
    let swiperMoveRatio: number = Math.abs(swiperOffset / this.swiperWidth);
    let tabMoveValue: number = swiperMoveRatio >= 1 ? this.otherWidth : this.otherWidth * swiperMoveRatio;
    return tabMoveValue;
  }
}
Enter fullscreen mode Exit fullscreen mode

Comparison of the two solutions:

Solution 1: preloadItems

It can be loaded in batches and is easy to use.

In common scenarios, this method is recommended.

Solution 2: Swiper

It cannot be preloaded in batches, and requires relatively complex customization.

Customize Tabs and use the Swiper component features.

Written by Mucahid Kincir

Top comments (0)