鸿蒙ArkUI布局与样式进阶(十二)——自定义TabBar + class类机制全解析(含手机商城底部导航案例)

🚀在鸿蒙应用开发中,底部导航栏 TabBar 是使用频率最高的组件之一。

很多人只用默认样式,其实 ArkUI 提供了更强的定制能力------我们可以完全自定义样式、选中状态、甚至中间凸起的特殊按钮。

本篇就带你从「官方结构」到「自定义高亮控制」「class 类机制」的完整实现,一步步写出像小米商城一样精致的底部导航栏。

目录

[🧩一、TabBar 的基础逻辑与结构](#🧩一、TabBar 的基础逻辑与结构)

[🎨二、自定义 TabBar 的核心思路](#🎨二、自定义 TabBar 的核心思路)

[🔧1️⃣ 定义状态与构建函数](#🔧1️⃣ 定义状态与构建函数)

[⚙️三、Tabs 事件监听与状态更新](#⚙️三、Tabs 事件监听与状态更新)

💎四、手机商城式底部导航实战:中间凸起特效

[🧠五、进阶拓展:TabBar 动效与图标联动](#🧠五、进阶拓展:TabBar 动效与图标联动)

[🧱六、TypeScript 中的 class 类机制详解](#🧱六、TypeScript 中的 class 类机制详解)

一.类的字段与初始化

[例 1:Person 类](#例 1:Person 类)

[例 2:Cat 类](#例 2:Cat 类)

二.类的构造函数

当不同实例需要不同字段初始值时,通过构造函数实现初始化。

构造函数基础语法:

[例:Food 类(直接传参)](#例:Food 类(直接传参))

[例:Food 类(结合接口传参)](#例:Food 类(结合接口传参))

三.类的方法定义

方法定义语法:

[例:Person 类的 sayHi 方法](#例:Person 类的 sayHi 方法)

[❓七、常见问题 Q&A](#❓七、常见问题 Q&A)

🏁结语

🧩一、TabBar 的基础逻辑与结构

在 ArkUI 中,Tabs 与 TabContent 组件组合使用,就能实现基础的多页切换。

简单结构如下👇:

TypeScript 复制代码
Tabs() {
  TabContent() {
    Text('首页内容')
  }.tabBar('首页')

  TabContent() {
    Text('购物车内容')
  }.tabBar('购物车')
}

✅ 每个 TabContent 对应一个页面。

✅ .tabBar() 定义页签标题或自定义内容。

🎨二、自定义 TabBar 的核心思路

默认 TabBar 的样式往往不能满足需求,比如想让选中状态显示红色、图标变化、或添加动画效果。

这时我们可以借助 @Builder 来自定义 TabBar 外观。

🔧1️⃣ 定义状态与构建函数
TypeScript 复制代码
@State selectedIndex: number = 0

@Builder
myBuilder(itemIndex: number, title: string, img: ResourceStr, selImg: ResourceStr) {
  Column() {
    // 图标根据选中状态切换
    Image(this.selectedIndex === itemIndex ? selImg : img)
      .width(30)
      .height(30)
      .opacity(this.selectedIndex === itemIndex ? 1 : 0.7)
    // 文字颜色动态变化
    Text(title)
      .fontColor(this.selectedIndex === itemIndex ? Color.Red : Color.Black)
      .fontSize(12)
  }
}

💡关键点:

• 通过 @State 管理当前激活索引

• 使用三元表达式控制图标和颜色

结合 .opacity() 可增强点击的动效感

⚙️三、Tabs 事件监听与状态更新

要让切换真正起作用,必须监听 Tabs 的切换事件。

|-----------------|-------------------|---------------|
| 事件 | 说明 | 参数 |
| onChange() | 页面切换后触发(滑动、点击均触发) | index(当前页索引) |
| onTabBarClick() | 点击页签时触发 | index(被点击页索引) |

示例:

TypeScript 复制代码
Tabs() {
  TabContent() {
    Text('购物车内容')
  }
  .tabBar(this.myBuilder(2, '购物车', $r('app.media.ic_tabbar_icon_2'), $r('app.media.ic_tabbar_2_selected')))
  .onChange((index: number) => {
    console.log('当前激活索引:', index)
    this.selectedIndex = index
  })
}

✅ onChange 更适合逻辑绑定,比如刷新数据或更新选中态;

✅ onTabBarClick 可实现拦截操作,如未登录提示跳转。

💎四、手机商城式底部导航实战:中间凸起特效

现在我们来实现一个类似「手机商城」风格的底部导航栏。

中间有一个凸起的活动按钮,左右两边是常规页面。

TypeScript 复制代码
@State selectedIndex: number = 0

@Builder
CenterBuilder() {
  // 中间凸起的图标
  Image($r('app.media.ic_reuse_02'))
    .width(40)
    .height(40)
    .margin({ bottom: 10 }) // 向上凸起效果
}

.build() {
  TabBar.barPosition(BarPosition.End)
  
  TabContent() {
    Text('首页内容')
      .tabBar(this.myBuilder(0, '首页', $r('app.media.ic_tabbar_icon_1'), $r('app.media.ic_tabbar_1_selected')))
  }

  TabContent() {
    Text('分类内容')
      .tabBar(this.myBuilder(1, '分类', $r('app.media.ic_tabbar_icon_4'), $r('app.media.ic_tabbar_4_selected')))
  }

  // 🔥中间特殊按钮
  TabContent() {
    Text('活动内容')
      .tabBar(this.centerBuilder())
  }

  TabContent() {
    Text('购物车内容')
      .tabBar(this.myBuilder(2, '购物车', $r('app.media.ic_tabbar_icon_2'), $r('app.media.ic_tabbar_2_selected')))
  }

  TabContent() {
    Text('我的内容')
      .tabBar(this.myBuilder(3, '我的', $r('app.media.ic_tabbar_icon_3'), $r('app.media.ic_tabbar_3_selected')))
      .onChange((index: number) => {
        this.selectedIndex = index
        console.log('当前激活页:', index)
      })
  }
}

🔍细节说明:

• 中间按钮通过 .margin({ bottom: 10 }) 上移实现"凸起"效果。

• 选中态逻辑与普通页一致,只是视觉层面差异。

• 这种结构常见于商城、短视频、社交类App中。

🧠五、进阶拓展:TabBar 动效与图标联动

如果希望实现更流畅的动效,比如点击时放大图标或添加淡入淡出效果,可结合 animateTo() 或 TransitionEffect:

TypeScript 复制代码
Image(this.selectedIndex === itemIndex ? selImg : img)
  .width(30)
  .height(30)
  .transition(TransitionEffect.OPACITY) // 淡入淡出
  .scale(this.selectedIndex === itemIndex ? 1.2 : 1.0)
  .animation({ duration: 150 }) // 微动效

💡体验优化技巧:

• scale + animation:制造点击反馈感
• TransitionEffect:在状态切换时过渡更柔和
• 让TabBar不再"死板",而是具备品牌特征

🧱六、TypeScript 中的 class 类机制详解

在 ArkTS 中,class 是封装数据与逻辑的重要结构。

✅基础语法

TypeScript 复制代码
class Person {
  name: string
  age: number

  constructor(name: string, age: number) {
    this.name = name
    this.age = age
  }

  sayHello() {
    console.log(`你好,我是 ${this.name},今年 ${this.age} 岁`)
  }
}

const p = new Person('小鸿', 18)
p.sayHello()

✨constructor() 用于初始化对象属性,

this 关键字指向当前实例。

🌟继承机制

继承能让我们在组件逻辑上实现"模板 + 扩展"的效果。

TypeScript 复制代码
class Animal {
  constructor(public name: string) {}
  speak() {
    console.log(`${this.name} 在发声`)
  }
}

class Dog extends Animal {
  constructor(name: string, public breed: string) {
    super(name)
  }
  speak() {
    console.log(`${this.name}(${this.breed})在汪汪叫!`)
  }
}

const myDog = new Dog('旺财', '柴犬')
myDog.speak()

✅ extends:继承父类

✅ super():调用父类构造函数或方法

✅ override:可以重写父类方法,灵活实现多态行为

一.类的字段与初始化

  • 字段定义规则
    • 字段名:类型 = 初始值
    • 字段名?: 类型 (可选,可选字段访问时需用链式操作符避免报错)

小例:

例 1:Person 类
TypeScript 复制代码
class Person {
  name: string = 'jack'
  food?: string
}
const p = new Person()
p.name = 'tom'
console.log(p.name)
console.log('字符串长度', p.food?.length) // 可选字段用链式操作符,找不到不报错
例 2:Cat 类
TypeScript 复制代码
class Cat {
  name: string = 'Tom'
  food?: string
}
// 基于类创建对象
let p: Cat = new Cat()
console.log('姓名长度:', p.name.length)
console.log('食物长度:', p.food?.length)
p.food = '小黄鱼'

二.类的构造函数

当不同实例需要不同字段初始值时,通过构造函数实现初始化。
构造函数基础语法:
TypeScript 复制代码
class 类名 {
  字段A: 类型
  字段B: 类型
  constructor(参数...) {
    // 通过new实例化时调用构造函数
    // 通过this获取实例对象,给字段赋值
    this.字段A = 参数
    this.字段B = 参数
  }
}
const 实例1 = new 类名(参数...)
const 实例2 = new 类名(参数...)
例:Food 类(直接传参)
TypeScript 复制代码
class Food {
  name: string
  price: number
  constructor(name: string, price: number) {
    this.name = name
    this.price = price
  }
}
const f1 = new Food('西红柿鸡蛋', 15)
const f2 = new Food('土豆炖鸡块', 24) // 不同实例有不同初始值
例:Food 类(结合接口传参)
TypeScript 复制代码
interface IFood {
  name: string
  price: number // 修正原笔记中price类型错误
  desc: string
}
class Food {
  constructor(paramsObj: IFood) {
    this.name = paramsObj.name
    this.price = paramsObj.price // 修正原笔记中赋值错误
    this.desc = paramsObj.desc
  }
}
let p1: Food = new Food({
  name: '西兰花',
  desc: '好吃',
  price: 20
})
let p2: Food = new Food({
  name: '黄瓜炒鸡蛋',
  desc: '清爽',
  price: 12
})
console.log('名称', p1.name)
console.log('名称', p2.name)

三.类的方法定义

类中可定义方法,用于封装逻辑,通过this可访问实例对象的字段。

方法定义语法:
TypeScript 复制代码
class 类名 {
  方法名(参数...): 返回值类型 {
    // 逻辑...
    // 通过this获取实例对象
  }
}
例:Person 类的 sayHi 方法
TypeScript 复制代码
class Person {
  name: string
  constructor(name: string) {
    this.name = name
  }
  sayHi(who: string) {
    console.log(`你好${who},我是${this.name}`)
  }
}
const p: Person = new Person('jack')
p.sayHi('rose')

❓七、常见问题 Q&A

Q1:为什么点击 Tab 图标没切换?

👉 确保 onChange() 正确绑定并更新 selectedIndex。

Q2:多个 Tab 状态不一致?

👉 确保每个 Tab 调用的 builder 传入的 itemIndex 唯一。

Q3:能否动态生成 Tabs?

👉 可以,循环数组生成 TabContent() 即可,非常适合后台配置导航的场景。

🏁结语

通过本篇内容,我们实现了从 默认 TabBar → 自定义样式 → 动效优化 → class 封装逻辑 的完整进阶。

这不仅是界面的美化,更是状态管理、组件封装与逻辑分层的实践。

未来你完全可以在此基础上扩展出:

• 动态加载图标资源

• 主题适配模式(亮/暗)

• 页面生命周期联动动画

💡掌握了 TabBar 的自定义技巧,也就掌握了 ArkUI 的一半交互核心!

恭喜您,迈出了鸿蒙应用开发坚实的一步!🎉

🤔 接下来,我将继续为大家更新鸿蒙开发的相关知识点,如果喜欢,记得关注驻波。

✅ 如果你觉得这篇 ArkUI 学习笔记对你有帮助,记得 点赞 + 收藏,关注我获取更多鸿蒙开发干货!📝 有任何问题欢迎在评论区留言,我们一起交流学习!

过往的博客欢迎各位大佬捧场。
从 0 到 1:用 ArkUI 写出你的第一个鸿蒙 App 界面 -CSDN博客

鸿蒙ArkUI布局与样式进阶(二)------ SVG 设计图案、Padding、Margin、Border、圆角与背景实战-CSDN博客

鸿蒙ArkUI布局与样式进阶(三)------线性布局(Row 与 Column)深度解析与工程实践-CSDN博客

鸿蒙ArkUI布局与样式进阶(四)------从 Column 到 Grid,一篇搞懂常用布局(含实战案例与逐行注释)_arkui gridrow按钮-CSDN博客

鸿蒙ArkUI布局与样式进阶(五)------从状态到界面:ArkUI 的响应式编程核心-CSDN博客

鸿蒙ArkUI布局与样式进阶(六)------ArkUI 支持的各种运算符和语法基础与数组操作-CSDN博客

鸿蒙ArkUI布局与样式进阶(七)------鸿蒙 ArkUI 开发中的条件判断与循环语句全解析(实战 + 思维延伸)-CSDN博客

对harmonyos感兴趣的培养记得关注驻波

那年窗外下的雪.-CSDN博客

相关推荐
马拉萨的春天5 小时前
探索Objective-C中的对象复制:深入理解copy和mutableCopy
开发语言·ios·objective-c
IT_陈寒5 小时前
Python性能优化:5个被低估但效果惊人的内置函数实战解析
前端·人工智能·后端
00后程序员张5 小时前
Fiddler使用教程,全面掌握Fiddler抓包工具的配置方法、代理设置与调试技巧(HTTPHTTPS全解析)
前端·测试工具·ios·小程序·fiddler·uni-app·webview
啊森要自信5 小时前
【MySQL 数据库】使用C语言操作MySQL
linux·c语言·开发语言·数据库·mysql
Rysxt_5 小时前
Electron 与 uni-app 区别教程:如何选择适合你的跨平台开发框架?
javascript·electron·uni-app·跨平台
前端架构师-老李5 小时前
15、Electron专题:使用 electron-store 进行本地数据存储
前端·javascript·electron
小白学大数据5 小时前
双管齐下:结合显式等待与Timeout处理复杂Ajax网页
前端·javascript·ajax
Rysxt_5 小时前
Electron 教程:从背景到 Vue3 桌面应用开发
前端·javascript·electron