HarmonyOS6与7沉浸光感落地实录:两个接口分支、四个影响变量、一份上线自检清单

引子:需求单上只有三行字
给一个资讯类应用做界面升级,产品给的需求单很短:
一、列表往下滑的时候,顶部标题栏要"慢慢糊起来",不要一条硬邦邦的白条。
二、底部导航别再贴着屏幕底边,要浮起来,内容能从它下面透过去。
三、整屏质感要统一——不能标题栏像玻璃、页签像塑料。
三行字,对应到 HarmonyOS 里就是沉浸光感加上悬浮页签这一组能力。
我原本以为这是个"查属性、抄代码"的活。真正动手之后才发现,这个能力根本不是"怎么写",而是一道连续决策题——你必须依次回答四个问题,任何一个答错,结果都一样:代码不报错,页面没变化。
这篇文章就是把这四道题拆开,加上一份我整理的上线自检清单。
一、第一层决策:接口分支,看前缀不看功能
沉浸光感的第一道坎,是它有两套接口。
这件事官方文档里写得很清楚,但分散在不同章节里,第一遍看很容易滑过去。我把两套接口的关键差异整理成一张对照表:
| 判断维度 | 分支 A | 分支 B |
|---|---|---|
| 组件长什么样 | Hds 前缀(HdsNavigation、HdsTabs…) | 普通 ArkUI 组件(Column、Row、搜索框…) |
| 引哪个模块 | hdsMaterial(@kit.UIDesignKit) | uiMaterial(@kit.ArkUI) |
| 挂哪个属性 | systemMaterialEffect | systemMaterial |
| 用什么枚举 | MaterialType + MaterialLevel | ImmersiveStyle + ImmersiveOptions |
| 最低版本 | 6.1.0(23) | API 26.0.0 |

我的判断口诀只有六个字:看前缀,不看功能。
不要去问"我这个组件是不是玻璃相关的",要问"它是不是 Hds 开头的"。因为这两套接口的枚举不能互换——把 MaterialLevel 挂到普通组件上、把 ImmersiveStyle 挂到 HdsTabs 上,编译都能过,效果都没有。这是我第一遍动手时踩的坑。
至于为什么会分成两条路径,我自己的理解是这样:
HDS 是华为设计体系里的组件,材质能力先在自家组件上落地,所以 6.1.0(23) 就有了 hdsMaterial;而普通 ArkUI 组件要拿到材质,需要 SDK 提供一个可挂载的材质对象(ImmersiveMaterial),这个对象到 API 26 才随 ArkUI 一起提供。
"先 HDS、后通用"这个顺序,是理解后面所有差异的钥匙——包括为什么生效范围会不一样、为什么有些组件"自动就变好看了"。
二、第二层决策:档位,先查能力再谈效果
选完分支,下一个问题是"用什么档位"。
HDS 分支提供四个档位,枚举名和特性如下(档位定义与视觉特性来自官方文档):
| 档位 | 枚举 | 视觉效果 | 性能开销 | 我什么时候用它 |
|---|---|---|---|---|
| 强 | EXQUISITE | 完整光效,通透感最强 | 较高 | 只在旗舰机 + 明确有高质感诉求时 |
| 均衡 | GENTLE | 适度,视觉与性能折中 | 中 | 通用场景;亮色底 + 白色叠层时首选 |
| 弱 | SMOOTH | 只保留核心特性 | 低 | 低端设备的降级目标 |
| 系统自适应 | ADAPTIVE | 由系统按设备能力决定 | 由系统决定 | 默认就给这个 |

官方对档位选择的建议是优先用 ADAPTIVE,并在需要手动指定时先查询设备支持情况——原因是并非所有设备都支持高级沉浸光感,强行开启可能导致卡顿和发热(沉浸光感 · 最佳实践)。
我把这段建议落成了一个四步决策流程,比记结论更实用:
- 先查支持:调
hdsMaterial.getSystemMaterialTypes(),返回的数组里包含IMMERSIVE才算这台设备支持沉浸材质。 - 再定默认:默认给
ADAPTIVE,让系统去挑。 - 把选择权交出去:把四档做成一个用户可切换的菜单(放进"我的 → 界面效果"这类位置),而不是替他决定。
- 只在确定时手动指定:只有当设备确认支持、且产品明确要求"要最强效果"时,才写死
EXQUISITE,并同时准备降级分支。
第 3 步是我强烈建议加的。原因很实在:系统档位是四变量之一(后面第五节展开),用户可以在"设置 → 桌面和个性化 → 沉浸光感"里选强/均衡/弱。你把代码写死最高档,用户的系统档位是"弱",最终呈现就是收敛的——这不是你的代码错,而是你少算了一个变量。与其较劲,不如把档位菜单开放给用户。
import { hdsMaterial } from '@kit.UIDesignKit';
// 能力查询:返回当前设备支持的 HDS 材质类型
function deviceSupportsImmersive(): boolean {
try {
const types = hdsMaterial.getSystemMaterialTypes();
return types.indexOf(hdsMaterial.MaterialType.IMMERSIVE) >= 0;
} catch (err) {
// 查询本身可能因 SDK 版本、系统版本或运行环境而失败,失败即视为不支持
return false;
}
}
这里有个细节值得提醒:这个查询在模拟器上的返回值不一定代表真机能力。官方常见问题里提到过,调用失败可能与 SDK、系统版本或模拟器镜像有关(沉浸光感常见问题)。所以这类能力不要只在模拟器里下结论。
三、第三层决策:位置,决定属性有没有意义
这是最容易被忽略、也最伤人的一层。
沉浸光感不是"挂上属性就生效",它挑位置。 官方在 OS 平台行为变更说明里专门收紧了这一点,理由是"为确保性能和功耗体验最优,规范沉浸光感组件使用"。
我把生效规则整理成一张速查表,比读长文档快:
| 你把组件放在哪 | 光感生效吗 |
|---|---|
Navigation / NavDestination 标题栏 | 生效 |
横向 Tabs 中 barPosition 为 BarPosition.End 的底部 TabBar | 生效 |
| 弹窗类组件(AlertDialog、CustomDialog、ActionSheet、各类 Picker、Menu 等) | 生效 |
| 弹窗类接口(PromptAction、Popup、Tips、菜单控制、半模态转场) | 生效 |
Slider / Toggle / Select | 生效 |
内容区的 Column / Row / 卡片 / 自绘工具栏 | 不生效 |

官方给的反例非常直白:一个普通 Column 通过 systemMaterial 设置沉浸光感,这条约束收紧之后不再生效(开启沉浸光感)。
这条约束只在 targetSdkVersion ≥ 26.0.0 时生效。 也就是说,同一个属性、同一份代码,在不同 targetSDK 下表现可能不一样——联调时遇到"昨天还好好的",先去看一眼 targetSDK。
对我那个资讯应用来说,这条规则直接排除了一个想法:我原本想给"文章卡片"也加上玻璃质感,但卡片在内容区,属性写得再对也不会生效。要做玻璃效果,只能挪进弹窗,或者换别的视觉方案。
所以第三层决策的正确问法是:我这个组件,在不在生效名单里? 不在,就别浪费时间去调参数。
四、落地:一份自己写的完整示例
把前两层决策落成代码,我写了一个最小可用的组合:标题栏渐变模糊 + 底部悬浮页签 + MiniBar。
先写能力判断和档位映射,收在一个独立文件里,避免每个页面各写一遍:
// common/MaterialGate.ets
import { uiMaterial } from '@kit.ArkUI';
import { hdsMaterial } from '@kit.UIDesignKit';
import { deviceInfo } from '@kit.BasicServicesKit';
export type LevelKey = 'adaptive' | 'exquisite' | 'gentle' | 'smooth';
export class MaterialGate {
private static cached: boolean | undefined = undefined;
// 结果缓存:能力在一台设备上不会变,没必要每次进页面都查
static canUseImmersive(): boolean {
if (MaterialGate.cached === undefined) {
try {
MaterialGate.cached =
deviceInfo.apiAvailable('26.0.0') &&
hdsMaterial.getSystemMaterialTypes().indexOf(hdsMaterial.MaterialType.IMMERSIVE) >= 0;
} catch (err) {
MaterialGate.cached = false;
}
}
return MaterialGate.cached;
}
// 不支持时统一降级到 SMOOTH,调用方不需要自己写 if
static toLevel(key: LevelKey): hdsMaterial.MaterialLevel {
if (!MaterialGate.canUseImmersive()) {
return hdsMaterial.MaterialLevel.SMOOTH;
}
switch (key) {
case 'exquisite':
return hdsMaterial.MaterialLevel.EXQUISITE;
case 'gentle':
return hdsMaterial.MaterialLevel.GENTLE;
case 'smooth':
return hdsMaterial.MaterialLevel.SMOOTH;
default:
return hdsMaterial.MaterialLevel.ADAPTIVE;
}
}
// 普通 ArkUI 组件用的材质对象;不支持时返回 empty 表示"不加材质"
static forComponent(key: LevelKey): uiMaterial.Material {
if (!MaterialGate.canUseImmersive()) {
return uiMaterial.Material.empty;
}
return new uiMaterial.ImmersiveMaterial({
style: uiMaterial.ImmersiveStyle.REGULAR,
interactive: true,
});
}
}
这个封装里有三个我自己加的东西,值得说一下:
- 结果缓存:设备能力在一台机器上是常量,没必要每次进页面都调一次查询;
- 降级收口:不支持就统一返回
SMOOTH,页面层不写if,逻辑干净; Material.empty兜底:普通组件那条路径用uiMaterial.Material.empty表示"不加材质",这是官方给出的关闭方式。
然后是页面主体:
// pages/FeedPage.ets
import {
hdsMaterial,
HdsNavigation,
HdsNavigationTitleMode,
HdsTabs,
HdsTabsController,
HideMode,
ScrollEffectType,
} from '@kit.UIDesignKit';
import { MaterialGate, LevelKey } from '../common/MaterialGate';
@Entry
@Component
struct FeedPage {
private tabController: HdsTabsController = new HdsTabsController();
private contentScroller: Scroller = new Scroller();
// 档位做成状态,供设置页修改
@State levelKey: LevelKey = 'adaptive';
@Builder
miniBar() {
Row() {
// 左侧:当前内容状态
// 中间:一行摘要
// 右侧:快捷操作按钮
}
.height('100%')
}
build() {
HdsNavigation() {
HdsTabs({ controller: this.tabController }) {
// TabContent 列表内容,此处省略
}
.scrollable(false)
.barOverlap(true) // 页签栏与内容重叠,这是"浮"起来的前提
.vertical(false) // 横向排列
.barPosition(BarPosition.End) // 放在底部
.barFloatingStyle({
barBottomMargin: 28,
adaptToHandedness: true, // 左右跟手
gradientMask: {
maskColor: '#66F1F3F5',
maskHeight: 92,
},
systemMaterialEffect: {
materialType: hdsMaterial.MaterialType.ADAPTIVE,
materialLevel: MaterialGate.toLevel(this.levelKey),
},
miniBar: {
miniBarBuilder: () => this.miniBar(),
},
})
}
.titleMode(HdsNavigationTitleMode.MINI)
.titleBar({
style: {
scrollEffectOpts: {
enableScrollEffect: true,
// 从完全透明渐变到模糊,比 GRADIENT_BLUR 的过渡更自然
scrollEffectType: ScrollEffectType.IMMERSIVE_GRADIENT_BLUR,
},
systemMaterialEffect: {
materialType: hdsMaterial.MaterialType.ADAPTIVE,
materialLevel: MaterialGate.toLevel(this.levelKey),
},
},
})
.dynamicHideTitleBar({
hideTitleArea: true,
hideStatusBar: true,
mode: HideMode.SCROLL_UP_TO,
})
.bindToScrollable([this.contentScroller]) // 不绑,标题栏就不会跟着滚动
}
}
这段代码里有三处必须同时成立才会出现"浮"的效果:barOverlap(true)、barPosition(BarPosition.End)、vertical(false)。少任何一个,页签栏都会老老实实占住底部空间。
还有一处特别隐蔽:.bindToScrollable() 忘了绑,标题栏不会报错,只是"不跟手"。滚动效果和材质是两件事——只写 scrollEffectOpts 不写 systemMaterialEffect,你得到的是普通模糊标题栏,不是沉浸光感。
五、第四层决策:性能,四个变量一起看
前三个决策答对,效果就有了。但"有"不等于"稳"。
沉浸光感的本质是 GPU 实时渲染,官方专门出了功耗优化文档,这说明它不是能无脑铺满全屏的能力。
我把它总结成一个四变量模型,用来解释所有"我明明配了但效果不对"的情况:
系统档位 × 设备算力 × 应用开关 × 组件参数 = 你看到的最终效果
| 变量 | 由谁决定 | 你需要做什么 |
|---|---|---|
| 系统档位 | 用户在系统设置里选强/均衡/弱 | 把档位菜单做进应用,别替他决定 |
| 设备算力 | 厂商分档,你控制不了 | 先查能力,不支持就降级 |
| 应用开关 | module.json5 中 ohos.arkui.UIMaterial.state(default / enable / disable) | 确认只在 entry 模块生效;升级到 26 且未配置时,组件默认开启 |
| 组件参数 | 你的代码 | 走对分支、选对档位、放对位置 |
排查"没效果"时,按这个顺序问自己:组件位置对吗 → targetSDK 够吗 → 应用开关是 disable 吗 → 设备支持吗 → 档位写对了吗。按这个顺序查,比乱试属性快得多。
应用开关还可以读出来核对:
import { uiMaterial } from '@kit.ArkUI';
const info: uiMaterial.MaterialInfo = uiMaterial.getMaterialInfo();
// info.state:DEFAULT / ENABLE / DISABLE
// info.type :应用配置的材质类型
要区分清楚:应用配置决定材质"是否允许生效",组件参数决定"生效成什么样"。两个都对了才有结果。
性能验证我认同官方给的方法:在目标设备上分别用 ADAPTIVE 与 EXQUISITE 跑同一个页面,用 hiperf 或 DevEco Profiler 观察帧率和功耗;低端机出现掉帧或发热,先退回 GENTLE / SMOOTH。
这里有个好消息可以减轻焦虑:SDK 26 支持 LTPO 可变帧率,界面静止时刷新率可以降下来。所以**"开了光感就一定一直高刷耗电"这个担心是不成立的**。
但也不代表可以随便铺。如果页面本身已经很重(长列表 + 动效 + 网络请求),再叠加光感仍可能掉帧。折中方案是选择性开启——只给当前焦点组件上光感。
最后是一条设计上的铁律,我建议直接写进评审清单:
焦点组件用光感,背景组件不要用——满屏都是光感,就等于没有光感。
落地成三条可执行的规则:
- 优先给交互频繁的组件:底部导航、筛选标签、主操作按钮;
- 慎给纯展示组件:文本阅读区加光感会分散注意力;
- 暗色背景下效果更明显:同一档位,深色底上的视觉冲击强于浅色底。
六、版本取舍:6.1 够用,还是必须上 7.0
两个版本的能力边界差异很大,我做了一张取舍表:
| 你的需求 | 6.1.0(23) 够吗 | 说明 |
|---|---|---|
| 只做标题栏 + 底部页签的材质 | 够 | HDS 分支在 6.1 就已提供 |
| 想让普通组件(搜索框、自绘工具栏)也有材质 | 不够 | 需要 uiMaterial,API 26 起 |
| 想吃"Toast / Tips 自动变好看" | 不够 | API 26 起,升上去即可自动生效 |
| 需要光随指动等新动效 | 不够 | 7.0 新增 |
我的建议是分两步走:如果当前只做导航区域,先用 6.1 的 HDS 分支上线,成本最低;等产品明确要求"全组件统一质感"时,再整体升到 API 26。
升 targetSDK 前必须做两件事:
- 回归生效范围:第三节那条约束只在 26 生效,升级后原本"生效"的组件可能变成"不生效",必须逐个核对;
- 回归亮色主题:亮色底 + 白色叠层场景下,
EXQUISITE可能盖住叠层,宜换GENTLE(官方在最佳实践中提示过这一点)。
另外提醒一个历史限制:在 API 23 及以前版本的 SDK 中,同层渲染场景下控件使能沉浸光感会变透明(例如 Web 组件内嵌 ArkUI 控件)。遇到这类场景,二选一:在对应控件上关闭光感,或者关闭同层渲染。
七、上线自检清单
我把上面的决策点整理成了一份可以贴进 PR 描述的清单:
- 组件的接口分支走对了吗?(看
Hds前缀,不看功能) -
targetSdkVersion≥ 26.0.0 了吗?否则"开启沉浸光感"这件事不成立 - 组件在生效名单里吗?(标题栏 / 底部 TabBar / 弹窗 / Slider·Toggle·Select)
- 调用
getSystemMaterialTypes()做过能力查询了吗?有不支持时的降级分支吗? - 老版本兼容做了吗?(版本判断 + 能力判断,两个条件缺一不可)
-
barOverlap(true)加了吗?页签不"浮",先查这里 - 标题栏
.bindToScrollable()绑了吗?不绑就没有联动 - 档位菜单开放给用户了吗?还是替他写死了?
- 亮色主题 + 白色叠层场景,
EXQUISITE换GENTLE试过了吗? - 用 Profiler 在真机上对比过
ADAPTIVE与EXQUISITE的帧率与功耗吗? - 同层渲染场景(Web 内嵌 ArkUI)关掉光感了吗?
- 光感只用在焦点组件上,而不是铺满全屏吗?
参考与出处
本文涉及的事实性信息(API 名称、枚举取值、版本号、官方约束)来自以下官方文档,文中的结构、代码示例、决策流程与自检清单为本人整理编写:
最后一句:这个能力真正难的不是 API——API 就那么几行。难的是在动手前把四层决策依次答对:走哪个分支、选哪个档位、放哪个位置、怎么兜性能。前四层想清楚了,一行属性就出效果;没想清楚,写十行也是白板。
更多推荐


所有评论(0)