文章目录
-
- 前言
- [一、先把 Tabs、HdsTabs 和悬浮页签分清楚](#一、先把 Tabs、HdsTabs 和悬浮页签分清楚)
-
- [Tabs 负责常规页签切换](#Tabs 负责常规页签切换)
- [HdsTabs 属于 UI Design Kit](#HdsTabs 属于 UI Design Kit)
- [悬浮页签是 HdsTabs 的悬浮形态](#悬浮页签是 HdsTabs 的悬浮形态)
- [二、现有项目怎么判断要不要换成 HdsTabs](#二、现有项目怎么判断要不要换成 HdsTabs)
-
- [普通底部导航可以继续使用 Tabs](#普通底部导航可以继续使用 Tabs)
- [准备使用悬浮导航时,再进入 HdsTabs](#准备使用悬浮导航时,再进入 HdsTabs)
- [智感握姿也建立在悬浮 HdsTabs 上](#智感握姿也建立在悬浮 HdsTabs 上)
- [HdsTabsController 负责控制问题](#HdsTabsController 负责控制问题)
- [三、先跑通基础 HdsTabs 再增加悬浮配置](#三、先跑通基础 HdsTabs 再增加悬浮配置)
-
- [先确认 UIDesignKit 能够正常导入](#先确认 UIDesignKit 能够正常导入)
- 悬浮配置适合分步加入
- 只确认当前环境已经走通基础调用
- 总结
- 完整代码
前言
现有 HarmonyOS 项目已经有底部导航时,准备改成悬浮页签,最先遇到的问题通常发生在接口选择这里。
项目里原来可能使用 Tabs,页面切换、选中状态和业务数据都已经稳定。开始接触悬浮导航以后,又会碰到 HdsTabs、HdsTabsController、BottomTabBarStyle 和 barFloatingStyle。几个名称放到一起以后,很容易把它们理解成几套可以互相替换的页签组件。
实际关系要简单一些。
Tabs 属于 ArkUI,用来组织多个 TabContent 并完成页签内容切换。HdsTabs 位于 UI Design Kit,UI Design Kit 本身是符合 HarmonyOS Design System 的界面开发套件集合;HdsTabs 已经在 6.0.0(20) Beta1 的 UI Design Kit API 中出现。
悬浮页签继续使用 HdsTabs。页面需要同时设置底部页签、横向排列、内容与页签叠加,再通过 barFloatingStyle 配置悬浮区域。当前悬浮页签的基本条件可以归纳为 barPosition=BarPosition.End、vertical=false、barOverlap=true 和 barFloatingStyle。
所以,已有项目准备改底部导航时,可以先回答两个问题。
当前页面只需要普通页签切换,还是已经准备使用悬浮导航?悬浮导航后面是否还会继续处理 MiniBar、握姿跟随或者不同窗口宽度?
这两个问题确认以后,Tabs 和 HdsTabs 的选择会清楚很多。
目前我的测试环境仍然以 HarmonyOS 7 模拟器为主。基础组件、页签切换和悬浮布局可以先在模拟器中检查;左右手握姿、真实折叠状态以及最终操作手感仍然需要支持相关能力的真机。

一、先把 Tabs、HdsTabs 和悬浮页签分清楚
已有项目最容易混淆的地方,是把三者都理解成底部导航。它们所在的层级其实不同。
Tabs 负责常规页签切换
Tabs 是 ArkUI 中的页签容器,内部通过多个 TabContent 承载不同内容。barPosition 可以决定页签栏的位置,TabContent 则负责每一个页签对应的页面内容。
一个普通的底部导航可以写成:
ts
Tabs({
barPosition: BarPosition.End
}) {
TabContent() {
Text('首页内容')
}
.tabBar('首页')
TabContent() {
Text('任务内容')
}
.tabBar('任务')
TabContent() {
Text('我的内容')
}
.tabBar('我的')
}
这种结构已经能够覆盖大量业务页面。
例如首页、任务、个人中心三个一级入口,只需要固定底栏和内容切换,现有 Tabs 已经运行稳定,项目可以继续保持原来的导航结构。
此时没有必要因为悬浮页签出现,就先修改整个导航容器。真正需要重新判断的时候,是需求开始进入 HDS 页签范围。
HdsTabs 属于 UI Design Kit
HdsTabs 位于 UI Design Kit。它依然使用 TabContent 承载页面,因此已有业务内容通常可以继续保留。改变的主要是外层页签容器,以及 HDS 提供的页签能力。
基础结构可以写成:
ts
import {
HdsTabs,
HdsTabsController
} from '@kit.UIDesignKit';
private controller: HdsTabsController =
new HdsTabsController();
HdsTabs({
controller: this.controller
}) {
TabContent() {
Text('首页内容')
}
.tabBar('首页')
TabContent() {
Text('任务内容')
}
.tabBar('任务')
TabContent() {
Text('我的内容')
}
.tabBar('我的')
}
.barPosition(BarPosition.End)
.vertical(false)
这里有一个对已有项目很友好的地方。
原来的首页、任务页、个人中心仍然可以放在 TabContent 里。迁移时可以先处理导航容器,业务页面暂时保持原状。
这样出现问题时,排查范围主要集中在页签结构,不会同时把业务状态、接口请求和页面布局全部卷进来。
悬浮页签是 HdsTabs 的悬浮形态
大家很容易继续寻找一个单独的悬浮页签组件。
当前悬浮形态依然建立在 HdsTabs 上。页面需要把页签放到底部、保持横向排列,让 TabBar 覆盖到 TabContent 上,再配置 barFloatingStyle。
调用关系可以整理成:
text
HdsTabs
↓
barPosition = BarPosition.End
↓
vertical = false
↓
barOverlap = true
↓
barFloatingStyle(...)
↓
底部悬浮页签
所以项目里的几个对象可以这样区分:
| 对象 | 主要职责 |
|---|---|
Tabs |
ArkUI 常规页签与内容切换 |
HdsTabs |
HDS 页签容器 |
TabContent |
每个页签对应的业务内容 |
BottomTabBarStyle |
底部页签的图标、文字等样式 |
HdsTabsController |
页签相关控制 |
barFloatingStyle |
悬浮页签区域配置 |
| 悬浮页签 | HdsTabs 进入悬浮布局后的页面形态 |
BottomTabBarStyle 也需要单独理解。
它负责底部页签的视觉内容,和整个 HdsTabs 容器并不是同一个对象。HarmonyOS 的底部页签设计本身就包含图标、文本、选中状态等内容表达。
因此,整个结构更适合按照下面的层次去理解:
text
业务内容
TabContent
页签容器
Tabs / HdsTabs
底部页签样式
BottomTabBarStyle
悬浮布局
barFloatingStyle
页签控制
HdsTabsController
把这些层次分开以后,后面看到 MiniBar、页签显隐或者握姿跟随时,就知道应该继续检查哪一层接口。

三者的关系清楚以后,接口选择就可以回到已有项目本身。
二、现有项目怎么判断要不要换成 HdsTabs
页面选择 Tabs 还是 HdsTabs,没有必要只看底栏视觉效果。更实用的判断方式,是把当前需求和接下来准备增加的能力列出来。
普通底部导航可以继续使用 Tabs
假设一个工具应用已经有四个一级页面:
text
首页
收藏
历史
设置
业务只要求固定底栏、页面切换和选中状态,底栏样式也已经由项目自己维护。这种情况下,Tabs 已经能够承担当前任务。
保留现有组件可以减少改动范围,也不会因为导航改造去碰已经稳定的页面逻辑。
准备使用悬浮导航时,再进入 HdsTabs
当页面开始需要胶囊形悬浮导航,或者内容要延伸到底部导航后方时,HdsTabs 就进入了选择范围。
悬浮形态下,barOverlap=true 会让 TabBar 与 TabContent 形成叠加关系,因此内容区和底部导航之间的空间处理也会跟普通固定底栏不同。
项目可以先按照需求做一轮判断:
| 当前需求 | 可以优先考虑 |
|---|---|
| 普通顶部或底部页签 | Tabs |
| HDS 页签结构 | HdsTabs |
| 底部悬浮导航 | HdsTabs + barFloatingStyle |
| 内容延伸到页签后方 | HdsTabs + barOverlap |
| MiniBar | HDS 悬浮页签 |
| 左右手握姿适配 | HDS 悬浮页签 |
| 宽窗口继续调整悬浮导航 | HDS 页签结构 |
这里比较容易误判的是,把 HdsTabs 当成一个必须替换 Tabs 的新版本。
项目是否迁移,还是要看现有页面准备使用哪些能力。
如果普通页签已经满足需求,继续维护 Tabs 会更省事。如果页面已经确定要做悬浮导航,并且后面还会增加 MiniBar、握姿跟随或者多窗口处理,那么从 HdsTabs 开始搭结构会更合适。
智感握姿也建立在悬浮 HdsTabs 上
握姿适配可以进一步帮助判断 HdsTabs 的使用边界。
HdsTabs 进入悬浮状态以后,barFloatingStyle 中可以通过 adaptToHandedness 让底部页签栏跟随握持手变化。这个能力要求页签已经处于 barOverlap 开启的悬浮状态。
所以,当产品设计里已经出现单手操作、左右手热区或者悬浮导航跟随需求时,页面从一开始就需要考虑 HdsTabs 的悬浮结构。
当前模拟器可以先检查属性和布局,真实握姿变化仍然要留到支持对应能力的设备上。
HdsTabsController 负责控制问题
另外一个容易和样式混在一起的对象是 HdsTabsController。HdsTabsOptions 提供 controller?: HdsTabsController,因此控制器属于 HdsTabs 的控制入口。
把结构继续拆开以后,可以形成下面这组关系:
text
页面放什么
→ TabContent
用哪一种页签容器
→ Tabs / HdsTabs
底部页签长什么样
→ BottomTabBarStyle
页签是否悬浮
→ barFloatingStyle
页签如何被控制
→ HdsTabsController
这套分法在后面处理显隐、主动切换和业务状态时会很有用。
同一个问题只交给一层处理,代码也更容易维护。
三、先跑通基础 HdsTabs 再增加悬浮配置
接口选择最终还是要回到工程里验证。
当前环境已经准备好 UI Design Kit 时,可以先搭一个只有三个页签的 HdsTabs 页面。页面不加入 MiniBar、握姿或者复杂材质,只确认容器、TabContent 和底部页签能够正常工作。
先确认 UIDesignKit 能够正常导入
核心导入只有:
ts
import {
HdsTabs,
HdsTabsController
} from '@kit.UIDesignKit';
HdsTabs 已经在 6.0.0(20) Beta1 的 UI Design Kit API 中出现,因此使用 HarmonyOS 7 环境时,要避免把 HdsTabs 本身误判为 HarmonyOS 7 才提供的组件。
页面随后创建控制器:
ts
private controller: HdsTabsController =
new HdsTabsController();
再准备三个 TabContent:
ts
HdsTabs({
controller: this.controller
}) {
TabContent() {
this.tabPage(
'首页',
'当前页面用于检查 HdsTabs 的基础切换。'
)
}
.tabBar('首页')
TabContent() {
this.tabPage(
'任务',
'业务内容继续由 TabContent 承载。'
)
}
.tabBar('任务')
TabContent() {
this.tabPage(
'我的',
'页签容器变化不会要求业务内容重写。'
)
}
.tabBar('我的')
}
.barPosition(BarPosition.End)
.vertical(false)
运行以后,先确认三个结果:
text
HdsTabs 正常创建
TabContent 正常显示
三个页签可以正常切换
这三个条件成立以后,基础 HDS 页签链路就已经可用。
悬浮配置适合分步加入
基础页面已经能够正常切换以后,再增加:
ts
.barOverlap(true)
随后加入:
ts
.barFloatingStyle({
// 悬浮页签配置
})
悬浮页签本身要求 barPosition=BarPosition.End、vertical=false、barOverlap=true,并配置 barFloatingStyle。
分步增加有一个很现实的好处。
页签突然不符合预期时,可以知道问题发生在普通 HdsTabs,还是发生在悬浮布局配置。
如果从第一版代码就同时加入 MiniBar、材质、渐变遮罩和握姿适配,任何一个字段出错都会扩大排查范围。
只确认当前环境已经走通基础调用

模拟器中的这张页面能够帮助确认基础工程链路。
如果 @kit.UIDesignKit 无法导入,问题需要回到 SDK 和工程环境。如果 HdsTabs 已经创建,但页面结构异常,可以继续检查容器和 TabContent。基础结构稳定以后,再进入 barOverlap 和 barFloatingStyle。
总结
现有项目遇到 Tabs、HdsTabs 和悬浮页签时,可以先把它们放到不同层级。
Tabs 负责 ArkUI 常规页签。HdsTabs 属于 UI Design Kit,业务内容仍然通过 TabContent 承载。悬浮页签继续使用 HdsTabs,通过 barPosition、vertical、barOverlap 和 barFloatingStyle 调整成悬浮布局。
项目选择时也可以保持一个比较简单的判断。
普通底栏已经满足需求,现有 Tabs 可以继续维护。页面准备使用 HDS 悬浮导航、MiniBar、智感握姿或者继续处理宽窗口布局时,再评估 HdsTabs。
已有业务页面通常不用跟着导航容器一起重写。首页、任务页、个人中心仍然可以留在 TabContent 中,先完成外层容器验证,再增加悬浮布局。
当前模拟器能够承担 HdsTabs 基础调用、页面切换和悬浮结构的验证。智感握姿、真实折叠状态、最终交互距离和设备性能仍然需要对应真机环境。
完整代码
Main.ets
ts
/**
* HarmonyOS 7 悬浮页签深度实战 01
*
*/
import {
HdsTabs,
HdsTabsController
} from '@kit.UIDesignKit';
@Entry
@Component
struct Main {
/**
* HdsTabs 的控制对象。
* 页签状态控制可以继续复用这个对象。
*/
private controller: HdsTabsController =
new HdsTabsController();
@Builder
private tabPage(
title: string,
description: string,
label: string
) {
Column({ space: 18 }) {
Column({ space: 6 }) {
Text('HarmonyOS 7 悬浮页签')
.fontSize(28)
.fontWeight(FontWeight.Bold)
.fontColor('#11182C')
.width('100%')
Text('HdsTabs 基础模式')
.fontSize(16)
.fontColor('#68708A')
.width('100%')
}
.width('100%')
.alignItems(HorizontalAlign.Start)
Column({ space: 8 }) {
Text('当前检查')
.fontSize(14)
.fontWeight(FontWeight.Medium)
.fontColor('#5065E8')
.width('100%')
Text(
'当前页面检查 HdsTabs、'
+ 'TabContent 和底部页签的基础调用。'
)
.fontSize(13)
.fontColor('#68708A')
.lineHeight(20)
.width('100%')
Text(
'基础页面保持普通底栏,'
+ '方便观察容器和页面切换是否正常。'
)
.fontSize(13)
.fontColor('#68708A')
.lineHeight(20)
.width('100%')
}
.width('100%')
.padding(16)
.backgroundColor(Color.White)
.borderRadius(20)
.alignItems(HorizontalAlign.Start)
Column({ space: 10 }) {
Text(label)
.fontSize(12)
.fontWeight(FontWeight.Medium)
.fontColor('#5065E8')
.width('100%')
Text(title)
.fontSize(24)
.fontWeight(FontWeight.Bold)
.fontColor('#17203A')
.width('100%')
Text(description)
.fontSize(14)
.fontColor('#68708A')
.lineHeight(22)
.width('100%')
}
.width('100%')
.layoutWeight(1)
.padding(20)
.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Start)
.backgroundColor('#F8F9FD')
.borderRadius(24)
Column({ space: 6 }) {
Text('当前接口关系')
.fontSize(14)
.fontWeight(FontWeight.Medium)
.fontColor('#17203A')
.width('100%')
Text(
'HdsTabs → TabContent → tabBar'
)
.fontSize(13)
.fontColor('#68708A')
.width('100%')
Text(
'进入悬浮布局时,'
+ '还需要配置 barOverlap 和 barFloatingStyle。'
)
.fontSize(12)
.fontColor('#747C92')
.lineHeight(18)
.width('100%')
}
.width('100%')
.padding(16)
.backgroundColor(Color.White)
.borderRadius(20)
.alignItems(HorizontalAlign.Start)
}
.width('100%')
.height('100%')
.padding({
left: 20,
right: 20,
top: 24,
bottom: 18
})
.backgroundColor('#F4F6FB')
}
build() {
/**
* @Entry 的根节点使用 Column。
* HdsTabs 放在根容器内部,
* 避免直接把 HdsTabs 作为 Entry 根节点。
*/
Column() {
HdsTabs({
controller: this.controller
}) {
TabContent() {
this.tabPage(
'首页',
'基础页面已经放进 HdsTabs,'
+ '当前可以检查内容和底部页签切换。',
'TAB 01'
)
}
.tabBar('首页')
TabContent() {
this.tabPage(
'任务',
'业务内容继续由 TabContent 承载,'
+ '页面状态可以保持原来的组织方式。',
'TAB 02'
)
}
.tabBar('任务')
TabContent() {
this.tabPage(
'我的',
'外层使用 HdsTabs 后,'
+ '业务页面仍然可以保持独立。',
'TAB 03'
)
}
.tabBar('我的')
}
.barPosition(BarPosition.End)
.vertical(false)
.width('100%')
.layoutWeight(1)
}
.width('100%')
.height('100%')
}
}