HarmonyOS Tabs的频道changeIndex到最后的时候,获取的windowoffset.x不对 (API12+)
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切换的属性方式?
您需要先 登录 才能评论/回答
全部评论(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