HarmonyOS 开发实战——ArkUII自定义TabBar组件

468 篇文章 4 订阅
408 篇文章 0 订阅

📚往期笔录记录🔖:

🔖鸿蒙(HarmonyOS)北向开发知识点记录~
🔖鸿蒙(OpenHarmony)南向开发保姆级知识点汇总~
🔖鸿蒙应用开发与鸿蒙系统开发哪个更有前景?
🔖嵌入式开发适不适合做鸿蒙南向开发?看完这篇你就了解了~
🔖对于大前端开发来说,转鸿蒙开发究竟是福还是祸?
🔖鸿蒙岗位需求突增!移动端、PC端、IoT到底该怎么选?
🔖记录一场鸿蒙开发岗位面试经历~
🔖持续更新中……


在ArkUI中的Tabs,通过页签进行内容视图切换的容器组件,每个页签对应一个内容视图。其中内容是图TabContent作为Tabs的自组件,通过给TabContent设置tabBar属性来自定义导航栏样式。现在我们就根据UI设计的效果图来实现下图效果:

根据上图分析可知,要实现以上效果需要下面这几步:

1、设置tabBar背景颜色,以及点击选中背景样式;
2、自定义导航栏指示器;
3、设置指示器跟随内容视图一起滑动切换效果。

设置tabBar背景颜色以及点击选中背景样式

1、首先我们需要使用@Builder修饰方法来表示这是一个自定义组件;

2、根据用户点击的tab索引和当前索引来设置背景图片和背景颜色,这里需要注意的是设置背景颜色的时候,注意左上角和右上角是有圆角的,需要根据索引判断是否展示圆角。

3、由于选中样式是带圆角的梯形,所以这里是用来3个不同的梯形切图。

@Builder
tabBuilder(title: string, targetIndex: number, selectImage: ResourceStr) {
  // 创建一个Column布局
  Column() {
    // 创建一个Text组件,显示标题
    Text(title)
      // 根据当前索引和目标索引判断字体颜色
      .fontColor(this.currentIndex === targetIndex ? $r("app.color.text_one") : $r("app.color.text_two"))
      // 设置字体大小为14
      .fontSize(14)
      // 根据当前索引和目标索引判断字体粗细
      .fontWeight(this.currentIndex === targetIndex ? 600 : 400)
  }
  // 设置Column的宽度为100%
  .width('100%')
    // 设置Column的高度为100%
    .height("100%")
    // 设置Column的子组件垂直居中对齐
    .justifyContent(FlexAlign.Center)
    // 根据当前索引和目标索引判断是否设置背景图片
    .backgroundImage(this.currentIndex == targetIndex ? selectImage : null)
    // 设置Column的背景颜色
    .backgroundColor($r("app.color.bg_data_color"))
    // 根据目标索引判断是否需要设置顶部左右圆角
    .borderRadius({ topLeft: targetIndex == 0 ? 8 : 0, topRight: targetIndex == 2 ? 8 : 0 })
    // 设置背景图片填充方式为填充整个容器
    .backgroundImageSize(ImageSize.FILL)
}

自定义导航栏指示器

由于指示器需要跟随内容视图一起滑动切换,所以指示器不能在单个tabBuilder中设置。

1、使用Column组件定义底部指示器,设置一个宽度为文字宽度,高度为3的蓝色指示器;

2、这里的指示器宽度可以动态设置成文字的宽度,也可以直接设置成文字某个固定宽度;

3、指示器距离左边的距离需要动态设置,配上动画,可以实现指示器跟随手指滑动。

Stack() {
        Tabs({ barPosition: BarPosition.Start }) {
          TabContent() {
            this.tripPage()
          }.tabBar(this.tabBuilder("房源", 0, $r("app.media.trip_data_start_bg")))
          .align(Alignment.TopStart).margin({ top: 54 })
          ...
          ...
          ...
          ...
        }
        .backgroundColor($r("app.color.white"))
        .borderRadius(8)
        .barHeight(44)
        .width("93.6%")
        .height(380)
        .onChange((index) => {
          this.currentIndex = index
        })

       //自定义指示器,设置一个宽度为文字宽度,高度为3的蓝色指示器
        Column()
          .width(this.indicatorWidth)
          .height(3)
          .backgroundColor($r("app.color.main_color"))
          .margin({ left: this.indicatorLeftMargin, top: 42 })
          .borderRadius(1)
}

添加指示器动画

要实现指示器跟随手指滑动,切换不同的tab,需要为指示器添加动画,监听Tabs动画开始和动画结束,以及手势监听。

/**
   * 启动动画至指定位置
   *
   * @param duration 动画时长
   * @param leftMargin 动画结束后的左边距
   * @param width 动画结束后的宽度
   */
  private startAnimateTo(duration: number, leftMargin: number, width: number) {
    // 设置动画开始标志为true
    this.isStartAnimateTo = true
    animateTo({
      // 动画时长
      duration: duration, // 动画时长
      // 动画曲线
      curve: Curve.Linear, // 动画曲线
      // 播放次数
      iterations: 1, // 播放次数
      // 动画模式
      playMode: PlayMode.Normal, // 动画模式
      // 动画结束时的回调函数
      onFinish: () => {
        // 将动画开始标志设置为false
        this.isStartAnimateTo = false
        // 输出动画结束信息
        console.info('play end')
      }
    }, () => {
      // 设置指示器的左边距
      this.indicatorLeftMargin = leftMargin
      // 设置指示器的宽度
      this.indicatorWidth = width
    })
  }

1、动画开始的监听

Tab切换动画开始时,动画返回的目标索引设置为当前索引,调用startAnimateTo方法,给指示器设置动画,动态设置指示器的左边距。

  Tabs({ barPosition: BarPosition.Start }) {}
  .onAnimationStart((index: number, targetIndex: number, event: TabsAnimationEvent) => {
          // 切换动画开始时触发该回调。指示器跟着页面一起滑动。
          this.currentIndex = targetIndex
          this.startAnimateTo(this.animationDuration, this.textInfos[targetIndex][0], this.textInfos[targetIndex][1])
    })

2、动画结束的监听

tab切换动画结束时,回触发onAnimationEnd监听。

  Tabs({ barPosition: BarPosition.Start }) {}
  .onAnimationEnd((index: number, event: TabsAnimationEvent) => {
          // 切换动画结束时触发该回调。指示器动画停止。
          let currentIndicatorInfo = this.getCurrentIndicatorInfo(index, event)
          this.startAnimateTo(0, currentIndicatorInfo.left, currentIndicatorInfo.width)
   })

3、手势滑动监听

在页面跟手滑动过程中,逐帧触发该回调。

 Tabs({ barPosition: BarPosition.Start }) {}
.onGestureSwipe((index: number, event: TabsAnimationEvent) => {
          // 在页面跟手滑动过程中,逐帧触发该回调。
          let currentIndicatorInfo = this.getCurrentIndicatorInfo(index, event)
          //设置当前索引
          this.currentIndex = currentIndicatorInfo.index
          //设置指示器距离左边间距
          this.indicatorLeftMargin = currentIndicatorInfo.left
          //指示器宽度设置
          this.indicatorWidth = currentIndicatorInfo.width
 })

封装获取指示器信息方法,返回指示器的索引,左边距和指示器宽度,在手势滑动监听中调用该方法,可以动态获取指示器的左边距,配合动画,可以实现指示器跟随手势滑动。从而实现UI设计效果。

/**
   * 获取当前指示器信息
   *
   * @param index 当前索引
   * @param event Tabs动画事件
   * @returns 包含指示器索引、左边距和宽度的对象
   */
  private getCurrentIndicatorInfo(index: number, event: TabsAnimationEvent): Record<string, number> {
    // 当前Tab的索引
    let nextIndex = index

    // 如果当前索引大于0且滑动偏移量大于0,表示向左滑动,将nextIndex减1
    if (index > 0 && event.currentOffset > 0) {
      nextIndex--
    }
    // 如果当前索引小于3且滑动偏移量小于0,表示向右滑动,将nextIndex加1
    else if (index < 3 && event.currentOffset < 0) {
      nextIndex++
    }

    // 获取当前索引对应的Tab信息
    let indexInfo = this.textInfos[index]
    // 获取nextIndex对应的Tab信息
    let nextIndexInfo = this.textInfos[nextIndex]

    // 计算滑动比例
    let swipeRatio = Math.abs(event.currentOffset / this.tabsWidth)

    // 如果滑动比例大于0.5,则将currentIndex设为nextIndex,表示切换到下一页的tabBar
    // 页面滑动超过一半,tabBar切换到下一页。
    let currentIndex = swipeRatio > 0.5 ? nextIndex : index

    // 根据滑动比例计算当前Tab的左边距
    let currentLeft = indexInfo[0] + (nextIndexInfo[0] - indexInfo[0]) * swipeRatio
    // 根据滑动比例计算当前Tab的宽度
    let currentWidth = indexInfo[1] + (nextIndexInfo[1] - indexInfo[1]) * swipeRatio

    // 返回包含当前Tab索引、左边距和宽度的对象
    return { 'index': currentIndex, 'left': currentLeft, 'width': currentWidth }
  }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值