ArkUI自定义TabBar组件

news2024/11/25 3:31:41

在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 }
  }

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2223658.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

打包方式-jar和war的区别

1、jar包 JAR包是类的归档文件&#xff0c;与平台无关的文件格式&#xff0c;其实jar包就是java的类进行编译生成的class文件进行打包的压缩包。 JAR以ZIP文件格式为基础&#xff0c;与ZIP不同的是&#xff0c;JAR不仅用于压缩和发布&#xff0c;还用于部署和封装库、组件和插…

数据结构 ——— 数组栈oj题:有效括号

目录 题目要求 代码实现 题目要求 给定一个只包括 (&#xff0c;)&#xff0c;{&#xff0c;}&#xff0c;[&#xff0c;] 的字符串 s &#xff0c;判断字符串是否有效 有效字符串需满足&#xff1a; 左括号必须用相同类型的右括号闭合。 左括号必须以正确的顺序闭合。 每…

【p2p、分布式,区块链笔记 Blockchain】truffle001 以太坊开发框架truffle初步实践

以下是通过truffle框架将智能合约部署到Ganache的步骤 Truffle简介环境准备&#xff1a;智能合约 编写 & 编译部署合约本地服务器ganache配置网络配置部署合约: 运行Truffle迁移&#xff08;部署&#xff09;:与智能合约交互: 以下是通过truffle框架将智能合约部署到Ganach…

kaggle在线训练深度学习模型

kaggle https://www.kaggle.com/ code 通过jupyter notebook上传代码&#xff0c;执行训练 dataset 支持手动上传本地资源文件到input /kaggle/input&#xff0c;dataset通过input访问&#xff0c;input目录是只读的 /kaggle/working&#xff0c;保存文件到working&#…

网页中的滚动穿透

滚动穿透的现象 在开发中我们可以观察到这么一种现象&#xff0c;当用户在屏幕上某个可滚动区域滚动时&#xff0c;如果滚动到当前区域滚动边界时会带动其他区域进行滚动&#xff0c;直观表现是在进行博客评论时&#xff0c;滑动textarea会带动博客一起滑动&#xff0c;另一种…

智能汽车制造:海康NVR管理平台/工具EasyNVR多品牌NVR管理工具/设备实现无插件视频监控直播方案

一、背景介绍 近年来&#xff0c;随着网络在我国的普及和深化发展&#xff0c;企业的信息化建设不断深入&#xff0c;各行各业都加快了信息网络平台的建设&#xff0c;大多数单位已经或者正在铺设企业内部的计算机局域网。与此同时&#xff0c;网络也成为先进的新兴应用提供了…

【C++】一文带你深入理解C++异常机制

⭐️个人主页&#xff1a;小羊 ⭐️所属专栏&#xff1a;C 很荣幸您能阅读我的文章&#xff0c;诚请评论指点&#xff0c;欢迎欢迎 ~ 目录 前言一、C语言处理错误的方式二、C异常三、异常的使用3.1 异常的抛出和捕获3.2 异常的重新抛出3.3 异常安全3.4 异常规范 四、自定义异…

NVR小程序接入平台/设备EasyNVR多品牌NVR管理工具/设备的多维拓展与灵活应用

在数字化安防时代&#xff0c;NVR批量管理软件/平台EasyNVR作为一种先进的视频监控系统设备&#xff0c;正逐步成为各个领域监控解决方案的首选。NVR批量管理软件/平台EasyNVR作为一款基于端-边-云一体化架构的国标视频融合云平台&#xff0c;凭借其部署简单轻量、功能多样、兼…

使用 pydub 的 AudioSegment 获取音频时长 - python 实现

通过使用 pydub 的 AudioSegment 获取音频时长&#xff0c;音频常用格式如 m4a,wav等。 安装 python 库&#xff1a; pip install pydub 获取 m4a 格式的音频时长代码如下&#xff0c;代码如下&#xff1a; #-*-coding:utf-8-*- # date:2024-10 # Author: DataBall - XIAN #…

sqli-labs靶场安装以及刷题记录-docker

sqli-labs靶场安装以及刷题记录-docker sqli-labs靶场安装-dockersqli-labs靶场刷题less-1 单引号less-2 数字型less-3 单引号括号less-4 双引号括号less-5 单引号布尔盲注less-6 双引号布尔盲注less-7 单引号加括号、输出到文件less-8 单引号布尔盲注less-9 单引号时间盲注les…

【移动应用开发】界面设计(二)实现水果列表页面

续上一篇博客 【移动应用开发】界面设计&#xff08;一&#xff09;实现登录页面-CSDN博客 目录 一、采用ViewBinding实现一个RecyclerView 1.1 在app/build.gradle中添加recyclerview依赖&#xff0c;并打开viewBinding &#xff08;1&#xff09;在app/build.gradle中添加…

基于Python+SQL Server2008实现(GUI)快递管理系统

快递业务管理系统的设计与实现 摘要: 着网络新零售的到来&#xff0c;传统物流在网购的洗礼下迅速蜕变&#xff0c;在这场以互联网为基础的时代变革中&#xff0c;哪家企业能率先转变其工作模式就能最先分得一杯羹&#xff0c;物流管理也不例外。传统的物流管理模式效率低下&a…

Git_GitLab

Git_GitLab 安装 服务器准备 安装包准备 编写安装脚本 初始化 GitLab 服务 启动 GitLab 服务 浏览器访问 GitLab GitLab 创建远程库 IDEA 集成 GitLab 安装 GitLab 插件 设置 GitLab 插件 安装 服务器准备 准备一个系统为 CentOS7 以上版本的服务器&#xff0c;使…

日常记录,使用springboot,vue2,easyexcel使实现字段的匹配导入

目前的需求是数据库字段固定&#xff0c;而excel的字段不固定&#xff0c;需要实现excel导入到一个数据库内。 首先是前端的字段匹配&#xff0c;显示数据库字段和表头字段 读取表头字段&#xff1a; 我这里实现的是监听器导入&#xff0c;需要新建一个listen类。 读Excel …

Docker 搭建mysql

拉取mysql镜像 docker pull mysql # 拉取镜像 [rooteason ~]# docker pull mysql Using default tag: latest latest: Pulling from library/mysql 72a69066d2fe: Pull complete 93619dbc5b36: Pull complete 99da31dd6142: Pull complete 626033c43d70: Pull complete 37d…

大数据治理平台建设规划方案(71页WORD)

随着信息化时代的到来&#xff0c;大数据已成为企业管理和决策的重要基础。然而&#xff0c;大数据的快速增长和复杂性给数据的管理和治理带来了巨大挑战。为了有效应对这些挑战&#xff0c;构建一个高效、稳定的大数据治理平台显得尤为重要。 文档介绍&#xff1a; 该平台旨在…

JS无限执行隔行变色

代码&#xff1a; <!DOCTYPE html> <html lang"zh-CN"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Document</title> <…

【算法优化】混合策略改进的蝴蝶优化算法

摘要 蝴蝶优化算法 (Butterfly Optimization Algorithm, BOA) 是一种新兴的智能优化算法&#xff0c;其灵感来自蝴蝶的觅食行为。本文基于经典BOA&#xff0c;通过引入混合策略进行改进&#xff0c;从而提高其在全局寻优和局部搜索中的性能。实验结果表明&#xff0c;改进的蝴…

混个1024勋章

一眨眼毕业工作已经一年了&#xff0c;偶然进了游戏公司成了一名初级游戏服务器开发。前两天总结的时候&#xff0c;本来以为自己这一年没学到多少东西&#xff0c;但是看看自己的博客其实也有在进步&#xff0c;虽然比不上博客里的众多大佬&#xff0c;但是回头看也算是自己的…

鸿蒙到底是不是纯血?到底能不能走向世界?

关注卢松松&#xff0c;会经常给你分享一些我的经验和观点。 2016年5月鸿蒙系统开始立项。 2018年美国开始经济战争&#xff0c;其中一项就是制裁华为&#xff0c;不让华为用安卓。 2019年8月9日华为正式发布鸿蒙系统。问题就出在这里&#xff0c;大家可以仔细看。 安卓一…