同一套毛玻璃在不同设备上像三套 UI:HarmonyOS 7 uiMaterial 材质等级适配实战
同一套毛玻璃在不同设备上像三套 UI:HarmonyOS 7 uiMaterial 材质等级适配实战
菜谱页的顶部大图、筛选浮层和计时卡片都用了半透明材质。开发机上层次清楚,换到另一台设备后却出现两种相反结果:有的背景太实,像盖了一块灰板;有的背景太透,菜名和步骤文字压在照片上几乎读不清。
这类问题不能只靠调一个固定透明度解决。HarmonyOS 7(API 26)在 ArkUI 中新增了 uiMaterial 能力,应用可以读取系统的全局材质等级,并在真正支持沉浸式材质时再启用相应视觉。本文交付一套可复用的能力探测器、材质令牌映射、两种行为不同的案例和文件级验收表。

图里最重要的不是三个颜色块,而是两次判断:先读“系统希望呈现哪一级材质”,再问“当前设备能不能稳定呈现沉浸式材质”。只做其中一个判断,都可能把视觉意图误当成设备能力。
官方依据与版本边界
官方变更页:https://developer.huawei.com/consumer/cn/doc/doccenter-release-notes/js-apidiff-arkui-7003
- ArkUI 在 API 26 新增
uiMaterial模块。 MaterialLevel包含EXQUISITE = 0、GENTLE = 1、SMOOTH = 2。getGlobalMaterialLevel(): MaterialLevel返回系统当前全局材质等级。isImmersiveMaterialSupported(): boolean用于判断设备是否支持沉浸式材质。
本文依据 HarmonyOS 7.0.0 Release(API 26)官方 ArkUI 变更说明核对接口。状态映射和分支选择已通过宿主侧 TypeScript 测试;当前本机 SDK 为 API 24,且没有连接 HDC 设备,因此不把示例描述为 API 26 编译通过、真机效果或性能结论。接入前请在 API 26 工程和目标设备上完成编译、视觉与无障碍回归。
为什么固定透明度一定会出问题
固定值只描述“设计稿长什么样”,没有描述运行环境。真正影响结果的至少有四层:系统材质偏好、设备是否支持沉浸材质、当前背景的明暗与纹理、前景文字的对比度。
因此不要让页面到处散落 opacity(0.72) 或某个固定模糊半径。先把系统能力收口为少量业务令牌,再由组件消费令牌。这样一旦设备不支持、无障碍模式需要更高对比度,或图片背景过于复杂,只改一处映射即可。
可复用的 ArkTS 能力探测器
下面的片段直接调用 API 26 新增接口,但把返回值转换为应用自己的稳定模型。页面不需要认识平台枚举,也不会把“支持沉浸式材质”和“必须开启”混为一谈。
import uiMaterial from '@ohos.arkui.uiMaterial'
export type FoodMaterialMode = 'solid' | 'gentle' | 'smooth' | 'exquisite'
export interface FoodMaterialProfile {
mode: FoodMaterialMode
immersiveSupported: boolean
surfaceAlpha: number
textColor: string
borderColor: string
}
export function detectFoodMaterialProfile(): FoodMaterialProfile {
const immersiveSupported = uiMaterial.isImmersiveMaterialSupported()
if (!immersiveSupported) {
return {
mode: 'solid',
immersiveSupported: false,
surfaceAlpha: 0.96,
textColor: '#15191C',
borderColor: '#D8DEE2'
}
}
const level = uiMaterial.getGlobalMaterialLevel()
if (level === uiMaterial.MaterialLevel.EXQUISITE) {
return {
mode: 'exquisite',
immersiveSupported: true,
surfaceAlpha: 0.72,
textColor: '#101416',
borderColor: '#FFFFFF66'
}
}
if (level === uiMaterial.MaterialLevel.SMOOTH) {
return {
mode: 'smooth',
immersiveSupported: true,
surfaceAlpha: 0.82,
textColor: '#111619',
borderColor: '#FFFFFF88'
}
}
return {
mode: 'gentle',
immersiveSupported: true,
surfaceAlpha: 0.90,
textColor: '#14191C',
borderColor: '#E5EAED'
}
}
这里故意保留了 solid 分支。设备不支持沉浸材质时,最重要的目标是保证信息可读和交互边界清楚,而不是用普通透明层强行模仿系统材质。
案例一:菜谱列表需要稳定的扫描效率
列表页同时出现菜名、时长、难度和图片。它的任务是快速比较,不是展示最强的材质效果。
处理原则如下:
GENTLE使用接近实色的表面,只保留轻微层次。SMOOTH可以增加背景透出,但正文区域仍保持稳定对比度。EXQUISITE只用于分组头、筛选浮层等小面积区域,不给每一张列表卡片都叠高成本效果。- 不支持沉浸式材质时回到
solid,保留边框和分隔线,不伪造毛玻璃。
列表页的验收信号不是“截图更酷”,而是快速滚动、深浅图片交替时,菜名仍能被连续读出,卡片边界也不会消失。
案例二:烹饪计时卡需要优先保证状态可见
计时卡会覆盖在步骤视频或大图上。倒计时、暂停和结束提醒都属于高优先级信息,背景再漂亮也不能遮住状态。
export function chooseTimerSurface(
profile: FoodMaterialProfile,
remainingSeconds: number,
backgroundLuma: number
): FoodMaterialProfile {
const urgent = remainingSeconds <= 60
const noisyBackground = backgroundLuma < 0.18 || backgroundLuma > 0.86
if (urgent || noisyBackground) {
return {
...profile,
mode: 'solid',
surfaceAlpha: 0.97,
textColor: '#101416',
borderColor: urgent ? '#D94841' : '#CCD4D9'
}
}
return profile
}
这个案例和列表页不同:即使设备支持 EXQUISITE,最后一分钟也应主动提升不透明度。平台能力决定“能不能用”,业务状态决定“此刻该不该用”。
复现与验证
- 在 API 26 工程中记录
getGlobalMaterialLevel()与isImmersiveMaterialSupported()的原始结果。 - 准备一张浅色菜谱图、一张深色步骤图和一段背景变化明显的视频。
- 分别打开菜谱列表、筛选浮层和烹饪计时卡,记录最终
FoodMaterialProfile。 - 用“不支持沉浸材质”的测试替身执行同一流程,确认所有页面都进入
solid,且没有空白或透明穿透。 - 把计时器切到 60 秒以内,确认业务分支可以覆盖材质等级。
- 开启大字体和高对比度设置,重新检查标题、按钮和倒计时数字。
| 输入/场景 | 预期模式 | 验收信号 |
|---|---|---|
| 不支持沉浸材质 | solid | 信息完整、无透明穿透 |
| GENTLE + 菜谱列表 | gentle | 卡片边界清楚 |
| SMOOTH + 筛选浮层 | smooth | 背景可感知、文字可读 |
| EXQUISITE + 小面积浮层 | exquisite | 层次明确、不过度铺满 |
| 倒计时小于等于 60 秒 | solid | 状态优先、警示边框可见 |
| 极亮或极暗背景 | solid | 对比度不依赖背景运气 |
宿主侧状态模型测试
当前机器无法直接编译 API 26,因此先把“平台返回值到业务令牌”的纯逻辑拆出测试。它不能替代设备验证,但能提前发现枚举映射、默认分支和紧急状态覆盖错误。
const cases = [
{ supported: false, level: 0, expected: 'solid' },
{ supported: true, level: 0, expected: 'exquisite' },
{ supported: true, level: 1, expected: 'gentle' },
{ supported: true, level: 2, expected: 'smooth' }
]
for (const item of cases) {
const actual = mapMaterialMode(item.supported, item.level)
if (actual !== item.expected) {
throw new Error(`material mapping failed: ${JSON.stringify(item)} -> ${actual}`)
}
}
文件级落地清单
- 在
MaterialCapability.ets中集中调用两个 API 26 接口,不让页面重复探测。 - 在
FoodMaterialTokens.ets中维护四种业务模式和颜色、透明度、边框令牌。 - 菜谱列表与计时卡使用同一份 profile,但允许业务状态覆盖视觉等级。
- 为不支持沉浸材质、未知枚举和接口异常保留实色降级。
- 在 API 26 工程记录编译结果、设备型号、系统版本和实际返回值。
- 用浅图、深图、视频、大字体与高对比度完成视觉回归。
- 截图对比时同时记录输入与模式,避免只凭主观判断“更好看”。
小结
getGlobalMaterialLevel() 解决的是全局材质偏好,isImmersiveMaterialSupported() 解决的是设备能力边界。把两者组合成业务令牌后,页面才能在视觉层次和内容可读性之间稳定取舍。
对“中式美食”这类图片密集应用,最值得复用的并不是某组透明度,而是这条决策链:先探测、再映射、按场景覆盖、最后为不支持状态留下明确降级。
更多推荐



所有评论(0)