讨论广场 问答详情
HarmonyOS Tabs的频道changeIndex到最后的时候,获取的windowoffset.x不对 (API12+)
厂里的码农 2025-11-04 17:40:43
85 评论 分享
import { componentUtils } from '@kit.ArkUI';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { initTabData } from '../viewmodel/TabViewModel';
import { TabItem } from '../viewmodel/TabItem';
import { Constants } from '../common/Constants';

@Entry
@Component
struct SlideAndMoreTab {
  @State tabArray: Array<TabItem> = initTabData();
  @State focusIndex: number = 0;
  @State pre: number = 0;
  @State index: number = 0;
  @State test: boolean = false;
  @State indicatorLeftMargin: number = 0;
  @State indicatorWidth: number = 0;
  private controller: TabsController = new TabsController();
  private listScroller: Scroller = new Scroller();
  private tabsWidth: number = 0;
  private tabWidth: number = 0;
  private iteration: number = 1;
  private swipeRatio: number = 0.5;

  @Builder
  Tab(tabName: string | Resource, tabIndex: number) {
    Row() {
      Text(tabName)
        .fontSize(16)
        .fontColor(tabIndex === this.focusIndex ? '#0A59F7' : '#E6000000')
        .id(tabIndex.toString())
        .onAreaChange((oldValue: Area, newValue: Area) => {
          if (this.focusIndex === tabIndex &&
            (this.indicatorLeftMargin === 0 || this.indicatorWidth === 0)) {
            if (newValue.position.x !== undefined) {
              let positionX = Number.parseFloat(newValue.position.x.toString());
              this.indicatorLeftMargin = Number.isNaN(positionX) ? 0 : positionX;
            }
            let width = Number.parseFloat(newValue.width.toString());
            this.tabWidth = Number.isNaN(width) ? 0 : width;
            this.indicatorWidth = this.tabWidth;
          }
        })
    }
    .padding({ left: 12, right: 12 })
    .justifyContent(FlexAlign.Center)
    .height(30)
    .onClick(() => {
      this.controller.changeIndex(tabIndex);
      this.focusIndex = tabIndex;
    })
  }

  build() {
    Column() {
      Stack({ alignContent: Alignment.TopStart }) {
        Column() {
          Row() {
            List({ initialIndex: 0, scroller: this.listScroller }) {
              ForEach(this.tabArray, (item: TabItem, index: number) => {
                this.Tab(item.name, index)
              }, (item: TabItem, index: number) => JSON.stringify(item) + index)
            }
            .listDirection(Axis.Horizontal)
            .height(30)
            .scrollBar(BarState.Off)
            .width('85%')
            .friction(0.6)
            .onWillScroll((xOffset: number) => {
              this.indicatorLeftMargin -= xOffset;
            })

            Image($r('app.media.more'))
              .width(20)
              .height(15)
              .margin({ left: 16 })
              .onClick(() => {
                this.controller.changeIndex(8);
                // this.focusIndex = 8;
              })
          }
          .height(52)
          .width('100%')
        }
        .alignItems(HorizontalAlign.Center)
        .width('100%')

        Column()
          .height(2)
          .width(this.indicatorWidth)
          .margin({ left: this.indicatorLeftMargin, top: 40 })
          .backgroundColor('#0A59F7')
      }
      .width('100%')

      Tabs({ barPosition: BarPosition.Start, controller: this.controller }) {
        ForEach(this.tabArray, (item: TabItem) => {
          TabContent() {
            Row() {
              Text(item.name)
                .height(300)
                .fontSize(30)
            }
            .width('100%')
            .justifyContent(FlexAlign.Center)
          }
          .backgroundColor(Color.White)
        }, (item: TabItem, index: number) => JSON.stringify(item) + index)
      }
      .onAreaChange((_oldValue: Area, newValue: Area) => {
        let width = Number.parseFloat(newValue.width.toString());
        this.tabsWidth = Number.isNaN(width) ? 0 : width;
      })
      .width('100%')
      .barHeight(0)
      .animationDuration(Constants.ANIMATION_DURATION)
      .onAnimationStart((index: number, targetIndex: number) => {
        hilog.info(0x0000, 'index', index.toString());
        this.focusIndex = targetIndex;
        let targetIndexInfo = this.getTextInfo(targetIndex);
        this.startAnimateTo(Constants.ANIMATION_DURATION, targetIndexInfo.left, targetIndexInfo.width);
        this.listScroller.scrollToIndex(targetIndex, true, ScrollAlign.CENTER);
      })
      .onAnimationEnd((index: number, event: TabsAnimationEvent) => {
        let currentIndicatorInfo = this.getCurrentIndicatorInfo(index, event);
        this.startAnimateTo(0, currentIndicatorInfo.left, currentIndicatorInfo.width);
      })
      .onGestureSwipe((index: number, event: TabsAnimationEvent) => {
        let currentIndicatorInfo = this.getCurrentIndicatorInfo(index, event);
        this.focusIndex = currentIndicatorInfo.index;
        this.indicatorLeftMargin = currentIndicatorInfo.left;
        this.tabWidth = currentIndicatorInfo.width;
        this.indicatorWidth = currentIndicatorInfo.width;
      })
    }
    .height('100%')
  }

  // Gets component size, position, translation, scaling rotation, and affine matrix attribute information.
  private getTextInfo(index: number): Record<string, number> {
    let modePosition: componentUtils.ComponentInfo = componentUtils.getRectangleById(index.toString());
    return { 'left': px2vp(modePosition.windowOffset.x), 'width': px2vp(modePosition.size.width) }
  }

  private getCurrentIndicatorInfo(index: number, event: TabsAnimationEvent): Record<string, number> {
    let nextIndex = index;
    if (index > Constants.TAB_INDEX_ZERO && event.currentOffset > Constants.TAB_INDEX_ZERO) {
      nextIndex--;
    } else if (index < Constants.TAB_INDEX_THREE && event.currentOffset < Constants.TAB_INDEX_ZERO) {
      nextIndex++;
    }
    let indexInfo = this.getTextInfo(index);
    let nextIndexInfo = this.getTextInfo(nextIndex);
    let swipeRatio = Math.abs(event.currentOffset / this.tabsWidth);
    let currentIndex = swipeRatio > this.swipeRatio ? nextIndex : index;
    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 };
  }

  private startAnimateTo(duration: number, leftMargin: number, width: number) {
    animateTo({
      duration: duration,
      curve: Curve.Linear,
      iterations: this.iteration,
      playMode: PlayMode.Normal,
    }, () => {
      this.indicatorLeftMargin = leftMargin;
      this.indicatorWidth = width;
    })
  }
}

使用上述代码,当点击菜单滚动到最后一个频道,下划线的left坐标不对。怀疑是频道太多,切换的时候所有频道进行加载导致页面卡顿,获取的windowOffset.x不对。如果只有2页频道就能正常定位。怎么解决?

希望保留目前左右滑动切换tab的功能,同时提供一个实现方式:当点击最后一个tab切换时,能否关闭tabs切换(左右滑动)动画?直接定位到某个个tab,同时tabContent显示的就是那个列表。而不是通过动画切换过去。导致前面的所有tabcontent的列表都加载,导致页面卡顿,计算的滑块indicatorLeftMargin的坐标是错的。右侧有个显示全部菜单的功能,代码如下:

ChannelPickerView({
  titles: this.allMenuInfoList.flatMap(item => item.name),
  selectedTitle: this.allMenuInfoList[this.currentTabIndex].name,
  onBackClick: () => {
    this.showMenu = false
  },
  onItemClick: (name) => {
    const index = this.allMenuInfoList.findIndex(item => item.name === name)
    if (index >= 0) {
      this.animationDuration = 0
      this.controller.changeIndex(index)
      this.currentTabIndex = index
      this.scroller.scrollToIndex(index, false, ScrollAlign.CENTER);
      setTimeout(() => {
        let targetIndexInfo = getTextInfo(index, this.tabItemPrefix);
        this.indicatorLeftMargin = targetIndexInfo.left;
        this.indicatorWidth = targetIndexInfo.width;
        this.animationDuration = 300
      }, 100)
    }
    this.showMenu = false
  }
})
  .width('100%')
  .height('100%')

已经能实现点击tab频道,不进行动画切换tabcontent,但是用了setTimeout,这明显是不可取的实现方式,能否提供个不需要延时就能关闭tab切换动画,同时恢复tab切换的属性方式?

85 评论 分享
写回答
全部评论(1)

可以使用Tabs的onChange接口,点击自定义tab:

 .onClick(() => {
   const index = this.tabArray.findIndex(item => item.name === tabName)
   this.focusIndex = index;
   this.listScroller.scrollToIndex(index, false, ScrollAlign.CENTER);
   this.controller.changeIndex(index);
 })

Tabs中的onChange接口:

.onChange((index: number) => {
  this.focusIndex = index;
  this.animationDuration = 0
  let targetIndexInfo = this.getTextInfo(index);
  this.indicatorWidth = targetIndexInfo.width;
  this.indicatorLeftMargin = targetIndexInfo.left;
 })

左右拖拽滑动时启动动画,onAnimationStart中设置:this.animationDuration = 300

2025-11-04 17:41:34