鸿蒙Tabs实现底部导航渐变效果

2025-06-01  本文已影响0人  胡修波

1.需要用到tabs两个回调函数:

onGestureSwipe(handler: (index: number, event: TabsAnimationEvent) => void)**在页面跟手滑动过程中,逐帧触发该回调
通过这个函数回调,我们可以知道手指滑动的距离,和滑动方向,然后根据滑动距离和屏幕宽度计算一个百分比,用做修改tab的色值透明度
onAnimationStart(handler: (index: number, targetIndex: number, event: TabsAnimationEvent) => void)
通过这个函数,我们可以知道滑动触发切换成功,页面已经切换,这里我们将最终色值属性赋值

2.滑动涉及到当前选中和滑动目标页,因此需要定义两个index作为标记,用作text和image判断是否是当前页面和目标页。

3.tab的设计用stack层叠布局,下面放默认状态的布局,不进行修改,上面叠一个变化的布局,即选中状态的样式,通过改变这个布局的透明度,做到渐变

4.当前选中页tab的透明度和目标页的透明度如何根据index设置,给出一段伪代码参考

@Entry
@ComponentV2
struct MainPage {
  tabsController: TabsController = new TabsController();
  pageInfos: NavPathStack = new NavPathStack()
  beginMoveTime: number = 0;
  @Local currentIndex: number = 0; //当前tab页
  @Local targetIndex: number = 0; //目标tab页
  @Local currentOpacity: number = 1; //当前tab页 tab选中情况下的透明度  变化值1-0
  @Local targetOpacity: number = 0; //目标tab页 tab 将要被选中时的透明度 变化值0-1

  @Builder
  tabBuilder(title: string, index: number, selectedImg: Resource, normalImg: Resource) {
    Column() {
      Stack() {
        Image(normalImg)
          .width(24)
          .height(24)
          .objectFit(ImageFit.Contain)
        Image(selectedImg)
          .width(24)
          .height(24)
          .objectFit(ImageFit.Contain)
          .opacity(this.currentIndex === index ? //如果选择当前的tab 则该图片显示 不透明 如果在移动过程中 target 透明度0-1 当前1-0
            this.targetIndex === index ? 1 : this.currentOpacity : this.targetIndex === index ? this.targetOpacity : 0)
      }

      Stack() {
        Text(title)
          .margin({ top: 4 })
          .fontSize(12)
          .fontColor('#9E9E9E')
        Text(title)
          .margin({ top: 4 })
          .fontSize(12)
          .fontColor('#007AFF')
          .opacity(this.currentIndex === index ?
            this.targetIndex === index ? 1 : this.currentOpacity
            : this.targetIndex === index ? this.targetOpacity : 0)
        //if (选中页==index){
        //  if(目标页==index){
        //    认为是没有滑动,显示正常选中状态 即 透明度=1
        //  }else{
        //    滑动中,选中页的图片透明度 变化范围是[1-0]
        //  }
        // }else{
        //    if(目标页==index){
        //      滑动中,目标页的图片透明度 变化范围是[0-1]
        //    }else{
        //      不是目标页 也不是当前页 其他页 选中状态的图片透明度 = 0
        //    }
        // ]
      }

    }
    .justifyContent(FlexAlign.Center)
    .height(52)
    .width('100%')
    .onClick(() => {
      this.currentIndex = index;
      this.targetIndex = index;
      this.tabsController.changeIndex(this.currentIndex);
    })
  }

  @Builder
  tabContentBuilder(text: string, index: number, selectedImg: Resource, normalImg: Resource) {
    TabContent() {
      Row() {
        Text(text)
          .height(300)
          .fontSize(30)
      }
      .width('100%')
      .justifyContent(FlexAlign.Center)
    }
    .backgroundColor(Color.White)
    .tabBar(this.tabBuilder(text, index, selectedImg, normalImg))
  }

  build() {
    Navigation(this.pageInfos) {
      Tabs({ barPosition: BarPosition.End, controller: this.tabsController }) {
        this.tabContentBuilder('首页', 0, $r('app.media.startIcon'), $r('app.media.startIcon'))
        this.tabContentBuilder('消息', 1, $r('app.media.startIcon'), $r('app.media.startIcon'))
        this.tabContentBuilder('同事圈', 2, $r('app.media.startIcon'), $r('app.media.startIcon'))
        this.tabContentBuilder('通讯录', 3, $r('app.media.startIcon'), $r('app.media.startIcon'))
        this.tabContentBuilder('我的', 4, $r('app.media.startIcon'), $r('app.media.startIcon'))
      }
      .width('100%')
      .backgroundColor('#F3F4F5')
      .barHeight(52)
      .barMode(BarMode.Fixed)
      //tab切换动画时间,例如tab从0-5 直接展示5 不显示中间的滑动过程
      .animationDuration(0)
      // .scrollable(false)  //如果不想让内容滑动,可关闭滑动效果
      .onAnimationStart((index: number, targetIndex: number, event: TabsAnimationEvent) => {
        //content 触发切换滑动开始的回调
        this.currentIndex = targetIndex
        this.targetIndex = targetIndex
        this.currentOpacity = 1
        this.targetOpacity = 0
        console.info("huxiubo","onAnimationStart", `index:${index}, targetIndex:${targetIndex}`)

      })
      .onGestureSwipe((index: number, event: TabsAnimationEvent) => {
        //向左滑动 大于0, 向右滑动 小于0
        let currentOffset = event.currentOffset
        console.log("huxiubo", ` onGestureSwipe currentOffset:${currentOffset}, index: ${index}`)
        if (currentOffset > 0 && index == 0) {
          console.log("huxiubo", "已经处于第一个,向左滑动")
          return
        } else if (currentOffset > 0) {
          this.targetIndex = index - 1
          console.log("huxiubo", `currentOffset >0  onGestureSwipe targetIndex:${this.targetIndex}`)

        } else if (currentOffset < 0 && index < 5) {
          this.targetIndex = index + 1
          console.log("huxiubo", `currentOffset < 0 && index < 5  onGestureSwipe targetIndex:${this.targetIndex}`)

        } else {
          console.log("huxiubo", "最后一个")
          return
        }

        // 获取屏幕的宽vp 根据手指左右滑动的距离除以屏幕的宽 计算tab图片的透明度
        let percent = Math.abs(currentOffset) / (400 * 3 / 4)
        if (percent > 1) {
          percent = 1
        }
        if (percent < 0) {
          percent = 0
        }
        this.currentOpacity = 1 - percent
        this.targetOpacity = percent;
        // console.info("======onGestureSwipe",
        //   'index:' + index + 'currentOffset:' + event.currentOffset + 'percent:' + percent)
      })

    }.hideTitleBar(true).hideToolBar(true)
    .width('100%')
    .height('100%')
  }
}

上一篇 下一篇

猜你喜欢

热点阅读