同一套毛玻璃在不同设备上像三套 UI:HarmonyOS 7 uiMaterial 材质等级适配实战

菜谱页的顶部大图、筛选浮层和计时卡片都用了半透明材质。开发机上层次清楚,换到另一台设备后却出现两种相反结果:有的背景太实,像盖了一块灰板;有的背景太透,菜名和步骤文字压在照片上几乎读不清。

这类问题不能只靠调一个固定透明度解决。HarmonyOS 7(API 26)在 ArkUI 中新增了 uiMaterial 能力,应用可以读取系统的全局材质等级,并在真正支持沉浸式材质时再启用相应视觉。本文交付一套可复用的能力探测器、材质令牌映射、两种行为不同的案例和文件级验收表。

HarmonyOS 7 材质能力探测、等级映射与降级路径

图里最重要的不是三个颜色块,而是两次判断:先读“系统希望呈现哪一级材质”,再问“当前设备能不能稳定呈现沉浸式材质”。只做其中一个判断,都可能把视觉意图误当成设备能力。

官方依据与版本边界

官方变更页:https://developer.huawei.com/consumer/cn/doc/doccenter-release-notes/js-apidiff-arkui-7003

  1. ArkUI 在 API 26 新增 uiMaterial 模块。
  2. MaterialLevel 包含 EXQUISITE = 0、GENTLE = 1、SMOOTH = 2。
  3. getGlobalMaterialLevel(): MaterialLevel 返回系统当前全局材质等级。
  4. 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 分支。设备不支持沉浸材质时,最重要的目标是保证信息可读和交互边界清楚,而不是用普通透明层强行模仿系统材质。

案例一:菜谱列表需要稳定的扫描效率

列表页同时出现菜名、时长、难度和图片。它的任务是快速比较,不是展示最强的材质效果。

处理原则如下:

  1. GENTLE 使用接近实色的表面,只保留轻微层次。
  2. SMOOTH 可以增加背景透出,但正文区域仍保持稳定对比度。
  3. EXQUISITE 只用于分组头、筛选浮层等小面积区域,不给每一张列表卡片都叠高成本效果。
  4. 不支持沉浸式材质时回到 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,最后一分钟也应主动提升不透明度。平台能力决定“能不能用”,业务状态决定“此刻该不该用”。

复现与验证

  1. 在 API 26 工程中记录 getGlobalMaterialLevel() 与 isImmersiveMaterialSupported() 的原始结果。
  2. 准备一张浅色菜谱图、一张深色步骤图和一段背景变化明显的视频。
  3. 分别打开菜谱列表、筛选浮层和烹饪计时卡,记录最终 FoodMaterialProfile。
  4. 用“不支持沉浸材质”的测试替身执行同一流程,确认所有页面都进入 solid,且没有空白或透明穿透。
  5. 把计时器切到 60 秒以内,确认业务分支可以覆盖材质等级。
  6. 开启大字体和高对比度设置,重新检查标题、按钮和倒计时数字。
输入/场景预期模式验收信号
不支持沉浸材质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() 解决的是设备能力边界。把两者组合成业务令牌后,页面才能在视觉层次和内容可读性之间稳定取舍。

对“中式美食”这类图片密集应用,最值得复用的并不是某组透明度,而是这条决策链:先探测、再映射、按场景覆盖、最后为不支持状态留下明确降级。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐