HarmonyOS 应用闪屏问题性能优化二
作者:李洋-蛟龙腾飞
- 2025-06-05 广东
本文字数:4410 字
阅读完需:约 14 分钟
动画过程中,Tabs 页签切换场景下的闪屏问题
问题现象
滑动 Tabs 组件时,上方标签不能同步更新,在下方内容完全切换后才会闪动跳转,产生闪屏问题。

1.@Entry
2.@Component
3.struct TabsError {
4. @State currentIndex: number = 0;
5. @State animationDuration: number = 300;
6. @State indicatorLeftMargin: number = 0;
7. @State indicatorWidth: number = 0;
8. private textInfos: [number, number][] = [];
9. private isStartAnimateTo: boolean = false;
10.
11.
12. @Builder
13. tabBuilder(index: number, name: string) {
14. Column() {
15. Text(name)
16. .fontSize(16)
17. .fontColor(this.currentIndex === index ? $r('sys.color.brand') : $r('sys.color.ohos_id_color_text_secondary'))
18. .fontWeight(this.currentIndex === index ? 500 : 400)
19. .id(index.toString())
20. .onAreaChange((_oldValue: Area, newValue: Area) => {
21. this.textInfos[index] = [newValue.globalPosition.x as number, newValue.width as number];
22. if (this.currentIndex === index && !this.isStartAnimateTo) {
23. this.indicatorLeftMargin = this.textInfos[index][0];
24. this.indicatorWidth = this.textInfos[index][1];
25. }
26. })
27. }.width('100%')
28. }
29.
30.
31. build() {
32. Stack({ alignContent: Alignment.TopStart }) {
33. Tabs({ barPosition: BarPosition.Start }) {
34. TabContent() {
35. Column()
36. .width('100%')
37. .height('100%')
38. .backgroundColor(Color.Green)
39. .expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM])
40. }
41. .tabBar(this.tabBuilder(0, 'green'))
42. .expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM])
43.
44.
45. TabContent() {
46. Column()
47. .width('100%')
48. .height('100%')
49. .backgroundColor(Color.Blue)
50. .expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM])
51. }
52. .tabBar(this.tabBuilder(1, 'blue'))
53. .expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM])
54.
55.
56. TabContent() {
57. Column()
58. .width('100%')
59. .height('100%')
60. .backgroundColor(Color.Yellow)
61. .expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM])
62. }
63. .tabBar(this.tabBuilder(2, 'yellow'))
64. .expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM])
65.
66.
67. TabContent() {
68. Column()
69. .width('100%')
70. .height('100%')
71. .backgroundColor(Color.Pink)
72. .expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM])
73. }
74. .tabBar(this.tabBuilder(3, 'pink'))
75. .expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM])
76. }
77. .barWidth('100%')
78. .barHeight(56)
79. .width('100%')
80. .backgroundColor('#F1F3F5')
81. .animationDuration(this.animationDuration)
82. .expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM])
83. .onChange((index: number) => {
84. this.currentIndex = index; // 监听索引index的变化,实现页签内容的切换。
85. })
86.
87.
88. Column()
89. .height(2)
90. .borderRadius(1)
91. .width(this.indicatorWidth)
92. .margin({ left: this.indicatorLeftMargin, top: 48 })
93. .backgroundColor($r('sys.color.brand'))
94. }.width('100%')
95. }
96.}
复制代码
可能原因
在 Tabs 左右翻页动画的结束回调中,刷新了选中页面的 index 值。造成当页面左右转场动画结束时,页签栏中 index 对应页签的样式(字体大小、下划线等)立刻发生改变,导致产生闪屏。
解决措施
在左右跟手翻页过程中,通过 TabsAnimationEvent 事件获取手指滑动距离,改变下划线在前后两个子页签之间的位置。在离手触发翻页动画时,一并触发下划线动画,保证下划线与页面左右转场动画同步进行。
1.build() {
2. Stack({ alignContent: Alignment.TopStart }) {
3. Tabs({ barPosition: BarPosition.Start }) {
4. TabContent() {
5. Column()
6. .width('100%')
7. .height('100%')
8. .backgroundColor(Color.Green)
9. .expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM])
10. }
11. .tabBar(this.tabBuilder(0, 'green'))
12. .expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM])
13.
14.
15. TabContent() {
16. Column()
17. .width('100%')
18. .height('100%')
19. .backgroundColor(Color.Blue)
20. .expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM])
21. }
22. .tabBar(this.tabBuilder(1, 'blue'))
23. .expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM])
24.
25.
26. TabContent() {
27. Column()
28. .width('100%')
29. .height('100%')
30. .backgroundColor(Color.Yellow)
31. .expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM])
32. }
33. .tabBar(this.tabBuilder(2, 'yellow'))
34. .expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM])
35.
36.
37. TabContent() {
38. Column()
39. .width('100%')
40. .height('100%')
41. .backgroundColor(Color.Pink)
42. .expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM])
43. }
44. .tabBar(this.tabBuilder(3, 'pink'))
45. .expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM])
46. }
47. .onAreaChange((_oldValue: Area, newValue: Area) => {
48. this.tabsWidth = newValue.width as number;
49. })
50. .barWidth('100%')
51. .barHeight(56)
52. .width('100%')
53. .expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM])
54. .backgroundColor('#F1F3F5')
55. .animationDuration(this.animationDuration)
56. .onChange((index: number) => {
57. this.currentIndex = index; // 监听索引index的变化,实现页签内容的切换。
58. })
59. .onAnimationStart((_index: number, targetIndex: number) => {
60. // 切换动画开始时触发该回调。下划线跟着页面一起滑动,同时宽度渐变。
61. this.currentIndex = targetIndex;
62. this.startAnimateTo(this.animationDuration, this.textInfos[targetIndex][0], this.textInfos[targetIndex][1]);
63. })
64. .onAnimationEnd((index: number, event: TabsAnimationEvent) => {
65. // 切换动画结束时触发该回调。下划线动画停止。
66. let currentIndicatorInfo = this.getCurrentIndicatorInfo(index, event);
67. this.startAnimateTo(0, currentIndicatorInfo.left, currentIndicatorInfo.width);
68. })
69. .onGestureSwipe((index: number, event: TabsAnimationEvent) => {
70. // 在页面跟手滑动过程中,逐帧触发该回调。
71. let currentIndicatorInfo = this.getCurrentIndicatorInfo(index, event);
72. this.currentIndex = currentIndicatorInfo.index;
73. this.indicatorLeftMargin = currentIndicatorInfo.left;
74. this.indicatorWidth = currentIndicatorInfo.width;
75. })
76.
77.
78. Column()
79. .height(2)
80. .borderRadius(1)
81. .width(this.indicatorWidth)
82. .margin({ left: this.indicatorLeftMargin, top: 48 })
83. .backgroundColor($r('sys.color.brand'))
84. }
85. .width('100%')
86.}
复制代码
TabsAnimationEvent 方法如下所示。
1.private getCurrentIndicatorInfo(index: number, event: TabsAnimationEvent): Record<string, number> {
2. let nextIndex = index;
3. if (index > 0 && event.currentOffset > 0) {
4. nextIndex--;
5. } else if (index < 3 && event.currentOffset < 0) {
6. nextIndex++;
7. }
8. let indexInfo = this.textInfos[index];
9. let nextIndexInfo = this.textInfos[nextIndex];
10. let swipeRatio = Math.abs(event.currentOffset / this.tabsWidth);
11. let currentIndex = swipeRatio > 0.5 ? nextIndex : index; // 页面滑动超过一半,tabBar切换到下一页。
12. let currentLeft = indexInfo[0] + (nextIndexInfo[0] - indexInfo[0]) * swipeRatio;
13. let currentWidth = indexInfo[1] + (nextIndexInfo[1] - indexInfo[1]) * swipeRatio;
14. return { 'index': currentIndex, 'left': currentLeft, 'width': currentWidth };
15.}
16.private startAnimateTo(duration: number, leftMargin: number, width: number) {
17. this.isStartAnimateTo = true;
18. animateTo({
19. duration: duration, // 动画时长
20. curve: Curve.Linear, // 动画曲线
21. iterations: 1, // 播放次数
22. playMode: PlayMode.Normal, // 动画模式
23. onFinish: () => {
24. this.isStartAnimateTo = false;
25. console.info('play end');
26. }
27. }, () => {
28. this.indicatorLeftMargin = leftMargin;
29. this.indicatorWidth = width;
30. })
31.}
复制代码
运行效果如下图所示。

本文主要引用参考 HarmonyOS 官方文档
划线
评论
复制
发布于: 刚刚阅读数: 3

李洋-蛟龙腾飞
关注
鸿蒙开发先行者 2024-09-25 加入
还未添加个人简介
评论