HarmonyOS 底部页签 · 沉浸光感组件实战:模糊样式打造毛玻璃效果

开篇:一个容易被忽略的 API 限制
HarmonyOS NEXT 开发中,HdsTabs 的模糊样式是很多人想用的功能,但实际落地时经常出现"效果出不来"或者"跟预期完全不一样"的情况。原因在于:这个 API 有严格的依赖条件,而且和行为和原生 Tabs 的属性存在冲突。
官方文档虽然给出了示例,但没有解释清楚"为什么必须这样设置"以及"不同属性之间是如何互相影响的"。这篇文章从源码行为出发,讲清楚模糊样式的两种实现方式,以及实际开发中必须注意的边界限制。
这个功能解决什么问题
HdsTabs 的模糊样式解决的问题很简单:让底部 TabBar 具备毛玻璃效果,同时保持对内容的可读性。


| 实现方式 | 原理 | 适用场景 |
|---|---|---|
| 直接模糊 | 通过 barBackgroundBlurStyle 设置系统定义的模糊风格 |
快速实现标准毛玻璃效果 |
| 渐变模糊 | 通过 barBackgroundStyle 自定义遮罩颜色和高度 |
需要与背景色融合的视觉设计 |
从 6.0.0(20) 版本开始支持,6.0.2(22) 后不再需要手动导入 HdsTabsAttribute。
环境说明
text
DevEco Studio 版本:DevEco Studio 6.1.0 及以上
HarmonyOS SDK 版本:HarmonyOS 6.1.0(23) 及以上
目标设备:手机
核心实现:两种模糊效果
示例一:直接模糊
直接模糊使用 barBackgroundBlurStyle 属性,设置 BlurStyle 枚举值来控制模糊程度。
typescript
import { HdsTabs, HdsTabsAttribute, HdsTabsController } from '@kit.UIDesignKit';
@Entry
@Component
struct DirectBlurDemo {
private controller: HdsTabsController = new HdsTabsController();
build() {
Column() {
HdsTabs({ controller: this.controller }) {
TabContent() {
Column().width('100%').height('100%').backgroundColor(Color.Pink)
}
.tabBar({ icon: $r('app.media.startIcon'), text: '页签1' })
TabContent() {
Column().width('100%').height('100%').backgroundColor(Color.Blue)
}
.tabBar({ icon: $r('app.media.startIcon'), text: '页签2' })
}
.barOverlap(true) // 关键:TabBar 叠加在 TabContent 之上
.barPosition(BarPosition.End) // 关键:必须位于底部
.vertical(false) // 关键:必须水平排列
.barBackgroundBlurStyle(BlurStyle.NONE) // 设置为 NONE 时,系统默认的模糊效果不生效
// 如果直接写 .barBackgroundBlurStyle(BlurStyle.COMPONENT_ULTRA_THICK)
// 系统默认模糊会被替换为自定义模糊
}
.width('100%')
.height('100%')
}
}
这里需要特别说明:官方文档提到"去掉TabBar节点,barBackgroundBlurStyle默认设置的模糊的属性值为BlurStyle.NONE",这个描述容易让人误解。实际行为是:
- 如果显式设置
barBackgroundBlurStyle为某个值,系统默认的毛玻璃效果会被这个值替代 - 如果想让毛玻璃完全不生效,必须显式设置为
BlurStyle.NONE - 这个设计有点反直觉,因为大多数开发者会认为不设置就默认没有模糊效果
示例二:渐变模糊
渐变模糊通过 barBackgroundStyle 实现,可以自定义遮罩的颜色和高度。
typescript
import { HdsTabs, HdsTabsAttribute, HdsTabsController } from '@kit.UIDesignKit';
@Entry
@Component
struct GradientBlurDemo {
private controller: HdsTabsController = new HdsTabsController();
build() {
Column() {
HdsTabs({ controller: this.controller }) {
TabContent() {
Column().width('100%').height('100%').backgroundColor(Color.Pink)
}
.tabBar({ icon: $r('app.media.startIcon'), text: '页签1' })
TabContent() {
Column().width('100%').height('100%').backgroundColor(Color.Blue)
}
.tabBar({ icon: $r('app.media.startIcon'), text: '页签2' })
}
.barOverlap(true)
.barPosition(BarPosition.End)
.vertical(false)
.barBackgroundStyle({
maskColor: Color.Yellow, // 遮罩颜色,可以根据 UI 设计图调整
maskHeight: 80 // 模糊区域高度,单位 vp
})
}
.width('100%')
.height('100%')
}
}
渐变模糊的原理是:通过 maskColor 定义一个颜色遮罩,maskHeight 控制这个遮罩从底部向上的高度。实际效果是底部纯色,向上逐渐透明,同时叠加模糊效果。
约束条件的底层逻辑
这三个条件缺一不可:
-
barPosition必须为BarPosition.End:HdsTabs 的模糊效果只支持底部 TabBar,如果改为顶部或者侧边,渲染管线会直接跳过模糊处理逻辑 -
barOverlap必须为true:毛玻璃效果依赖于 TabBar 和内容区域的叠加关系。如果barOverlap为false,TabBar 会独立占据一个区域,模糊效果无法正确渲染 -
vertical必须为false:水平排列的 TabBar 才能触发底部模糊逻辑
这三个条件的组合是框架层面的约束,修改任意一个都会导致 API 调用被忽略。实测发现,即使代码编译通过,运行时也不会有任何错误提示------效果不生效也不报错,这个问题在调试时比较难发现。
常见问题:与原生 Tabs 属性的冲突
这是开发中最容易踩的坑,官方文档明确提到了三种冲突情况:
冲突 1:barBackgroundBlurStyle
如果通过原生 Tabs 的 barBackgroundBlurStyle 属性设置模糊,HdsTabs 的默认模糊效果会完全失效。
typescript
// 错误写法:混合使用
HdsTabs({ controller: this.controller }) {
// ...
}
.barBackgroundBlurStyle(BlurStyle.COMPONENT_ULTRA_THICK) // 原生属性会覆盖 HdsTabs 的默认模糊
原因:HdsTabs 内部有自己的模糊渲染管线,原生 Tabs 属性设置后会替换这个管线。
冲突 2:barBackgroundEffect
typescript
// 同样会导致 HdsTabs 默认模糊失效
HdsTabs({ controller: this.controller }) {
// ...
}
.barBackgroundEffect(new BlurEffect()) // 原生属性冲突
冲突 3:barBackgroundColor
这个冲突比较隐蔽:只有模糊半径会生效(固定 80vp),颜色不会生效。
typescript
// 设置了背景色,但模糊效果只有半径生效
HdsTabs({ controller: this.controller }) {
// ...
}
.barBackgroundColor(Color.Red) // 颜色会被忽略,模糊半径 80vp 生效
实际开发建议 :不要混合使用 HdsTabs 的模糊属性和原生 Tabs 的模糊属性 。如果需要在 HdsTabs 上实现毛玻璃效果,统一使用 HdsTabs 提供的 barBackgroundStyle 或 barBackgroundBlurStyle。
最佳实践
-
统一使用 HdsTabs 的属性 :不要在
HdsTabs组件上混用原生Tabs的属性设置模糊效果。如果确定使用 HdsTabs,所有模糊相关设置都通过 HdsTabs 的 API 完成 -
maskHeight的取值参考设备高度 :maskHeight单位是 vp,常见的取值在 60-120vp 之间。取值过小会导致模糊区域太窄,视觉效果不明显;取值过大会让 TabBar 看起来像是被"淹没"在半透明区域中。建议根据 UI 设计稿的 TabBar 实际高度来确定 -
maskColor优先使用Color枚举或色值 :maskColor支持ResourceColor类型,但实测使用$r()资源引用时,在某些版本上会出现颜色解析失败的问题。推荐直接使用Color枚举或十六进制色值 -
验证约束条件 :在 UI 测试时,建议先单独检查
barOverlap和barPosition是否正确设置。可以在aboutToAppear()中加入日志验证:
typescript
aboutToAppear() {
console.log('HdsTabs barOverlap 检查')
// 如果效果不生效,优先检查这三个条件
}
Demo 入口
合并两种实现方式,方便快速验证:
typescript
@Entry
@Component
struct HdsTabsBlurDemo {
private controller: HdsTabsController = new HdsTabsController();
@State private useGradient: boolean = false;
build() {
Column() {
Button(this.useGradient ? '切换为直接模糊' : '切换为渐变模糊')
.onClick(() => {
this.useGradient = !this.useGradient;
})
HdsTabs({ controller: this.controller }) {
TabContent() {
Column().width('100%').height('100%').backgroundColor(Color.Pink)
}
.tabBar({ icon: $r('app.media.startIcon'), text: '首页' })
TabContent() {
Column().width('100%').height('100%').backgroundColor(Color.Blue)
}
.tabBar({ icon: $r('app.media.startIcon'), text: '发现' })
TabContent() {
Column().width('100%').height('100%').backgroundColor(Color.Orange)
}
.tabBar({ icon: $r('app.media.startIcon'), text: '我的' })
}
.barOverlap(true)
.barPosition(BarPosition.End)
.vertical(false)
.barBackgroundStyle(this.useGradient
? { maskColor: Color.Yellow, maskHeight: 80 }
: undefined)
.barBackgroundBlurStyle(this.useGradient
? BlurStyle.NONE
: BlurStyle.COMPONENT_ULTRA_THICK)
}
.width('100%')
.height('100%')
}
}
FAQ
Q:为什么设置了约束条件,毛玻璃效果还是不生效?
A:检查是否混用了原生 Tabs 的属性。如果写入了 barBackgroundColor、barBackgroundEffect 或原生 barBackgroundBlurStyle,HdsTabs 默认的毛玻璃效果会被覆盖。建议先去掉所有原生 Tabs 的模糊相关属性再测试。
Q:barBackgroundStyle 的 maskHeight 单位是什么?支持变量动态修改吗?
A:单位是 vp,支持通过 @State 变量动态修改。但注意:修改后不会触发 TabBar 的重定位,只是模糊区域的范围变化。如果需要配合动画,建议使用 animateTo() 进行过渡。
Q:为什么设置了 barBackgroundColor 后,模糊半径变成了 80vp?
A:这是 HdsTabs 的默认行为。当检测到原生 barBackgroundColor 属性设置后,HdsTabs 会回退到一套默认的模糊处理逻辑:固定模糊半径 80vp,颜色不生效。这种设计是为了保持兼容性,但实际开发中不建议依赖这个行为------因为 80vp 的模糊半径无法自定义,且效果不稳定。