ArkUI自定义TabBar组件

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

react18中如何实现同步的setState来实现所见即所得的效果

在react项目中&#xff0c;实现添加列表项&#xff0c;最后一项自动显示在可视区域范围&#xff01;&#xff01; 实现效果 代码实现 import { useState, useRef } from "react"; import { flushSync } from "react-dom"; function FlushSyncRef() {con…

关于Pytest fixture,我们了解多少?

关于Pytest fixtures&#xff0c;根据官方文档介绍&#xff1a;fixture用于提供一个固定的基线&#xff0c;使 Cases 可以在此基础上可靠地、重复地执行。 对比 PyUnit 经典的setup/teardown形式&#xff0c;它在以下方面有了明显的改进&#xff1a; fixture拥有一个明确的名称…

Linux 之 fdisk 【磁盘分区管理】

删除分区 1.查看磁盘信息 lsblk 2.删除分区sdb硬盘下的所有分区 # 1 进入d的磁盘分区 fdisk /dev/sdb # 2 输入p查看磁盘的分区信息 # 3 输入d进入删除磁盘分区命令 # 4 选择要删除的分区号 重复3&#xff0c;4 全部删除 # 5 w 保存退出并生效操作信息 &#xff08;输入q…

postman使用——在公司的项目落地回顾总结

背景 使用postman做接口自动化以及有差不多一年了&#xff0c;迭代更新了也差不多一年了&#xff0c;本篇文章主要介绍与总结&#xff1a; 为什么使用postman做自动化如何使用postman做接口自动化实际落地的方案实施postman优势与限制 为什么使用postman做接口自动化 有以下…

ORACLE在企业中的运用及岗位介绍

微思 | Oracle 19C OCP 认证培训 厦门面授班 | 全国直播班 同步上课 课程介绍&#xff1a;Oracle OCP 19C课程介绍 培训讲师—吴振兴 往期考试战报&#xff1a;【ORACLE战报】 OCP 认证 OCP &#xff1a;Oracle 数据库认证专家&#xff08; Oracle Certified Professional…

【Linux系列】在 Linux 中使用 `watch` 命令监控 Docker 容器状态

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

【Linux】僵尸进程和孤儿进程

一、僵尸进程 何为僵尸进程&#xff1f; 在 Unix/Linux 系统中&#xff0c;正常情况下&#xff0c;子进程是通过父进程创建的&#xff0c;且两者的运行是相互独立的&#xff0c;父进程永远无法预测子进程到底什么时候结束。当一个进程调用 exit 命令结束自己的生命时&#xff…

FineReport 全局参数

全局参数与模板参数的区别如下&#xff1a; 1&#xff09;全局参数&#xff1a;当前工程下的所有模板都可以使用。 2&#xff09;模板参数&#xff1a;只有当前模板才可以使用 注&#xff1a;全局参数 area 并不是在当前模板下创建的&#xff0c;但是可以在模板中直接调用 全…

C++ 十进制数转换成7进制字符串

题目要求&#xff1a; 给定一个整数 num&#xff0c;将其转化为 7 进制&#xff0c;并以字符串形式输出。 C源码&#xff1a; #include "stdafx.h" #include <String> using namespace std;string convertToBase7(int num) {int tempNum num;char t;string…

WGCLOUD可以监控GPU吗

可以的 采集主机GPU信息功能&#xff0c;是WGCLOUD v3.5.5新增的一个功能模块&#xff0c;所以需要升级到v3.5.5或者以上版本 我们在主机管理的列表页面&#xff0c;点击【查看更多】->【扩展监控】按钮&#xff0c;就可以看到该主机的GPU信息 agent每间隔10分钟就会采集一…

DES对称加密算法

DES&#xff08;Data Encryption Standard&#xff0c;数据加密标准&#xff09;是一种对称加密算法。 算法概述 加密类型&#xff1a;对称加密&#xff08;同一密钥用于加密和解密&#xff09;。密钥长度&#xff1a;64位&#xff08;8字节&#xff09;&#xff0c;其中有效…

基于SSM网络在线考试系统的设计

管理员账户功能包括&#xff1a;系统首页&#xff0c;个人中心&#xff0c;学生管理&#xff0c;在线考试管理&#xff0c;试题管理&#xff0c;考试管理&#xff0c;系统管理 前台账号功能包括&#xff1a;系统首页&#xff0c;个人中心&#xff0c;在线考试&#xff0c;公告信…

最新物流行业CRM系统应用数字化解决方案

因势利导 ——全球化物流的挑战与机遇 在全球经济一体化与互联网技术快速发展的双重驱动下,物流行业正经历着前所未有的变革时期。这一变革不仅影响 着行业的发展模式,还对运营效率和客户体验提出了新的要求。 随着市场需求的不断演变,物流行业已呈现出多元化和专业 化并行的发…

OpenCV双目视觉三角测量代码实现C++

在双目视觉系统中&#xff0c;三角测量是一种基于几何原理的三维重建技术&#xff0c;通过分析同一场景在两个不同视角下的二维图像来确定物体的三维坐标。这种方法的核心在于利用摄像机的内参和相对位姿&#xff08;由旋转矩阵和平移向量描述&#xff09;&#xff0c;将图像中…

数据科学家必须掌握的12个Python功能

Python 已经成为数据科学家的必选语言&#xff0c;从数据处理到机器学习&#xff0c;它几乎无所不能。本文将探讨一些Python特性&#xff0c;这些特性不仅能帮助你编写更高效、更易读、更易维护的代码&#xff0c;还特别适合数据科学的需求&#xff0c;使你的代码简洁且优雅。 …

mysql8 使用idb文件实现数据备份

文章目录 1.备份脚本示例2.设置 Cron 任务3. 数据恢复 本文档只是为了留档方便以后工作运维&#xff0c;或者给同事分享文档内容比较简陋命令也不是特别全&#xff0c;不适合小白观看&#xff0c;如有不懂可以私信&#xff0c;上班期间都是在得 需求&#xff0c;在离线情况下实…

【Linux】————磁盘与文件系统

作者主页&#xff1a; 作者主页 本篇博客专栏&#xff1a;Linux 创作时间 &#xff1a;2024年10月17日 一、磁盘的物理结构 磁盘的物理结构如图所示&#xff1a; 其中具体的物理存储结构如下&#xff1a; 磁盘中存储的基本单位为扇区&#xff0c;一个扇区的大小一般为512字…

研发运营一体化(DevOps)能力成熟度模型

目录 应用设计 安全风险管理 技术运 持续交付 敏捷开发管理 基于微服务的端到端持续交付流水线案例 应用设计 安全风险管理 技术运 持续交付

14 django管理系统 - 注册与登录 - 注销

首先先创建注销的入口&#xff0c;在base.html中修改 <ul class"nav navbar-nav navbar-right"><li><a href"/account/login/">登录</a></li><li><a href"/account/register/">注册</a></l…

2024互联网大厂营收排名:京东/阿里/华为前三,超多技术岗都在热招!

2024年已经过去一大半&#xff0c;各大互联网大厂的竞争如火如荼&#xff0c;营收都取得了不俗的成绩&#xff0c;京东、阿里、华为分别占领前三&#xff01; 第四第五名则为华为和拼多多。 根据排行榜里的公司名单&#xff0c;小码特意为大家整理了一批各大厂的招聘岗位。 阿…