鸿蒙开发简单实现Tab导航栏动画(点击动画,消除延迟)

 动画实现方式:属性动画、关键帧动画、转场动画

效果

实现代码


@Entry
@Component
struct Index {
  private tabsController: TabsController = new TabsController()
  @State currentTabIndex: number = 0
  @State iconScale: number = 1;   // 默认不缩放,在关键帧中设置缩放大小

  private uiContent: UIContext = this.getUIContext?.();

  @Builder
  tabBar(index: number, label: string, unSelectIcon: ResourceStr, selectIcon: ResourceStr) {
    Column() {
      if (this.currentTabIndex === index) {   // 避免使用三目表达式控制当前index显示的icon,因为只需要为激活状态的图标设置动画
        Image(selectIcon)
          .width(30)
          .height(30)
          .scale({ x: this.iconScale, y: this.iconScale })    // 绑定属性
      } else {
        Image(unSelectIcon)
          .width(30)
          .height(30)
      }
      Text(label).fontSize(14).fontColor(this.currentTabIndex === index ? "#9c5ff3" : "#8a8a8a")
        .padding({ top: 6 })
    }
  }

  build() {
    Column() {
      Tabs({ controller: this.tabsController }) {
        TabContent() {
          Text("首页")
            .fontSize(30)
            .fontWeight(FontWeight.Bold)
        }.tabBar(this.tabBar(0, "首页", $r("app.media.ic_home"), $r("app.media.ic_home_")))

        TabContent() {
          Text("VIP")
            .fontSize(30)
            .fontWeight(FontWeight.Bold)
        }.tabBar(this.tabBar(1, "VIP", $r("app.media.ic_vip"), $r("app.media.ic_vip_")))

        TabContent() {
          Text("我的")
            .fontSize(30)
            .fontWeight(FontWeight.Bold)
        }.tabBar(this.tabBar(2, "我的", $r("app.media.ic_mine"), $r("app.media.ic_mine_")))
      }
      .onContentWillChange((currentIndex: number, comeIndex: number) => {
        this.currentTabIndex = comeIndex
        if (!this.uiContent) {
          this.iconScale = 1
        } else {
          this.uiContent.keyframeAnimateTo( //设置关键帧动画
            { iterations: 1 }, // 关键帧播放次数为一次
            [{
              duration: 200,
              event: () => {
                this.iconScale = 0.7  // 先缩放到0.7倍
              }
            }, {
            duration: 150,
            curve: Curve.EaseOut,
            event: () => {
              this.iconScale = 1    // 恢复正常大小
            }
          }])
        }
        return true
      })
      // .onChange((index:number)=>{
      //   this.currentTabIndex = index
      // })     // 避免在onChange中切换index,onChange是切换tebContent切换完成后触发的回调
      .barPosition(BarPosition.End)
      .scrollable(false)
    }
  }
}

原理【消除延迟】

要实现动画对于点击没有延迟,需要先消除tab点击的延迟效果。Tabs的tabbar通过CustomBuilder自定义底部tabBar导航栏的时候,点击tab会有TabContent切换完成后才对tabbar切换,这是因为CustomBuilder中切换显示图标是根据tabbar的index改变来切换的,onchange里面对index进行修改导致切换状态的时间和点击tab的时间对应不上,所以看上去有延迟的效果,onchange回调是在tab已经切换之后才触发的,对于这个问题,可以将实现变更index的代码在onContentWillChange中实现即可做到点击和切换同时响应【官方建议在onAnimationStart中监听并刷新当前索引】。

onChange的效果(明显延迟)

onContentWillChange效果

onContentWillChange效果

另外,如果不使用CustomBuilder,在默认样式的tabar中存在的延迟效果,可以通SubTabBarStyle来实现消除点击后tabContent和tabBar之间切换动画的延迟

SubTabBarStyle效果

代码:

TabContent() {
   Column() {
       Text("我的").fontSize(30).fontWeight(FontWeight.Bold)
   }
    .height("100%").width("100%").justifyContent(FlexAlign.End).padding({ bottom: 40 })
}.tabBar(new SubTabBarStyle("我的"))

原理【关键帧动画

同过关键帧动画实现tabbar切换后将图标的大小缩放到指定倍数(关键帧)又后回到原始大小,效果上呈现出点击回弹效果。

参考:鸿蒙关于关键帧动画的文档:【关键帧动画 (keyframeAnimateTo)

扩展(实现更复杂的动画样式)

通过转场动画和其它动画接口实现更复杂的动画效果

添加转场动画

private appearEffect : TransitionEffect = TransitionEffect.OPACITY.animation({
      curve: curves.springMotion(0.6, 0.8)    // 默认透明度转场效果,指定springMotion(0.6, 0.8)曲线
    })
      .combine(TransitionEffect.rotate({ angle: 90 }))// 添加平移转场效果
      .combine(TransitionEffect.translate({ y: 10 }).animation({ curve: curves.springMotion() }))// 添加move转场效果

为image组件绑定动画

if (this.currentTabIndex === index) {   // 避免使用三目表达式控制当前index显示的icon,因为只需要为激活状态的图标设置动画
        Image(selectIcon)
          .width(30)
          .height(30)
          .transition(this.appearEffect)   // 添加转场效果
          .scale({ x: this.iconScale, y: this.iconScale })    // 绑定属性
      } else {
        Image(unSelectIcon)
          .width(30)
          .transition(this.appearEffect)      // 同时为未激活icon添加转场效果
          .height(30)
      }

效果(消失和出现)

完整代码https://gitcode.com/mtyee/tabAnimation