写点什么

HarmonyOS 应用闪屏问题性能优化二

  • 2025-06-05
    广东
  • 本文字数:4410 字

    阅读完需:约 14 分钟

动画过程中,Tabs 页签切换场景下的闪屏问题

问题现象

滑动 Tabs 组件时,上方标签不能同步更新,在下方内容完全切换后才会闪动跳转,产生闪屏问题。


1.@Entry2.@Component3.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.  @Builder13.  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 官方文档

用户头像

鸿蒙开发先行者 2024-09-25 加入

还未添加个人简介

评论

发布
暂无评论
HarmonyOS应用闪屏问题性能优化二_李洋-蛟龙腾飞_InfoQ写作社区