往期回顾:
在前两篇中,我们已经完成了两件事:
- 使用
HdsTabs实现 API23 的底部悬浮页签。 - 通过
systemMaterialEffect接入官方的沉浸光感材质。
但其实从 HarmonyOS 6.1.0(23) 开始,底部悬浮页签还新增了一个比较实用的能力:迷你栏 MiniBar。
这篇我们就继续基于之前的悬浮页签,做一个可以实际运行的 Demo。
一、什么是迷你栏
官方文档里对迷你栏的描述是:
迷你栏是新增的自定义区域,跟页签栏高度相等且水平对齐,支持展开和折叠两种样式。
简单理解就是:
以前底部悬浮区域只有一个 TabBar,也就是我们常见的"首页 / 发现 / 消息 / 我的"。
现在可以在页签栏旁边再加一块自定义区域,例如:
- 音乐播放控制器。
- 课程播放进度。
- 当前任务状态。
- 快捷操作入口。
这块区域就是 miniBar。
它不是普通的 Row 手写悬浮层,而是 HdsTabs 官方悬浮体系的一部分,所以它可以跟页签栏一起处理:
- 悬浮背景。
- 渐变蒙层。
- 沉浸光感材质。
- 折叠/展开动效。
- 点击触发展开或收起。
二、最终 Demo 效果
这次 Demo 做了:
- 底部是
HdsTabs悬浮页签。 - 左侧是一个迷你音乐播放栏。
- 首次进入时迷你栏处于展开态。
- 点击底部迷你栏或页签栏区域后,迷你栏会在展开态和折叠态之间切换。


三、开发前先看约束条件
文档中对这个能力有几个约束,初学者一定要先看。
3.1 页签栏放到底部
需要设置:
typescript
.barPosition(BarPosition.End)
.vertical(false)
其中:
barPosition: BarPosition.End:表示页签栏在容器底部。vertical(false):表示不是纵向页签,而是横向底部页签。
3.2 页签栏允许悬浮叠加
需要设置:
typescript
.barOverlap(true)
这个配置的作用是让 TabBar 可以悬浮在 TabContent 上方。
如果不设置,底部页签就还是普通布局,不是我们要的悬浮效果。
3.3 当前支持的页签样式有限制
文档里也提到,当前支持的样式主要是:
BottomTabBarStyleCustomBuilder
所以这篇 Demo 里,我们直接用 BottomTabBarStyle 来做底部页签,重点放在 miniBar 上。
四、导入模块与基础状态
先看导入和基础状态。
typescript
import { HdsTabs, HdsTabsController, hdsMaterial } from '@kit.UIDesignKit';
这里用到了 3 个内容:
| 名称 | 作用 |
|---|---|
HdsTabs |
HDS 页签组件 |
HdsTabsController |
页签控制器 |
hdsMaterial |
沉浸光感材质参数 |
Demo 中的状态代码如下:
typescript
@Entry
@Component
struct Index {
private controller: HdsTabsController = new HdsTabsController();
@State currentTab: number = 0;
@State miniBarStyle: string = '折叠中';
@State tabBarStyle: string = '展开';
@State logText: string = '';
aboutToAppear(): void {
this.logText = '=== 迷你栏事件日志 ===\n';
}
}
这里主要做两件事:
- 使用
HdsTabsController初始化控制器。 - 用几个
@State变量记录当前页签、迷你栏状态、页签栏状态和日志内容。
五、先写迷你栏内容
miniBar 本质上是一个自定义区域。
所以我们先通过 @Builder 写一个迷你栏 UI。
Demo 中我们把它做成了一个音乐播放控制器:左侧播放按钮,中间文字,右侧暂停按钮。
typescript
// ========== 迷你栏构建器(音乐播放控制器) ==========
@Builder
miniBarBuilder() {
Row() {
// 折叠态主要展示这个按钮;展开态继续展示后面的文字和操作按钮。
Column() {
Image($r('sys.media.ohos_ic_public_play'))
.width(26)
.height(26)
.fillColor('#007DFF')
}
.width(48)
.height(48)
.justifyContent(FlexAlign.Center)
.borderRadius(24)
.backgroundColor('#E8F0FE')
.margin({ left: 4, right: 8 })
Column({ space: 2 }) {
Text('正在播放')
.fontSize(12)
.fontColor('#182431')
.fontWeight(FontWeight.Medium)
Text('HarmonyOS 入门到精通')
.fontSize(10)
.fontColor('#8D9299')
.maxLines(1)
.textOverflow({ overflow: TextOverflow.Ellipsis })
}
.width(116)
.alignItems(HorizontalAlign.Start)
Column() {
Image($r('sys.media.ohos_ic_public_pause'))
.width(22)
.height(22)
.fillColor('#007DFF')
}
.width(48)
.height(48)
.justifyContent(FlexAlign.Center)
.borderRadius(24)
.backgroundColor('#E8F0FE')
.margin({ left: 8, right: 4 })
}
.height(48)
.alignItems(VerticalAlign.Center)
}
这里有两个细节要注意。
5.1 不要给迷你栏根节点强行写 width('100%')
迷你栏的宽度应该交给 miniBarWidth 和组件内部的折叠/展开逻辑管理。
如果我们在 miniBarBuilder() 的根节点上强行写:
typescript
.width('100%')
就可能导致折叠时视觉表现不明显,看起来像没有收回。
所以这里根节点只设置高度:
typescript
.height(48)
5.2 折叠态主要看第一个元素
迷你栏折叠时,组件会把内容裁剪到较小宽度。
所以我们把最重要的播放按钮放在最左边:
typescript
Image($r('sys.media.ohos_ic_public_play'))
这样折叠后,用户还能看到一个明确的入口。
六、接入 HdsTabs
有了迷你栏内容后,就可以放进 HdsTabs 了。
Demo 中一共放了 4 个页签。
typescript
HdsTabs({ controller: this.controller }) {
TabContent() {
this.HomeTabContent()
}
.tabBar(new BottomTabBarStyle($r('sys.media.ohos_ic_public_clock'), '首页'))
TabContent() {
this.DiscoverTabContent()
}
.tabBar(new BottomTabBarStyle($r('sys.media.ohos_ic_public_clock'), '发现'))
TabContent() {
this.MessageTabContent()
}
.tabBar(new BottomTabBarStyle($r('sys.media.ohos_ic_public_clock'), '消息'))
TabContent() {
this.ProfileTabContent()
}
.tabBar(new BottomTabBarStyle($r('sys.media.ohos_ic_public_clock'), '我的'))
}
这里的页签图标都使用了同一个系统资源:
typescript
$r('sys.media.ohos_ic_public_clock')
主要是为了保证 Demo 能稳定编译运行。不同 SDK 暴露的系统资源名可能有差异,教程 Demo 优先保证稳定性。
七、核心:barFloatingStyle 配置
迷你栏的核心配置都在 .barFloatingStyle() 里。
这是本篇最关键的代码:
typescript
.width('100%')
.height('100%')
// 基础布局:底部、水平、悬浮叠加
.barOverlap(true)
.barPosition(BarPosition.End)
.vertical(false)
// 核心新特性:悬浮样式 + 迷你栏
.barFloatingStyle({
// 页签栏分档宽度(响应式)
barWidth: { smallWidth: 240, mediumWidth: 300, largeWidth: 360 },
// 距离底部边距
barBottomMargin: 28,
// 渐变蒙层
gradientMask: { maskColor: '#66F1F3F5', maskHeight: 92 },
// 沉浸光感材质
systemMaterialEffect: {
materialType: hdsMaterial.MaterialType.IMMERSIVE,
materialLevel: hdsMaterial.MaterialLevel.ADAPTIVE
},
// 迷你栏配置
miniBar: {
miniBarBuilder: () => this.miniBarBuilder(),
// 手机宽度下首次进入先展示展开态,点击迷你栏/页签栏后会在展开态和折叠态之间切换。
miniBarStyle: 1,
// 迷你栏展开态分档宽度,折叠态由组件内部按内容高度收起。
miniBarWidth: { smallWidth: 240, mediumWidth: 260, largeWidth: 320 },
// 样式变更回调
onBarStyleChange: (miniBarStyle: number, tabBarStyle: number,
_miniBarWidth: number, _tabBarWidth: number,
mode: number) => {
this.miniBarStyle = miniBarStyle === 0 ? 'COLLAPSE(折叠)' : 'EXPAND(展开)';
this.tabBarStyle = tabBarStyle === 0 ? 'COLLAPSE(折叠)' : 'EXPAND(展开)';
const modeStr = mode === 0 ? 'NORMAL(屏幕尺寸)' :
mode === 1 ? 'USER_CLICK(用户点击)' : 'APP_TRIGGER(应用触发)';
this.appendLog(`onBarStyleChange → 迷你栏:${this.miniBarStyle} | 页签栏:${this.tabBarStyle} | 模式:${modeStr}`);
},
// 迷你栏动画开始回调
onMiniBarAnimationStart: (style: number, width: number) => {
const styleStr = style === 0 ? '折叠中...' : '展开中...';
this.appendLog(`迷你栏动画开始 → ${styleStr} 目标宽度:${width}vp`);
},
// 迷你栏动画结束回调
onMiniBarAnimationEnd: (style: number) => {
const styleStr = style === 0 ? '已折叠' : '已展开';
this.appendLog(`迷你栏动画结束 → ${styleStr}`);
},
// 页签栏动画开始回调
onTabBarAnimationStart: (style: number, width: number) => {
const styleStr = style === 0 ? '折叠中...' : '展开中...';
this.appendLog(`页签栏动画开始 → ${styleStr} 目标宽度:${width}vp`);
},
// 迷你栏背景跟随页签栏
enableMiniBarBackground: true,
// 必须裁剪内容,否则折叠时内部文字仍可能露出,看起来就像没有收回。
enableMiniBarClip: true
}
})
看起来配置比较多,但初学者可以先把它分成两部分理解。
八、第一部分:页签栏悬浮样式
这一部分控制的是底部页签栏本身。
typescript
barWidth: { smallWidth: 240, mediumWidth: 300, largeWidth: 360 },
barBottomMargin: 28,
gradientMask: { maskColor: '#66F1F3F5', maskHeight: 92 },
systemMaterialEffect: {
materialType: hdsMaterial.MaterialType.IMMERSIVE,
materialLevel: hdsMaterial.MaterialLevel.ADAPTIVE
}
8.1 barWidth
barWidth 是页签栏的分档宽度。
官方文档中,HdsBarWidthRangeOptions 提供了 3 个宽度档位:
| 属性 | 含义 |
|---|---|
smallWidth |
HdsTabs 宽度小于 440vp 时使用 |
mediumWidth |
中等宽度场景使用 |
largeWidth |
大屏或宽屏场景使用 |
Demo 中这样设置:
typescript
barWidth: { smallWidth: 240, mediumWidth: 300, largeWidth: 360 }
这样在不同宽度设备上,底部页签栏会有不同宽度。
8.2 barBottomMargin
typescript
barBottomMargin: 28
表示底部页签栏距离 HdsTabs 底部 28vp。
这个值不建议太小,否则悬浮感不明显;也不建议太大,否则容易遮挡内容。
8.3 gradientMask
typescript
gradientMask: { maskColor: '#66F1F3F5', maskHeight: 92 }
这个是底部背板蒙层。
它的作用是让底部悬浮区域和页面内容之间有一个柔和过渡,不会显得特别突兀。
8.4 systemMaterialEffect
typescript
systemMaterialEffect: {
materialType: hdsMaterial.MaterialType.IMMERSIVE,
materialLevel: hdsMaterial.MaterialLevel.ADAPTIVE
}
这部分就是上一篇讲过的沉浸光感材质。
这里我们使用:
IMMERSIVE:沉浸式材质。ADAPTIVE:材质等级交给系统自适应。
九、第二部分:迷你栏配置
真正让迷你栏生效的是 miniBar。
typescript
miniBar: {
miniBarBuilder: () => this.miniBarBuilder(),
miniBarStyle: 1,
miniBarWidth: { smallWidth: 240, mediumWidth: 260, largeWidth: 320 },
enableMiniBarBackground: true,
enableMiniBarClip: true
}
9.1 miniBarBuilder
typescript
miniBarBuilder: () => this.miniBarBuilder()
这里把前面写好的 @Builder miniBarBuilder() 传给 HdsTabs。
也就是说,迷你栏显示什么内容,完全由我们自己决定。
9.2 miniBarStyle
typescript
miniBarStyle: 1
文档中的 HdsBarStyle 有两个值:
| 值 | 含义 |
|---|---|
0 |
COLLAPSE,折叠样式 |
1 |
EXPAND,展开样式 |
Demo 中设置为 1,也就是首次进入页面时先展示展开态。
这样更适合截图和教学,因为一进入页面就能看到完整迷你栏内容。
文档也说明了:
miniBarStyle仅在HdsTabs宽度小于 600vp 时生效。大屏场景下,首次创建时通常会按展开样式展示。
9.3 miniBarWidth
typescript
miniBarWidth: { smallWidth: 240, mediumWidth: 260, largeWidth: 320 }
这个控制的是迷你栏展开态的宽度。
文档里也提醒了:迷你栏最大宽度不超过 328vp。
所以我们这里最大给到 320,没有超过限制。
9.4 enableMiniBarBackground
typescript
enableMiniBarBackground: true
表示迷你栏背景跟随页签栏背景。
如果设置为 false,迷你栏背景会变成透明,视觉上就不会跟底部页签栏融在一起。
9.5 enableMiniBarClip
typescript
enableMiniBarClip: true
这是本篇中最容易踩坑的地方。
如果设置为 false,迷你栏折叠时,miniBarBuilder 里的文字或按钮可能仍然露出来。
这样看起来就像"左侧迷你栏没有收回"。
所以如果我们希望折叠状态表现正常,一般建议开启裁剪:
typescript
enableMiniBarClip: true
十、如何监听折叠和展开
文档中提供了几个回调,用来监听迷你栏和页签栏的状态变化。
10.1 样式变化回调
Demo 中使用的是:
typescript
onBarStyleChange: (miniBarStyle: number, tabBarStyle: number,
_miniBarWidth: number, _tabBarWidth: number,
mode: number) => {
this.miniBarStyle = miniBarStyle === 0 ? 'COLLAPSE(折叠)' : 'EXPAND(展开)';
this.tabBarStyle = tabBarStyle === 0 ? 'COLLAPSE(折叠)' : 'EXPAND(展开)';
const modeStr = mode === 0 ? 'NORMAL(屏幕尺寸)' :
mode === 1 ? 'USER_CLICK(用户点击)' : 'APP_TRIGGER(应用触发)';
this.appendLog(`onBarStyleChange → 迷你栏:${this.miniBarStyle} | 页签栏:${this.tabBarStyle} | 模式:${modeStr}`);
}
这里可以拿到 5 个参数:
| 参数 | 含义 |
|---|---|
miniBarStyle |
当前迷你栏样式 |
tabBarStyle |
当前页签栏样式 |
miniBarWidth |
当前迷你栏宽度 |
tabBarWidth |
当前页签栏宽度 |
mode |
本次变化的触发模式 |
文档中 mode 对应 HdsTabsBarChangeMode:
| 值 | 含义 |
|---|---|
0 |
NORMAL,屏幕尺寸变化触发 |
1 |
USER_CLICK,用户点击触发 |
2 |
APP_TRIGGER,应用触发 |
在我们的 Demo 里,最常见的是用户点击触发,也就是 USER_CLICK。
10.2 迷你栏动画开始
typescript
onMiniBarAnimationStart: (style: number, width: number) => {
const styleStr = style === 0 ? '折叠中...' : '展开中...';
this.appendLog(`迷你栏动画开始 → ${styleStr} 目标宽度:${width}vp`);
}
这个回调会在迷你栏折叠/展开动画开始时触发。
10.3 迷你栏动画结束
typescript
onMiniBarAnimationEnd: (style: number) => {
const styleStr = style === 0 ? '已折叠' : '已展开';
this.appendLog(`迷你栏动画结束 → ${styleStr}`);
}
这个回调会在动画结束时触发。
10.4 页签栏动画开始
typescript
onTabBarAnimationStart: (style: number, width: number) => {
const styleStr = style === 0 ? '折叠中...' : '展开中...';
this.appendLog(`页签栏动画开始 → ${styleStr} 目标宽度:${width}vp`);
}
这里监听的是底部页签栏自己的折叠/展开动画。
十一、事件日志怎么做
为了方便观察回调触发顺序,Demo 中做了一个简单的日志方法:
typescript
private appendLog(msg: string): void {
const time = new Date().toLocaleTimeString();
this.logText += `[${time}] ${msg}\n`;
}
首页中直接显示这个日志:
typescript
Text(this.logText)
.fontSize(11)
.fontColor('#666666')
.width('100%')
.maxLines(10)
.textAlign(TextAlign.Start)
.backgroundColor('#F5F5F5')
.borderRadius(12)
.padding(12)
这样在点击底部迷你栏或页签栏时,就可以看到类似:
text
onBarStyleChange → 迷你栏:COLLAPSE(折叠) | 页签栏:EXPAND(展开) | 模式:USER_CLICK(用户点击)
迷你栏动画开始 → 折叠中... 目标宽度:xxvp
迷你栏动画结束 → 已折叠
这对初学者很友好,因为可以直接看到"点击之后到底发生了什么"。
十二、总结
这篇我们在前两篇的基础上,继续补齐了 API23 新增的迷你栏能力。
到这里,API23 的底部悬浮导航三件套基本就完整了:
- 悬浮页签。
- 沉浸光感。
- 迷你栏。