HarmonyOS Tabs如何设置不自动滚动

https://developer.huawei.com/consumer/cn/doc/harmonyos-references-V5/ts-container-tabs-V5#%E7%A4%BA%E4%BE%8B9

单子里的示例9,在tabs数量很多,需要设置为可滑动模式时,切换tab会跳动导致底部的蓝条无法对上

HarmonyOS
2024-12-25 07:57:15
浏览
收藏 0
回答 1
待解决
回答 1
按赞同
/
按时间
superinsect

当前tabs标签数量较多时,切换过程存在延迟效果,暂无法规避; 可通过 Scroll + Swiper 自定义方式实现类似Tabs组件效果,参考代码如下:

import curves from '@ohos.curves';
import display from '@ohos.display';
import ComponentUtils from '@ohos.arkui.UIContext';


@Entry
@Component
struct TabsDemo {
  private componentUtils: ComponentUtils.ComponentUtils = this.getUIContext().getComponentUtils()
  private displayInfo: display.Display | null = null;
  private swiperController: SwiperController = new SwiperController();
  private initialTabMargin: number = 5;
  private animationDuration: number = 500;
  private animationCurve: ICurve = curves.interpolatingSpring(7, 1, 328, 34);
  @State swiperIndex: number = 0; // 内容区域页号
  @State swiperWidth: number = 0;
  @State underlineWidth: number = 0; // 下划线宽度
  @State underlineMarginLeft: number = 0; // 下划线起点
  @State indicatorIndex: number = 0; // 页签编号
  private scroller: Scroller = new Scroller();
  private titleList: string[] =
    ['Pink', 'Yellow', 'Blue', 'Green', 'Pink', 'Yellow', 'Blue', 'Green', 'Pink', 'Yellow', 'Blue', 'Green', 'Pink',
      'Yellow', 'Blue', 'Green'];
  private data: number[] = []; // 内容数据
  private textLength: number[] = []; // 控制页签所在父容器宽度

  aboutToAppear(): void {
    this.displayInfo = display.getDefaultDisplaySync();
    this.data = this.titleList.map((item, index) => index + 1);
    this.textLength = this.titleList.map(item => item.length);
  }

  // 获取屏幕宽度,用于计算使选中页签居中所需要的位移量
  private getDisplayWidth(): number {
    return this.displayInfo != null ? px2vp(this.displayInfo.width) : 0;
  }

  // 获取页签的起始位置和宽度,决定选中页签下划线的位置和宽度
  private getTextInfo(index: number): Record<string, number> {
    let rectangle = this.componentUtils.getRectangleById(index.toString())
    return { 'left': px2vp(rectangle.windowOffset.x), 'width': px2vp(rectangle.size.width) }
  }

  // 获取当前选中页签信息
  private getCurrentIndicatorInfo(index: number, event: TabsAnimationEvent): Record<string, number> {
    let nextIndex = index;
    if (index > 0 && event.currentOffset > 0) {
      nextIndex--; // 左滑
    } else if (index < this.data.length - 1 && event.currentOffset < 0) {
      nextIndex++; // 右滑
    }

    let indexInfo = this.getTextInfo(index);
    let nextIndexInfo = this.getTextInfo(nextIndex);
    let swipeRatio = Math.abs(event.currentOffset / this.swiperWidth); // 计算翻页进度
    let currentIndex = swipeRatio > 0.5 ? nextIndex : index // 页面滑动超过一半,tabBar切换到下一页。
    let currentLeft = indexInfo.left + (nextIndexInfo.left - indexInfo.left) * swipeRatio
    let currentWidth = indexInfo.width + (nextIndexInfo.width - indexInfo.width) * swipeRatio
    return { 'index': currentIndex, 'left': currentLeft, 'width': currentWidth }
  }

  // 控制页签所在Scroll容器的偏移,保证选中页签居中显示,当选中页签左边的页签总宽度有限时,offset为0,右边同理
  private scrollIntoView(currentIndex: number): void {
    const indexInfo = this.getTextInfo(currentIndex);
    let currentIndexLeft = indexInfo.left;
    let currentIndexWidth = indexInfo.width;
    let screenWidth = this.getDisplayWidth();
    if (screenWidth < 100) {
      screenWidth = 300;
    }
    let targetLeft = screenWidth / 2 - currentIndexWidth / 2;
    const currentOffsetX: number = this.scroller.currentOffset().xOffset; // 当前位移
    this.scroller.scrollTo({
      xOffset: currentOffsetX + (currentIndexLeft - targetLeft), // 目标位移 = 当前位移 + 目标页签移动距离
      yOffset: 0,
      animation: {
        duration: this.animationDuration,
        curve: this.animationCurve,
      }
    });
  }

  // 下划线滑动动画
  private startAnimateTo(duration: number, marginLeft: number, width: number): void {
    animateTo({
      duration: duration,
      curve: this.animationCurve,
    }, () => {
      this.underlineMarginLeft = marginLeft;
      this.underlineWidth = width;
    })
  }

  // 控制下划线滑动
  private underlineScrollAuto(duration: number, index: number): void {
    const indexInfo = this.getTextInfo(index);
    this.startAnimateTo(duration, indexInfo.left, indexInfo.width);
  }

  build() {
    Column() {
      Column() {
        // 页签
        Scroll(this.scroller) {
          Row() {
            ForEach(this.titleList, (item: string, index: number) => {
              Column() {
                Text(item)
                  .fontColor(this.indicatorIndex == index ? Color.Black : Color.Gray)
                  .fontWeight(this.indicatorIndex == index ? FontWeight.Bold : FontWeight.Normal)
                  .margin({ left: this.initialTabMargin, right: this.initialTabMargin })
                  .id(index.toString())
                  .onAreaChange((oldValue: Area, newValue: Area) => {
                    if (this.indicatorIndex == index && (this.underlineMarginLeft === 0 || this.underlineWidth === 0)) {
                      if (newValue.globalPosition.x != undefined) {
                        let positionX = Number.parseFloat(newValue.globalPosition.x.toString());
                        this.underlineMarginLeft = Number.isNaN(positionX) ? 0 : positionX;
                      }
                      let width = Number.parseFloat(newValue.width.toString());
                      this.underlineWidth = Number.isNaN(width) ? 0 : width;
                    }
                  })
                  .onClick(() => {
                    this.indicatorIndex = index; // 调整当前页签
                    this.scrollIntoView(index); // 调整页签位置
                    this.underlineScrollAuto(this.animationDuration, index); // 调整下划线位置
                    this.swiperIndex = index; // 调整内容区域显示
                  })
              }
              .width(this.textLength[index] * 12)
            }, (item: string) => item)
          }
          .height(32)
        }
        .width('90%')
        .scrollable(ScrollDirection.Horizontal)
        .scrollBar(BarState.Off)
        .edgeEffect(EdgeEffect.Spring) // 开启页签栏边缘回弹
        .onWillScroll((xOffset: number) => {
          this.underlineMarginLeft -= xOffset;
        })
        .onScrollStop(() => {
          this.underlineScrollAuto(this.animationDuration, this.indicatorIndex);
        })

        // 线条
        Column()
          .width(this.underlineWidth)
          .height(2)
          .borderRadius(2)
          .backgroundColor(Color.Blue)
          .margin({ left: this.underlineMarginLeft, top: 5 })
          .alignSelf(ItemAlign.Start)
      }
      .width('100%')
      .margin({ top: 30, bottom: 15 })

      // 内容区域
      Swiper(this.swiperController) {
        ForEach(this.data, (item: number, index: number) => {
          Column() {
            Text(item.toString())
              .width('100%')
              .height(360)
              .borderRadius(5)
              .backgroundColor(Color[this.titleList[index]])
              .textAlign(TextAlign.Center)
              .fontSize(32)
          }
          .onAreaChange((oldValue: Area, newValue: Area) => {
            let width = Number.parseFloat(newValue.width.toString());
            this.swiperWidth = Number.isNaN(width) ? 0 : width;
          })
        }, (item: string) => item)
      }
      .cachedCount(2)
      .index(this.swiperIndex)
      .indicator(false)
      // .curve(this.animationCurve) // 自定义动画曲线
      .curve(Curve.Linear)
      .loop(false)
      .onAnimationStart((index: number, targetIndex: number, event: SwiperAnimationEvent) => {
        this.scrollIntoView(targetIndex); // 调整页签位置
        this.indicatorIndex = targetIndex; // 调整当前页签
        this.underlineScrollAuto(this.animationDuration, targetIndex); // 调整下划线位置
      })
      .onChange((index: number) => {
        this.swiperIndex = index;
      })
      .onAnimationEnd((index: number, event: SwiperAnimationEvent) => {
      })
      .onGestureSwipe((index: number, event: SwiperAnimationEvent) => {
        let currentIndicatorInfo = this.getCurrentIndicatorInfo(index, event);
        this.indicatorIndex = currentIndicatorInfo.index;
        this.underlineMarginLeft = currentIndicatorInfo.left;
        this.underlineWidth = currentIndicatorInfo.width;
      })
    }
    .width('100%')
  }
}
分享
微博
QQ
微信
回复
2024-12-25 10:53:09
相关问题
HarmonyOS axios目前不自动维护cookies么
202浏览 • 1回复 待解决
HarmonyOS 自动横向滚动List
265浏览 • 1回复 待解决
HarmonyOS Tabs组件的Tab栏滚动问题
682浏览 • 1回复 待解决
HarmonyOS 顶部tabs如何设置左对齐
567浏览 • 1回复 待解决
HarmonyOS 如何动态设置Grid的滚动方向
139浏览 • 1回复 待解决