写点什么

鸿蒙 Next Tabs 实现底部导航进阶

作者:auhgnixgnahz
  • 2025-06-23
    北京
  • 本文字数:3112 字

    阅读完需:约 10 分钟

目标:实现一个仿微信底部 Tab 标签随页面滑动颜色渐变的效果


最终效果:



实现思路:


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 设置,给出一段伪代码参考


//if (选中页==index){//  if(目标页==index){//    认为是没有滑动,显示正常选中状态 即 透明度=1//  }else{//    滑动中,选中页的图片透明度 变化范围是[1-0]//  }// }else{//    if(目标页==index){//      滑动中,目标页的图片透明度 变化范围是[0-1]//    }else{//      不是目标页 也不是当前页 其他页 选中状态的图片透明度 = 0//    }// }
复制代码


实现代码:


import { getScreenWidth } from '../utils/DisplayUtil';import Logger from '../utils/Logger'
@Entry@ComponentV2struct 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.map_tab_home_sel'), $r('app.media.map_tab_home_nor')) this.tabContentBuilder('消息', 1, $r('app.media.map_tab1_sel'), $r('app.media.map_tab1_nor')) this.tabContentBuilder('同事圈', 2, $r('app.media.map_tab2_sel'), $r('app.media.map_tab2_nor')) this.tabContentBuilder('通讯录', 3, $r('app.media.map_tab3_sel'), $r('app.media.map_tab3_nor')) this.tabContentBuilder('我的', 4, $r('app.media.map_tab4_sel'), $r('app.media.map_tab4_nor'))
} .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 触发切换滑动开始的回调 Logger.info("======onAnimationStart",'index:'+index+'targetIndex:'+targetIndex) this.currentIndex = targetIndex this.targetIndex=targetIndex this.currentOpacity =1 this.targetOpacity = 0 }) .onGestureSwipe((index: number, event: TabsAnimationEvent) =>{ //左划小于0 右滑大于0 let currentOffset = event.currentOffset if (currentOffset>0&&index==0) { return }else if(currentOffset>0){ this.targetIndex=index-1 }else if (currentOffset<0&& index<5){ this.targetIndex=index+1 }else { return }
// 获取屏幕的宽vp 根据手指左右滑动的距离除以屏幕的宽 计算tab图片的透明度 let percent = Math.abs(currentOffset)/(getScreenWidth()*3/4) if (percent>1) { percent=1 } if (percent<0) { percent=0 } this.currentOpacity = 1-percent this.targetOpacity =percent; Logger.info("======onGestureSwipe",'index:'+index+'currentOffset:'+event.currentOffset+'percent:'+percent) })
}.hideTitleBar(true).hideToolBar(true) .width('100%') .height('100%') }}
复制代码


###注意:


官方文档中onAnimationStart 这个回调函数下面写了,当 animationDuration 为 0 时动画关闭,不触发该回调。


这个解释是不严谨的,当 animationDuration=0 时,只是点击 tab 切换页签不回调这个函数,滑动切换还是会回调这个函数的。


发布于: 刚刚阅读数: 4
用户头像

auhgnixgnahz

关注

还未添加个人签名 2018-07-10 加入

还未添加个人简介

评论

发布
暂无评论
鸿蒙Next Tabs实现底部导航进阶_鸿蒙Next_auhgnixgnahz_InfoQ写作社区