ArkUI自定义TabBar组件

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

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

1、设置tabBar背景颜色,以及点击选中背景样式;

2、自定义导航栏指示器;

3、设置指示器跟随内容视图一起滑动切换效果。

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

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

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

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

arkts 复制代码
@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、指示器距离左边的距离需要动态设置,配上动画,可以实现指示器跟随手指滑动。

arkts 复制代码
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动画开始和动画结束,以及手势监听。

arkts 复制代码
/**
   * 启动动画至指定位置
   *
   * @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方法,给指示器设置动画,动态设置指示器的左边距。

arkts 复制代码
  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监听。

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

3、手势滑动监听

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

arkts 复制代码
 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设计效果。

arkts 复制代码
/**
   * 获取当前指示器信息
   *
   * @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 }
  }
相关推荐
Random_index5 小时前
#Uniapp篇:支持纯血鸿蒙&发布&适配&UIUI
uni-app·harmonyos
比格丽巴格丽抱7 小时前
flutter项目苹果编译运行打包上线
flutter·ios
SoaringHeart7 小时前
Flutter进阶:基于 MLKit 的 OCR 文字识别
前端·flutter
鸿蒙自习室8 小时前
鸿蒙多线程开发——线程间数据通信对象02
ui·harmonyos·鸿蒙
SuperHeroWu710 小时前
【HarmonyOS】鸿蒙应用接入微博分享
华为·harmonyos·鸿蒙·微博·微博分享·微博sdk集成·sdk集成
AiFlutter12 小时前
Flutter通过 Coap发送组播
flutter
zhangjr057513 小时前
【HarmonyOS Next】鸿蒙实用装饰器一览(一)
前端·harmonyos·arkts
诗歌难吟46420 小时前
初识ArkUI
harmonyos
SameX20 小时前
HarmonyOS Next 设备安全特性深度剖析学习
harmonyos
郭梧悠21 小时前
HarmonyOS(57) UI性能优化
ui·性能优化·harmonyos