鸿蒙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%')
}
}