前言
打开 HarmonyOS 的设置、图库、文件、应用市场,你会发现它们的底部导航栏有一个共同特点:悬浮在内容之上,带有毛玻璃模糊效果,内容可以从底部穿过。这不是简单的固定底栏,而是 HarmonyOS Design System (HDS) 提供的 Floating TabBar 能力。
很多开发者在实现底部导航时,会选择手写一个固定在底部的 Row + 图标——这能用,但和官方效果差距明显。本文将带你用 HDS 原生组件HdsTabs实现真正的 Floating TabBar,并记录我们在真机调试中踩过的每一个坑。
一、目标与 Benchmark
我们的目标不是"做一个底部导航",而是建立一个可复用的 Floating TabBar AppShell,以官方应用为 Benchmark:
| Benchmark 应用 | 我们要对齐的点 |
|---|---|
| 设置 | 悬浮材质、模糊背景、点击反馈 |
| 图库 | 内容穿过 TabBar、渐变遮罩 |
| 文件 | Tab 切换、页面栈独立 |
| 应用市场 | MiniBar、展开/折叠动画 |
| 华为主题 | Safe Area、横竖屏适配 |
二、技术选型:为什么不用手写底栏
| 方案 | 优点 | 缺点 |
|---|---|---|
| 手写 Row + 图标 | 完全自定义 | 没有模糊材质、没有系统反馈、没有 Safe Area |
| ArkUI Tabs | 基础能力 | 没有悬浮样式、没有 MiniBar |
| HdsTabs + barFloatingStyle | 官方原生、模糊材质、MiniBar、Safe Area 一体化 | API 较复杂 |
答案很明确:用 HdsTabs。手写底栏意味着你要自己处理模糊材质、Safe Area、系统点击反馈、展开折叠动画——这些 HDS 都已经做好了。
三、核心实现:HdsTabs + Floating Style
3.1 基础结构
@Component struct FloatingTabBarShell { @State currentIndex: number = 0 private controller: TabsController = new TabsController() private homeScroller: Scroller = new Scroller() private exploreScroller: Scroller = new Scroller() private libraryScroller: Scroller = new Scroller() private profileScroller: Scroller = new Scroller() aboutToAppear() { this.controller.bindScroller(0, this.homeScroller) this.controller.bindScroller(1, this.exploreScroller) this.controller.bindScroller(2, this.libraryScroller) this.controller.bindScroller(3, this.profileScroller) } build() { Tabs({ index: this.currentIndex, controller: this.controller }) { TabContent() { TabPage({ scroller: this.homeScroller, tabName: 'Home' }) } TabContent() { TabPage({ scroller: this.exploreScroller, tabName: 'Explore' }) } TabContent() { TabPage({ scroller: this.libraryScroller, tabName: 'Library' }) } TabContent() { TabPage({ scroller: this.profileScroller, tabName: 'Profile' }) } } .barPosition(BarPosition.End) .barOverlap(true) .barFloatingStyle({ barWidth: 300, barSideMargin: 40, barBottomMargin: 28, gradientMask: { color: '#66F1F3F5', height: 92 } }) .barBackgroundBlurStyle(BlurStyle.Thick, { colorStrategy: BlurStrategy.ADAPTIVE })