鸿蒙项目开发中,主界面中都会有TabBar,普通的TabBar大家都会使用了,今天来聊聊异于普通样式的TabBar,并添加一些动画效果,更加的美观。
因普通的TabBar样式满足不了开发需求,想要中间添加一个+号会比较困难。基于这一点打算发挥自己创新思维,整体使用Stack()帧布局方式,给TabBar提前预留一个+号的位置,然后使用+号的Image给帧到对应位置,就可以实现了,有想法直接开搞。
@Entry
@Component
struct MainPage {
@State
activeIndex: number = 0;
@State
angleA: number = 0;
@State
translateY: number = 0;
@State
flag: boolean = false;
/**
* 正常Tab 图片 文字 公共管理
* @param title
* @param targetIndex
* @param defaultImg
* @param selectedImg
*/
@Builder
TabBarBuilder(title: string, targetIndex: number, defaultImg: Resource, selectedImg: Resource) {
Column() {
Image(this.activeIndex === targetIndex ? selectedImg : defaultImg)
.size({ width: vp2vp(20), height: vp2vp(20) })
Text(title)
.fontColor(this.activeIndex === targetIndex ? $r('app.color.tab_selected_color') : $r('app.color.tab_default_color'))
.fontSize(vp2vp(12))
.padding({ top: vp2vp(5) })
.lineHeight(vp2vp(12))
}
.width('100%')
.height(vp2vp(50))
.justifyContent(FlexAlign.Center)
.border({ width: { top: 0.5 }, color: $r('app.color.ih_bg_color'), style: BorderStyle.Solid })
.onClick(() => {
this.activeIndex = targetIndex
})
}
dialogController: CustomDialogController = new CustomDialogController({
builder: SignCustomDialogWidget({}),
customStyle: true,
alignment: DialogAlignment.Center
})
build() {
/*****************首页正常 start **************************/
Stack() {
Tabs({
barPosition: BarPosition.End,
index: this.activeIndex
}) {
TabContent() {
HomePage()
}.tabBar(this.TabBarBuilder('首页', 0, $r('app.media.icon_sy_un'), $r('app.media.icon_sy_sel')))
TabContent() {
TaskCenterPage()
}.tabBar(this.TabBarBuilder('任务中心', 1, $r('app.media.icon_rwzx_un'), $r('app.media.icon_rwzx_sel')))
TabContent() { //加号占位 只是占位 不显示
TaskCenterPage()
}.tabBar().onClick(() => {
this.activeIndex = 1;
})
TabContent() {
NotificationPage()
}.tabBar(this.TabBarBuilder('通知通报', 3, $r('app.media.icon_tz_un'), $r('app.media.icon_tz_sel')))
TabContent() {
MinePage()
}.tabBar(this.TabBarBuilder('我的', 4, $r('app.media.icon_my_un'), $r('app.media.icon_my_sel')))
}
.vertical(false)
.onChange((index) => {
if (index === 2) {
this.activeIndex = 1;
} else {
this.activeIndex = index
}
})
.barHeight(vp2vp(80))
Image($r('app.media.icon_add'))
.width('60vp')
.height('60vp')
.margin({ bottom: 40 })
.onClick(() => {
this.flag = !this.flag;
//+号动画更改值
this.change(45)
this.translateChange(-160)
})
.visibility(this.flag === false ? Visibility.Visible : Visibility.Hidden)
/*****************首页正常 end **************************/
/*****************加号弹出 start **************************/
Stack({ alignContent: Alignment.Bottom }) {
Row() {
Image($r('app.media.btn_qiandao'))
.width('80vp')
.height('80vp')
.objectFit(ImageFit.Fill)
.translate({
y: this.translateY
})
.animation({
duration: 1000
})
.onClick(() => {
this.change(0)
this.translateChange(160)
setTimeout(() => {
this.flag = !this.flag;
}, 1100)
if (this.dialogController != undefined) {
this.dialogController.open();
}
})
Image($r('app.media.btn_shijianshangbao'))
.width('80vp')
.height('80vp')
.objectFit(ImageFit.Fill)
.translate({
y: this.translateY-60
})
.animation({
duration: 700
})
.onClick(() => {
this.change(0)
this.translateChange(160)
setTimeout(() => {
this.flag = !this.flag;
}, 1100)
router.pushUrl({ url: RoutePath.SjsbPage })
})
Image($r('app.media.btn_chuzhi'))
.width('80vp')
.height('80vp')
.objectFit(ImageFit.Fill)
.translate({
y: this.translateY-100
})
.animation({
duration: 500
})
.onClick(() => {
this.change(0)
this.translateChange(160)
setTimeout(() => {
this.flag = !this.flag;
}, 1100)
router.pushUrl({ url: RoutePath.CzsbPage })
})
Image($r('app.media.btn_shixiangshangbao'))
.width('80vp')
.height('80vp')
.objectFit(ImageFit.Fill)
.translate({
y: this.translateY-60
})
.animation({
duration: 700
})
.onClick(() => {
this.change(0)
this.translateChange(160)
setTimeout(() => {
this.flag = !this.flag;
}, 1100)
router.pushUrl({ url: RoutePath.SxsbPage })
})
Image($r('app.media.btn_weekly'))
.width('80vp')
.height('80vp')
.objectFit(ImageFit.Fill)
.translate({
y: this.translateY
})
.animation({
duration: 1000
})
.onClick(() => {
this.change(0)
this.translateChange(160)
setTimeout(() => {
this.flag = !this.flag;
}, 1100)
router.pushUrl({ url: RoutePath.WeeklyReportPage })
})
}.justifyContent(FlexAlign.Center).margin({bottom:-60})
Row() {
Image($r('app.media.icon_add'))
.width('60vp')
.height('60vp')
.objectFit(ImageFit.Fill)
.onClick(() => {
this.change(0)
this.translateChange(160)
setTimeout(() => {
this.flag = !this.flag;
}, 1100)
})
.rotate({
x: 0,
y: 0,
z: 1,
angle: this.angleA,
centerX: 30,
centerY: 30,
})
}.padding({ bottom: 40 }).width('80%')
.justifyContent(FlexAlign.Center)
}
.backgroundColor($r('app.color.transparent_bg'))
.width('100%')
.height('100%')
.visibility(this.flag === true ? Visibility.Visible : Visibility.Hidden)
/*****************加号弹出 end **************************/
}.alignContent(Alignment.Bottom)
}
/**
* 旋转
* @param angle
*/
change(angle: number) {
animateTo({
duration: 1000,
iterations: 1,
curve: Curve.Linear
}, () => {
this.angleA = angle
})
}
/**
* 位移
* @param translateY
*/
translateChange(translateY: number) {
animateTo({
duration: 1000,
iterations: 1,
curve: Curve.Friction
}, () => {
this.translateY = translateY
})
}
}
开发完具体使用的过程发现了一个问题,TabBar可以左右切换,+号的位置对应的应该也会有TabContent,我就把+号空白部位的点击给赋值了下标1,这样+号Image下面空白的位置点击会跳转到下标1的TabContent,这样在左右滑动的时候下标为1的TabContent会出现2遍,暂时想不到更好的方案,有想法的小伙伴,可以一起探讨一下!