https://developer.huawei.com/consumer/cn/doc/harmonyos-references-V5/ts-container-tabs-V5\#%E7%A4%BA%E4%BE%8B9单子里的示例9,在tabs数量很多,需要设置为可滑动模式时,切换tab会跳动导致底部的蓝条无法对上
当前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%') } }
当前tabs标签数量较多时,切换过程存在延迟效果,暂无法规避; 可通过 Scroll + Swiper 自定义方式实现类似Tabs组件效果,参考代码如下: