《HarmonyOS底部页签-沉浸光感组件实战》基础入门:认识HdsTabs容器与核心配置

问题从哪来

HarmonyOS NEXT 的底部页签在 API 12 以前主要靠 Tabs 组件硬撑。开发者自己调样式、自己管交互,每个项目写出来的底部栏长得都不一样。更麻烦的是页签和内容的层级关系------默认情况下列栏和内容区是上下排列的,想做出"悬浮""叠加""光感"效果,得自己叠层、算位置、处理事件穿透。这套逻辑本身不难,但每个项目都重写一遍就是纯体力活。

UI Design Kit 里的 HdsTabs 容器就是来解决这个问题的。它把底部页签常见的几种交互样式直接封装成了配置属性,不用再手动调层级。这篇文章只讲最基础的部分:怎么创建 HdsTabs、怎么让 tabBar 悬浮在内容之上、以及两个最核心的配置项 barOverlapbarPosition 到底是干什么的。

HdsTabs 解决了什么问题

普通 Tabs 组件布局是这样的:

复制代码
+-------------------+
|   TabContent 区域   |
+-------------------+
|   tabBar 栏        |
+-------------------+

栏和内容区各自占位,互不重叠。这本身没问题,但一旦设计师要求"底栏要半透明悬浮在内容上""内容可以滑到底栏下面""底栏要有玻璃模糊效果",普通 Tabs 就有点吃力------需要自己写 positionzIndex背景模糊,而且还要处理好内容区的内边距,防止内容被底栏挡住。

HdsTabs 容器把"叠加"这个行为变成了一个开关:barOverlap = true。打开后,tabBar 会悬浮在 TabContent 之上,不需要额外写任何布局代码。

barPosition 控制栏的位置,BarPosition.End 即底部。对于底部页签场景,这个值是固定的。

环境说明

text 复制代码
DevEco Studio 版本:DevEco Studio 6.1.0 及以上  
HarmonyOS SDK 版本:HarmonyOS 6.1.0(23) 及以上  
目标设备:手机  

核心实现

第一步:导入模块

从 API 22 开始,HdsTabs 的导入方式有了变化。如果你用的是 HarmonyOS 6.1.0(23) 及以上版本,不需要手动导入 HdsTabsAttribute,直接导入组件和控制器就行。

typescript 复制代码
import { HdsTabs, HdsTabsController } from '@kit.UIDesignKit';

如果你的 SDK 版本较旧(比如 6.0.0),需要额外导入属性类:

typescript 复制代码
import { HdsTabs, HdsTabsAttribute, HdsTabsController } from '@kit.UIDesignKit';

这一点官方文档写得比较收敛------实际开发中,建议直接用最新版本,少一个导入就少一个出错点。

第二步:创建基础结构

下面是完整的 HdsTabs 容器示例,包含两个 TabContent 页签,并且开启了叠加效果。

typescript 复制代码
@Entry
@Component
struct Index {
  private controller: HdsTabsController = new HdsTabsController()

  build() {
    Column() {
      HdsTabs({ controller: this.controller }) {
        // 页签1
        TabContent() {
          Column()
            .width('100%')
            .height('100%')
            .backgroundColor('#FFF3E0')
            .justifyContent(FlexAlign.Center)
            .alignItems(HorizontalAlign.Center)
            .children({
              Text('首页内容')
                .fontSize(20)
                .fontColor('#333333')
            })
        }
        .tabBar({ icon: $r('app.media.tab_home'), text: '首页' })

        // 页签2
        TabContent() {
          Column()
            .width('100%')
            .height('100%')
            .backgroundColor('#E3F2FD')
            .justifyContent(FlexAlign.Center)
            .alignItems(HorizontalAlign.Center)
            .children({
              Text('发现内容')
                .fontSize(20)
                .fontColor('#333333')
            })
        }
        .tabBar({ icon: $r('app.media.tab_explore'), text: '发现' })
      }
      // 关键属性:开启叠加,tabBar 悬浮在内容之上
      .barOverlap(true)
      // 关键属性:栏位置在底部
      .barPosition(BarPosition.End)
      // 必须为 false,否则叠加布局不生效
      .vertical(false)
    }
    .width('100%')
    .height('100%')
  }
}

这段代码里需要重点关注三个属性:

属性 作用
barOverlap true 让 tabBar 层叠在 TabContent 之上,而不是独占一行
barPosition BarPosition.End 将 tabBar 定位到容器底部
vertical false 横向布局模式,底部页签必须是这个值

如果你把 barOverlap 设为 false,tabBar 会回到普通 Tabs 的布局方式------栏和内容区各自占位,不叠加。 这个开关直接影响后续所有沉浸样式(模糊、悬浮、出血)的生效基础。

第三步:验证叠加效果

为了更直观地看到叠加发生,可以在 TabContent 底部留一些内容,验证内容是否被 tabBar 遮住。

typescript 复制代码
TabContent() {
  Column() {
    // 填充区域
    ForEach(Array.from({ length: 6 }), (_, index) => {
      Row()
        .width('100%')
        .height(120)
        .backgroundColor(index % 2 === 0 ? '#FFF8E1' : '#FFECB3')
        .margin({ bottom: 8 })
    })
    
    // 这段文本在内容区底部,会被悬浮的 tabBar 遮住一部分
    Text('底部内容,测试叠加效果')
      .fontSize(14)
      .fontColor('#999999')
      .padding({ bottom: 100 })  // 避免被 tabBar 完全遮住
  }
  .width('100%')
  .height('100%')
  .padding(16)
}
.tabBar({ icon: $r('app.media.tab_home'), text: '首页' })

运行后可以看到,tabBar 浮在内容上方,内容可以滚动到 tabBar 下面。如果在真机上操作,滑动内容时 tabBar 不会跟随滚动,一直停留在底部------这和普通 Tabs 的行为不同,普通模式下 tabBar 会随着内容一起滚动。

第四步:控制内容不被 tabBar 遮挡

叠加效果打开后,TabContent 的底部会被 tabBar 遮住一部分。实际项目中需要给内容区留出安全边距,避免交互按钮或文字被遮挡。

常用做法是在内容区底部加一个占位 padding:

typescript 复制代码
Column() {
  // ... 内容
}
.padding({ bottom: 80 })  // tabBar 高度通常在 64vp 左右,留 80vp 保证安全

这个数值不是固定的。不同设备、不同主题下 tabBar 高度会有差异。更稳妥的方式是通过 HdsTabsController 获取 tabBar 的高度,或者直接用 LayoutWeight 让内容区自适应。

常见问题

问题1:设置了 barOverlap=true 但没有叠加效果

现象:tabBar 依然在内容区下方,没有悬浮。

原因vertical 没有显式设为 false。在 HdsTabs 容器中,vertical 的默认值是 true(纵向排列),这时 barOverlap 不会生效。

解决方案

typescript 复制代码
HdsTabs({ controller: this.controller }) {
  // ...
}
.barOverlap(true)
.barPosition(BarPosition.End)
.vertical(false)  // 必须显式设置

问题2:页签图标显示不出来

现象:tabBar 上的图标位置是空的,或者显示问号。

原因$r('app.media.xxx') 资源引用路径不对,或者资源文件没有被正确放到 media 目录下。

解决方案

  1. 检查 src/main/resources/base/media/ 目录下是否存在对应文件

  2. 文件名不能包含中文和特殊字符

  3. 引用时不需要写文件后缀

    // 正确
    $r('app.media.tab_home')

    // 错误
    r('app.media.tab_home.png') r('app.media.首页')

验证方法 :在 DevEco Studio 中按住 Ctrl 点击 $r 引用,如果能跳转到资源文件说明路径正确。

问题3:页面返回后 tabBar 状态丢失

现象:从二级页面返回后,底部页签的选中状态重置了,或者当前显示的页签不对。

原因HdsTabsController 的生命周期和页面绑定,页面销毁后控制器状态会丢失。

解决方案 :使用 LocalStorageAppStorage 持久化当前选中页签的索引。

typescript 复制代码
@Entry
@Component
struct Index {
  @StorageLink('currentTabIndex') currentIndex: number = 0
  private controller: HdsTabsController = new HdsTabsController()

  onPageShow() {
    // 页面显示时恢复选中状态
    this.controller.changeIndex(this.currentIndex)
  }

  build() {
    HdsTabs({ controller: this.controller, index: this.currentIndex }) {
      // ...
    }
    .onChange((index: number) => {
      this.currentIndex = index
    })
  }
}

最佳实践

1. barOverlap 配合 barBackgroundStyle 使用

只开叠加不加样式,底栏看起来就是一块纯色条,没有"沉浸光感"。建议配合 barBackgroundStyle 设置半透明遮罩或模糊效果,才能真正体现 HdsTabs 的设计意图。

typescript 复制代码
.barOverlap(true)
.barPosition(BarPosition.End)
.vertical(false)
.barBackgroundStyle({
  maskColor: 'rgba(255, 255, 255, 0.85)',
  maskHeight: 80
})

为什么 :半透明遮罩让底栏和内容之间产生层次感,而不是生硬地叠在上面。maskHeight 控制遮罩的高度范围,可以让模糊效果从底栏向上渐变。(注:barBackgroundStyle 的具体参数在后续文章会详细展开,这里先有一个印象。)

2. 控制器不要重复创建

HdsTabsController 实例应该和组件同生命周期,不要每次 build 时 new 一个新的。

错误写法

typescript 复制代码
build() {
  HdsTabs({ controller: new HdsTabsController() }) {
    // ...
  }
}

正确写法

typescript 复制代码
private controller: HdsTabsController = new HdsTabsController()

为什么:每次 build 新建控制器会导致页签状态重置,而且还会造成内存泄漏------旧控制器不会被释放。

3. 内容区 padding 不要硬编码

不同设备的 tabBar 高度可能不同,直接用固定 padding 容易出现适配问题。推荐通过 LayoutWeight 或者 Column 的弹性布局来分配空间。

typescript 复制代码
Column() {
  // 内容区占据剩余空间
  Scroll() {
    // ...
  }
  .layoutWeight(1)
  
  // 底部预留安全区域
  Row()
    .height(64)  // 与 tabBar 高度匹配
    .width('100%')
}

为什么:这样即使 tabBar 高度在不同设备上发生变化,内容区也不会被遮挡或者留出过大空白。

完整 Demo 入口

typescript 复制代码
// Index.ets
import { HdsTabs, HdsTabsController } from '@kit.UIDesignKit'

@Entry
@Component
struct Index {
  private controller: HdsTabsController = new HdsTabsController()
  @State private currentIndex: number = 0

  build() {
    Column() {
      HdsTabs({ controller: this.controller, index: this.currentIndex }) {
        TabContent() {
          Column() {
            Text('首页')
              .fontSize(24)
              .fontWeight(FontWeight.Bold)
            
            ForEach(Array.from({ length: 8 }), (_, i) => {
              Row()
                .width('100%')
                .height(80)
                .backgroundColor(i % 2 === 0 ? '#FAFAFA' : '#F0F0F0')
                .margin({ bottom: 4 })
                .padding(16)
                .children({
                  Text(`列表项 ${i + 1}`)
                    .fontSize(16)
                })
            })
            
            Text('底部区域 - 验证叠加效果')
              .fontSize(14)
              .fontColor('#999999')
              .padding({ top: 8, bottom: 72 })
          }
          .width('100%')
          .height('100%')
          .padding(16)
        }
        .tabBar({ icon: $r('app.media.tab_home'), text: '首页' })

        TabContent() {
          Column() {
            Text('发现')
              .fontSize(24)
              .fontWeight(FontWeight.Bold)
            
            // 卡片内容
            ForEach(Array.from({ length: 4 }), (_, i) => {
              Row() {
                Circle()
                  .width(40)
                  .height(40)
                  .fill('#BBDEFB')
                
                Column() {
                  Text(`推荐内容 ${i + 1}`)
                    .fontSize(16)
                    .fontWeight(FontWeight.Medium)
                  Text('HarmonyOS NEXT 开发实践')
                    .fontSize(13)
                    .fontColor('#666666')
                }
                .margin({ left: 12 })
              }
              .width('100%')
              .height(72)
              .backgroundColor('#FFFFFF')
              .borderRadius(12)
              .padding(12)
              .margin({ bottom: 12 })
            })
          }
          .width('100%')
          .height('100%')
          .padding(16)
          .backgroundColor('#F5F5F5')
        }
        .tabBar({ icon: $r('app.media.tab_explore'), text: '发现' })
      }
      .barOverlap(true)
      .barPosition(BarPosition.End)
      .vertical(false)
      .onChange((index: number) => {
        this.currentIndex = index
        console.info(`当前选中页签: ${index}`)
      })
    }
    .width('100%')
    .height('100%')
  }
}

FAQ

Q:HdsTabs 和普通 Tabs 组件能不能混用?

A:不建议。HdsTabs 本身是对 Tabs 的封装,内部已经处理了叠加、模糊、悬浮等样式逻辑。如果在一个页面上同时使用普通 Tabs 和 HdsTabs,容易出现样式冲突和布局异常。如果项目已经用了普通 Tabs,想迁移到 HdsTabs,建议一次性替换完,不要局部混用。

Q:为什么只在底部页签场景推荐使用 HdsTabs?顶部页签能用吗?

A:HdsTabs 的设计目标是底部导航场景,barOverlapbarPosition(BarPosition.End) 这些属性都是为底部栏定制的。顶部页签用普通 Tabs 组件更合适,HdsTabs 的模糊效果和悬浮样式在顶部场景下体验不太自然。

Q:HdsTabs 是否支持无障碍和键盘导航?

A:支持。HdsTabs 继承了 Tabs 组件的无障碍能力,默认支持读屏焦点、TalkBack 播报、键盘方向键切换页签。不需要额外配置。但如果你自定义了 tabBar 的 builder,需要手动设置 accessibilityTextaccessibilityLevel

Q:真机运行正常,预览器里 tabBar 不显示叠加效果?

A:DevEco Studio 预览器对 UI Design Kit 组件的渲染支持有限,部分动画效果和模糊效果在预览器中无法正常显示。这是预览器本身的限制,不是代码问题。建议以真机运行为准。

相关推荐
不羁的木木1 小时前
《HarmonyOS技术精讲》三:记忆链接 ── 跨场景数据融合
pytorch·华为·harmonyos
2501_919749031 小时前
鸿蒙 Flutter 实战:image_crop 0.4.1 适配 3.27-ohos 全流程
flutter·华为·harmonyos
祭曦念2 小时前
鸿蒙应用的生命周期与页面跳转:从入门到实战
华为·harmonyos
轻口味2 小时前
HarmonyOS 6.1.1 全栈实战录 - 88 实战 Ability Kit 启动生命周期预热与快照恢复机
华为·harmonyos·鸿蒙
Goway_Hui3 小时前
【鸿蒙原生应用开发--ArkUI--013】Exercise-tracker 运动记录应用开发教程
华为·harmonyos
想你依然心痛3 小时前
HarmonyOS 6(API 23)实战:基于悬浮导航、沉浸光感与HMAF的“图谱智脑“——PC端AI智能体沉浸式知识图谱构建工作台
人工智能·ar·知识图谱·harmonyos·智能体
想你依然心痛3 小时前
HarmonyOS 6(API 23)实战:基于悬浮导航、沉浸光感与HMAF的“律界智脑“——PC端AI智能体沉浸式法律文档智能审查工作台
人工智能·华为·ar·harmonyos·智能体
特立独行的猫a3 小时前
鸿蒙 PC 平台 Python 第三方库移植全景指南
python·华为·harmonyos·三方库移植·鸿蒙pc
大雷神4 小时前
第31篇|位置信息写入照片记录:为什么拍照时要带上地点
harmonyos