HarmonyOS应用开发实战-自定义TabBar,并添加动画效果

鸿蒙项目开发中,主界面中都会有TabBar,普通的TabBar大家都会使用了,今天来聊聊异于普通样式的TabBar,并添加一些动画效果,更加的美观。

1.话不多说,先展示样式

2.设计思路

因普通的TabBar样式满足不了开发需求,想要中间添加一个+号会比较困难。基于这一点打算发挥自己创新思维,整体使用Stack()帧布局方式,给TabBar提前预留一个+号的位置,然后使用+号的Image给帧到对应位置,就可以实现了,有想法直接开搞。

3.具体代码

@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
    })
  }
}

4.发现问题

开发完具体使用的过程发现了一个问题,TabBar可以左右切换,+号的位置对应的应该也会有TabContent,我就把+号空白部位的点击给赋值了下标1,这样+号Image下面空白的位置点击会跳转到下标1的TabContent,这样在左右滑动的时候下标为1的TabContent会出现2遍,暂时想不到更好的方案,有想法的小伙伴,可以一起探讨一下!

你可能感兴趣的:(harmonyos,华为)