动画过程中,Tabs页签切换场景下的闪屏问题
问题现象
滑动Tabs组件时,上方标签不能同步更新,在下方内容完全切换后才会闪动跳转,产生闪屏问题。
@Entry
@Component
struct TabsError {
@State currentIndex: number = 0;
@State animationDuration: number = 300;
@State indicatorLeftMargin: number = 0;
@State indicatorWidth: number = 0;
private textInfos: [number, number][] = [];
private isStartAnimateTo: boolean = false;
@Builder
tabBuilder(index: number, name: string) {
Column() {
Text(name)
.fontSize(16)
.fontColor(this.currentIndex === index ? $r('sys.color.brand') : $r('sys.color.ohos_id_color_text_secondary'))
.fontWeight(this.currentIndex === index ? 500 : 400)
.id(index.toString())
.onAreaChange((_oldValue: Area, newValue: Area) => {
this.textInfos[index] = [newValue.globalPosition.x as number, newValue.width as number];
if (this.currentIndex === index && !this.isStartAnimateTo) {
this.indicatorLeftMargin = this.textInfos[index][0];
this.indicatorWidth = this.textInfos[index][1];
}
})
}.width('100%')
}
build() {
Stack({ alignContent: Alignment.TopStart }) {
Tabs({ barPosition: BarPosition.Start }) {
TabContent() {
Column()
.width('100%')
.height('100%')
.backgroundColor(Color.Green)
.expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM])
}
.tabBar(this.tabBuilder(0, 'green'))
.expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM])
TabContent() {
Column()
.width('100%')
.height('100%')
.backgroundColor(Color.Blue)
.expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM])
}
.tabBar(this.tabBuilder(1, 'blue'))
.expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM])
TabContent() {
Column()
.width('100%')
.height('100%')
.backgroundColor(Color.Yellow)
.expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM])
}
.tabBar(this.tabBuilder(2, 'yellow'))
.expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM])
TabContent() {
Column()
.width('100%')
.height('100%')
.backgroundColor(Color.Pink)
.expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM])
}
.tabBar(this.tabBuilder(3, 'pink'))
.expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM])
}
.barWidth('100%')
.barHeight(56)
.width('100%')
.backgroundColor('#F1F3F5')
.animationDuration(this.animationDuration)
.expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM])
.onChange((index: number) => {
this.currentIndex = index; // 监听索引index的变化,实现页签内容的切换。
})
Column()
.height(2)
.borderRadius(1)
.width(this.indicatorWidth)
.margin({ left: this.indicatorLeftMargin, top: 48 })
.backgroundColor($r('sys.color.brand'))
}.width('100%')
}
}
可能原因
在Tabs左右翻页动画的结束回调中,刷新了选中页面的index值。造成当页面左右转场动画结束时,页签栏中index对应页签的样式(字体大小、下划线等)立刻发生改变,导致产生闪屏。
解决措施
在左右跟手翻页过程中,通过TabsAnimationEvent事件获取手指滑动距离,改变下划线在前后两个子页签之间的位置。在离手触发翻页动画时,一并触发下划线动画,保证下划线与页面左右转场动画同步进行。
build() {
Stack({ alignContent: Alignment.TopStart }) {
Tabs({ barPosition: BarPosition.Start }) {
TabContent() {
Column()
.width('100%')
.height('100%')
.backgroundColor(Color.Green)
.expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM])
}
.tabBar(this.tabBuilder(0, 'green'))
.expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM])
TabContent() {
Column()
.width('100%')
.height('100%')
.backgroundColor(Color.Blue)
.expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM])
}
.tabBar(this.tabBuilder(1, 'blue'))
.expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM])
TabContent() {
Column()
.width('100%')
.height('100%')
.backgroundColor(Color.Yellow)
.expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM])
}
.tabBar(this.tabBuilder(2, 'yellow'))
.expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM])
TabContent() {
Column()
.width('100%')
.height('100%')
.backgroundColor(Color.Pink)
.expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM])
}
.tabBar(this.tabBuilder(3, 'pink'))
.expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM])
}
.onAreaChange((_oldValue: Area, newValue: Area) => {
this.tabsWidth = newValue.width as number;
})
.barWidth('100%')
.barHeight(56)
.width('100%')
.expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM])
.backgroundColor('#F1F3F5')
.animationDuration(this.animationDuration)
.onChange((index: number) => {
this.currentIndex = index; // 监听索引index的变化,实现页签内容的切换。
})
.onAnimationStart((_index: number, targetIndex: number) => {
// 切换动画开始时触发该回调。下划线跟着页面一起滑动,同时宽度渐变。
this.currentIndex = targetIndex;
this.startAnimateTo(this.animationDuration, this.textInfos[targetIndex][0], this.textInfos[targetIndex][1]);
})
.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.currentIndex = currentIndicatorInfo.index;
this.indicatorLeftMargin = currentIndicatorInfo.left;
this.indicatorWidth = currentIndicatorInfo.width;
})
Column()
.height(2)
.borderRadius(1)
.width(this.indicatorWidth)
.margin({ left: this.indicatorLeftMargin, top: 48 })
.backgroundColor($r('sys.color.brand'))
}
.width('100%')
}
TabsAnimationEvent方法如下所示。
private getCurrentIndicatorInfo(index: number, event: TabsAnimationEvent): Record<string, number> {
let nextIndex = index;
if (index > 0 && event.currentOffset > 0) {
nextIndex--;
} else if (index < 3 && event.currentOffset < 0) {
nextIndex++;
}
let indexInfo = this.textInfos[index];
let nextIndexInfo = this.textInfos[nextIndex];
let swipeRatio = Math.abs(event.currentOffset / this.tabsWidth);
let currentIndex = swipeRatio > 0.5 ? nextIndex : index; // 页面滑动超过一半,tabBar切换到下一页。
let currentLeft = indexInfo[0] + (nextIndexInfo[0] - indexInfo[0]) * swipeRatio;
let currentWidth = indexInfo[1] + (nextIndexInfo[1] - indexInfo[1]) * swipeRatio;
return { 'index': currentIndex, 'left': currentLeft, 'width': currentWidth };
}
private startAnimateTo(duration: number, leftMargin: number, width: number) {
this.isStartAnimateTo = true;
animateTo({
duration: duration, // 动画时长
curve: Curve.Linear, // 动画曲线
iterations: 1, // 播放次数
playMode: PlayMode.Normal, // 动画模式
onFinish: () => {
this.isStartAnimateTo = false;
console.info('play end');
}
}, () => {
this.indicatorLeftMargin = leftMargin;
this.indicatorWidth = width;
})
}
运行效果如下图所示。
本文主要引用参考HarmonyOS官方文档
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。