目标:实现一个仿微信底部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设置,给出一段伪代码参考
arduino
//if (选中页==index){
// if(目标页==index){
// 认为是没有滑动,显示正常选中状态 即 透明度=1
// }else{
// 滑动中,选中页的图片透明度 变化范围是[1-0]
// }
// }else{
// if(目标页==index){
// 滑动中,目标页的图片透明度 变化范围是[0-1]
// }else{
// 不是目标页 也不是当前页 其他页 选中状态的图片透明度 = 0
// }
// }
实现代码:
kotlin
import { getScreenWidth } from '../utils/DisplayUtil';
import Logger from '../utils/Logger'
@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.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切换页签不回调这个函数,滑动切换还是会回调这个函数的。