前言

在之前接口和开关都确认以后,普通组件已经具备显示沉浸材质的基本条件。

接下来,开发者很快就会遇到一个更具体的问题:五档 ImmersiveStyle 应该怎样选择。

ULTRA_THINTHINREGULARTHICKULTRA_THICK 的名称看起来很直观。刚开始接入时,大家通常会根据“薄”和“厚”直接做决定,例如工具栏使用薄材质,弹窗使用厚材质,页面能正常显示以后就继续开发。

我第一次比较这五档材质时,也采用过这种方式。后来把五张卡片放在相同背景、尺寸和文字条件下对比,我才逐渐发现,材质厚度只是选择依据之一。组件面积、所在位置、背景复杂度和前景信息量,同样会改变最终结果。

材质逐渐变厚以后,背景细节会不断减弱,前景内容会更加稳定,组件在页面中的视觉重量也会增加。搜索框使用过厚材质时,页面可能显得沉重;大面积浮层使用过薄材质时,背景内容又容易干扰文字和操作。

因此,这一轮测试继续控制变量。五张卡片使用相同的背景、尺寸、圆角和文字,只修改 ImmersiveStyle。先看清五档样式的变化,再回到搜索框、工具栏、菜单和弹窗中做选择。

目前我的测试设备还没有 HarmonyOS 7 实机测试权限,本篇先使用模拟器核对接口和基础效果。具备相应权限时,大家应优先在真机上复核材质表现。

一、五档样式的差异应该看什么

ImmersiveStyle 从 API 26.0.0 开始提供,它定义了五种沉浸式材质样式。每一种样式都对应一组系统管理的材质参数,开发者通过枚举选择整体厚度,无需自行组合模糊、高光和阴影等效果。

枚举值 材质特点 适合作为起点的场景
ULTRA_THIN 超薄,具有很强的透明效果 浮动工具栏、顶部悬浮区域
THIN 薄,具有较强的透明效果 搜索框、底部悬浮操作栏
REGULAR 常规厚度,前景与背景较为均衡 普通卡片、通用悬浮组件
THICK 厚,背景模糊效果较强 菜单、临时弹出的浮层
ULTRA_THICK 超厚,背景模糊效果很强 弹窗、半模态页面

从这张表看,五档样式形成了一条从通透到模糊的变化路径。不过,实际观察时还要同时看背景、前景和组件本身。

第一项是背景还剩多少信息。背景中的颜色分界、图案和文字越容易辨认,材质就越轻薄。对于需要延续页面内容的悬浮工具栏,这种通透感比较重要。

第二项是前景内容是否稳定。材质变厚以后,背景对文字和图标的干扰会逐渐降低。菜单、弹窗和信息量较大的浮层,更需要稳定的前景区域。

第三项是组件在页面中显得多重。同一张卡片使用 ULTRA_THIN 时会比较轻,使用 ULTRA_THICK 时会形成更明确的层级。组件面积越大,这种视觉重量的变化通常越明显。

除了样式本身,设备能力也会影响结果。style 参数主要影响高算力和中算力设备,系统会结合设备分档调整实际表现。用户设置的沉浸光感强度也会影响透明度、对比度、折射和环境交互程度。

因此,五档材质需要在相同设备、主题和系统设置下比较。先统一观察条件,后面的选择才有参考价值。

二、组件的位置和面积为什么会改变选择

了解五档材质的基本变化以后,下一步就要把它们放回实际页面。

同一档材质用在不同位置,带来的感受会明显不同。顶部悬浮栏通常覆盖标题、图片或滚动内容,底部操作栏承载按钮、页签和播放控制。菜单可能出现在任意背景上,半模态页面又会占据更大的画面面积。

这些差异决定了组件需要保留多少背景,也决定了前景内容需要多强的稳定性。

顶部悬浮区域可以先试 ULTRA_THIN

顶部悬浮组件需要和页面内容保持连续。ULTRA_THIN 能够保留更多背景信息,让滚动内容自然延伸到顶部。

顶部悬浮组件可以优先使用 ULTRA_THIN,并结合渐变模糊延展内容区域。背景中存在大量文字或高对比图案时,可以继续比较 THIN,重点检查标题与图标的可读性。

搜索框和底部操作栏可以先试 THIN

搜索框和底部操作栏既要保留页面背景,也要确保输入文字、按钮和图标足够清楚。

THIN 会比 ULTRA_THIN 多削弱一部分背景,组件自身仍然保持轻量。搜索框和底部悬浮区域都适合从这一档开始。

我在已有项目中通常先用 THIN 测试底部操作栏。按钮数量较多,或者背景图案比较复杂时,再向 REGULAR 调整。

普通悬浮卡片可以从 REGULAR 开始

REGULAR 位于五档样式中间,也是 ImmersiveMaterial 的默认样式。开发者没有填写 style 时,系统会采用这一档。

它适合作为比较基准。搜索框可以拿它和 THIN 比较,菜单可以拿它和 THICK 比较。这样比每次都测试五档更容易缩小范围。

菜单和临时浮层可以先试 THICK

菜单可能出现在图片、列表或视频上方,开发者很难提前控制它后面的背景。

THICK 能够明显降低背景干扰,让菜单文字和选项保持稳定。可能在任意位置弹出的临时组件也可以先从这一档开始。

菜单面积较小、背景又比较简单时,REGULAR 也可能已经够用,最终仍要结合真实页面判断。

弹窗和半模态页面可以先试 ULTRA_THICK

弹窗和半模态页面占据的面积更大,内部通常包含标题、正文、表单和操作按钮。

ULTRA_THICK 会进一步弱化背景,帮助当前内容形成清楚的层级。半模态页面和大面积弹出框也属于这一档的典型场景。

内容很少的小弹窗使用这一档后可能显得偏重。遇到这种情况,可以退回 THICK 再比较一次。

把组件位置和面积加入判断以后,五档样式就形成了更清楚的起始范围。

组件场景 建议先试 相邻比较
顶部悬浮工具栏 ULTRA_THIN 背景复杂时比较 THIN
搜索框 THIN 比较 ULTRA_THINREGULAR
底部操作栏 THIN 内容较多时比较 REGULAR
普通悬浮卡片 REGULAR 根据背景向两侧调整
菜单和临时浮层 THICK 面积较小时比较 REGULAR
弹窗和半模态页面 ULTRA_THICK 内容较少时比较 THICK

有了这张范围表,下一步就可以搭建一个变量足够少的页面,让五档样式在同一环境中直接对比。

三、怎样搭建一个可比较的最小页面

场景建议能够帮助开发者缩小范围,最终取值仍然需要通过运行结果判断。

为了让结果具有可比性,五张卡片必须使用相同条件。背景、尺寸、圆角或文字发生变化以后,页面差异就很难继续归因于 ImmersiveStyle

当前示例统一使用下面这些条件:

  • 五张卡片使用相同的宽度和高度。
  • 五张卡片使用相同的圆角和文字层级。
  • 五张卡片使用相同的三段彩色背景。
  • 五个材质对象只修改 style
  • 页面使用相同的主题和沉浸光感强度。
  • 截图使用相同的模拟器窗口尺寸。

ImmersiveMaterial 默认采用 REGULAR,材质赋色未设置,自动反色关闭,材质阴影开启,交互形变关闭,触点光感未设置。当前测试只覆盖 style,其余参数继续使用默认值。

这样处理以后,页面中最主要的变量就只剩材质厚度。

先创建五个材质对象

五个对象分别设置对应的 ImmersiveStyle

private readonly ultraThinMaterial: uiMaterial.Material =
  new uiMaterial.ImmersiveMaterial({
    style: uiMaterial.ImmersiveStyle.ULTRA_THIN
  });

private readonly thinMaterial: uiMaterial.Material =
  new uiMaterial.ImmersiveMaterial({
    style: uiMaterial.ImmersiveStyle.THIN
  });

private readonly regularMaterial: uiMaterial.Material =
  new uiMaterial.ImmersiveMaterial({
    style: uiMaterial.ImmersiveStyle.REGULAR
  });

private readonly thickMaterial: uiMaterial.Material =
  new uiMaterial.ImmersiveMaterial({
    style: uiMaterial.ImmersiveStyle.THICK
  });

private readonly ultraThickMaterial: uiMaterial.Material =
  new uiMaterial.ImmersiveMaterial({
    style: uiMaterial.ImmersiveStyle.ULTRA_THICK
  });

这一轮没有开启 interactivelightEffect。按压形变与触点光感会增加新的观察变量,后续验证交互参数时再打开更合适。

再让五张卡片共用同一套结构

五个材质对象通过同一个 @Builder 显示。

@Builder
private materialCard(
  title: string,
  scene: string,
  material: uiMaterial.Material
) {
  Stack() {
    this.comparisonBackground()

    Column({ space: 7 }) {
      Text(title)
        .fontSize(18)
        .fontWeight(FontWeight.Bold)
        .fontColor('#17203A')

      Text(scene)
        .fontSize(13)
        .fontColor('#596179')
    }
    .width('88%')
    .height(96)
    .borderRadius(24)
    .justifyContent(FlexAlign.Center)
    .alignItems(HorizontalAlign.Center)
    .systemMaterial(material)
  }
  .width('100%')
  .height(142)
  .borderRadius(24)
  .clip(true)
}

五张卡片共用同一组背景和布局。后续需要调整测试条件时,只修改一个构建方法即可,也能避免卡片尺寸出现偏差。

页面完成以后,运行结果可以按照轻薄、常规和厚重三组逐步观察。

四、模拟器中的五档样式为什么看起来一样

按照最初的测试计划,我准备把五档样式分成三组观察:先比较 ULTRA_THINTHIN,再把 REGULAR 作为中间参照,最后比较 THICKULTRA_THICK

实际运行以后,页面给出的结果与预期并不一致。

五个 ImmersiveStyle 都能正常创建,五张卡片也都成功显示了沉浸材质,但它们在当前 HarmonyOS 7 模拟器中的视觉效果基本相同。背景颜色、文字透出程度、卡片通透度和整体层级都没有出现稳定、可辨认的差异。

当前示例在 HarmonyOS 7 模拟器中运行。五档材质对象都能正常创建并通过 systemMaterial 设置到组件上,说明接口调用和页面结构可以正常执行。不过,从实际截图来看,五张卡片没有呈现出清楚的厚度差异。

这个结果意味着,当前模拟器适合确认下面几项内容:

  • ImmersiveStyle 的五个枚举值可以正常使用。
  • ImmersiveMaterial 可以成功创建材质对象。
  • 普通 ArkUI 组件可以通过 systemMaterial 显示沉浸材质。
  • 五张卡片使用了相同的测试条件。

我先后使用了纯色分区、背景文字和更高密度的测试元素,模拟器中的五档结果仍然接近。结合这次运行情况,我们更适合把官方定义与模拟器结果分开记录:

内容 当前能够确认的结论
五档枚举与接口调用 模拟器中可以正常执行
沉浸材质基础显示 模拟器中可以正常显示
五档视觉差异 当前模拟器中未观察到明显区别
具体场景选型 参考接口和设计指南,仍需真机验证
最终材质效果 以具备 HarmonyOS 7 测试权限的真机为准

这个结果虽然没有呈现预期中的五档变化,但它仍然有实际价值。开发者在模拟器中看到五档效果一致时,不需要立即怀疑枚举写错或材质对象创建失败。页面已经显示沉浸材质,说明基础调用链路已经接通;当前环境只是没有提供足够明确的视觉差异。

目前我的测试设备还没有相应的 HarmonyOS 7 实机测试权限,因此这一轮只能记录模拟器结果。具备开发者权限和支持设备时,建议使用同一份页面代码继续完成真机测试,并重点观察:

  • 背景文字和细线的保留程度。
  • 彩色区域交界处的柔化程度。
  • 卡片高光、阴影和边框的变化。
  • 大面积组件使用厚材质后的视觉重量。
  • 强、均衡和弱三种系统设置下的差异。

看完五档对比以后,项目还需要结合具体组件做最后一轮选择。

我的习惯是先根据组件位置选出一档,然后向相邻两档各测试一次。这样既能控制工作量,也能避免第一印象直接决定最终结果。

搜索框可以比较三档

搜索框可以从 THIN 开始,然后比较:

ULTRA_THIN
THIN
REGULAR

背景比较简单时,ULTRA_THIN 可能已经足够。背景存在图片、列表文字或高对比图案时,REGULAR 的前景稳定性更值得关注。

底部操作栏需要看按钮数量

按钮数量较少时,可以先比较 ULTRA_THINTHIN

按钮、文字和状态较多时,可以继续加入 REGULAR。组件面积增大以后,过薄材质可能保留过多背景,进而影响图标和文字。

普通卡片可以把 REGULAR 当作起点

普通悬浮卡片可以先使用 REGULAR

卡片主要用于简单操作时,可以向 THIN 调整。卡片包含较多说明文字和按钮时,可以向 THICK 调整。

菜单可以比较三档

菜单可以依次比较:

REGULAR
THICK
ULTRA_THICK

菜单背景简单、面积较小时,REGULAR 可能已经够用。菜单可能覆盖图片、视频或复杂页面时,THICK 通常更稳定。

弹窗和半模态页面需要关注整体重量

半模态页面可以从 ULTRA_THICK 开始,再与 THICK 比较。

内容较多时,超厚材质能够减少背景干扰。内容较少时,过厚材质可能让页面显得沉重,这时需要同时观察内容可读性和页面层级。

最终选择可以归纳为四步:

确认组件位置
↓
判断组件面积和信息密度
↓
选择一个起始样式
↓
与相邻样式进行同条件比较

这个过程不会一次解决所有页面,但它能够为每个组件留下清楚的选择依据。项目后续调整背景、字号或布局时,也可以继续沿用同样的测试方式。

总结

把五档材质放进同一个页面以后,我对 ImmersiveStyle 的理解也从枚举名称变成了更具体的页面判断。

ULTRA_THIN 会保留更多背景信息,适合顶部悬浮区域。THIN 在通透感和前景稳定性之间更容易取得平衡,搜索框和底部操作栏可以先从这一档开始。

REGULAR 是默认样式,也适合作为比较基准。THICK 能够减少复杂背景对内容的干扰,菜单和临时浮层可以优先测试。ULTRA_THICK 会进一步强化前后层级,大面积弹窗和半模态页面更容易从中受益。

对于已有项目,我更建议先从一个组件开始。搜索框比较三档,菜单比较三档,弹窗再比较两档。测试范围足够小,团队更容易看清差异,也能为后续调整留下依据。

模拟器暂时没有呈现出明显的五档差异,项目仍然可以根据官方定义和组件场景缩小选择范围。真正确定最终样式时,还需要把相邻档位放到支持相关能力的真机上继续比较。

这次运行让我确认了五档 ImmersiveStyle 的接口调用方式,也让我看到了模拟器验证的边界。五个材质对象都能正常显示,但当前模拟器没有呈现出稳定的视觉差异。因此,本文中的场景建议主要来自接口定义与设计指南,最终样式仍要通过真机运行结果确定。

完整代码

Main.ets

/**
 * HarmonyOS 7 沉浸光感深度实战 03
 *
 * 验证环境:
 * HarmonyOS SDK API 26
 * HarmonyOS 7 模拟器
 */

import { uiMaterial } from '@kit.ArkUI';

@Entry
@Component
struct Main {
  @State private materialStateText: string =
    '当前应用尚未读取状态';

  @State private stateColor: ResourceColor =
    '#68708A';

  @State private stateDescription: string =
    '页面尚未读取应用级材质状态';

  /**
   * 五个材质对象只设置 style。
   * 其他参数继续使用 ImmersiveMaterial 的默认值。
   */
  private readonly ultraThinMaterial: uiMaterial.Material =
    new uiMaterial.ImmersiveMaterial({
      style: uiMaterial.ImmersiveStyle.ULTRA_THIN
    });

  private readonly thinMaterial: uiMaterial.Material =
    new uiMaterial.ImmersiveMaterial({
      style: uiMaterial.ImmersiveStyle.THIN
    });

  private readonly regularMaterial: uiMaterial.Material =
    new uiMaterial.ImmersiveMaterial({
      style: uiMaterial.ImmersiveStyle.REGULAR
    });

  private readonly thickMaterial: uiMaterial.Material =
    new uiMaterial.ImmersiveMaterial({
      style: uiMaterial.ImmersiveStyle.THICK
    });

  private readonly ultraThickMaterial: uiMaterial.Material =
    new uiMaterial.ImmersiveMaterial({
      style: uiMaterial.ImmersiveStyle.ULTRA_THICK
    });

  aboutToAppear(): void {
    this.loadMaterialState();
  }

  /**
   * 读取当前应用的材质状态。
   * DISABLE 状态会关闭页面中的沉浸式系统材质。
   */
  private loadMaterialState(): void {
    const info: uiMaterial.MaterialInfo =
      uiMaterial.getMaterialInfo();

    this.materialStateText =
      this.getMaterialStateName(info.state);

    this.stateColor =
      this.getMaterialStateColor(info.state);

    this.stateDescription =
      this.getMaterialStateDescription(info.state);
  }

  private getMaterialStateName(
    state: uiMaterial.MaterialState
  ): string {
    switch (state) {
      case uiMaterial.MaterialState.DEFAULT:
        return 'DEFAULT';

      case uiMaterial.MaterialState.ENABLE:
        return 'ENABLE';

      case uiMaterial.MaterialState.DISABLE:
        return 'DISABLE';

      default:
        return `未知状态 ${state}`;
    }
  }

  private getMaterialStateColor(
    state: uiMaterial.MaterialState
  ): ResourceColor {
    switch (state) {
      case uiMaterial.MaterialState.DEFAULT:
        return '#5065E8';

      case uiMaterial.MaterialState.ENABLE:
        return '#1A8F5D';

      case uiMaterial.MaterialState.DISABLE:
        return '#C85A3A';

      default:
        return '#68708A';
    }
  }

  private getMaterialStateDescription(
    state: uiMaterial.MaterialState
  ): string {
    switch (state) {
      case uiMaterial.MaterialState.DEFAULT:
        return '当前状态允许普通组件主动设置沉浸材质。';

      case uiMaterial.MaterialState.ENABLE:
        return '当前状态允许页面比较五档沉浸材质。';

      case uiMaterial.MaterialState.DISABLE:
        return '当前状态会关闭页面中的全部沉浸材质。';

      default:
        return '当前状态没有对应说明。';
    }
  }

  @Builder
  private sectionTitle(
    title: string,
    description: string
  ) {
    Column({ space: 4 }) {
      Text(title)
        .fontSize(22)
        .fontWeight(FontWeight.Bold)
        .fontColor('#11182C')
        .width('100%')

      Text(description)
        .fontSize(14)
        .fontColor('#68708A')
        .lineHeight(21)
        .width('100%')
    }
    .alignItems(HorizontalAlign.Start)
    .width('100%')
  }

  /**
   * 三段背景同时保留颜色、文字和分界线,
   * 方便观察不同样式对背景细节的处理。
   */
  @Builder
  private comparisonBackground() {
    Row() {
      Column({ space: 4 }) {
        Text('01')
          .fontSize(24)
          .fontWeight(FontWeight.Bold)
          .fontColor('#DDE3FF')

        Text('BLUE')
          .fontSize(11)
          .fontColor('#DDE3FF')
      }
      .width('34%')
      .height('100%')
      .justifyContent(FlexAlign.Center)
      .alignItems(HorizontalAlign.Center)
      .backgroundColor('#4B62FF')

      Column({ space: 4 }) {
        Text('02')
          .fontSize(24)
          .fontWeight(FontWeight.Bold)
          .fontColor('#E5F5FF')

        Text('LIGHT')
          .fontSize(11)
          .fontColor('#E5F5FF')
      }
      .width('32%')
      .height('100%')
      .justifyContent(FlexAlign.Center)
      .alignItems(HorizontalAlign.Center)
      .backgroundColor('#59B7FF')

      Column({ space: 4 }) {
        Text('03')
          .fontSize(24)
          .fontWeight(FontWeight.Bold)
          .fontColor('#F0E5FF')

        Text('PURPLE')
          .fontSize(11)
          .fontColor('#F0E5FF')
      }
      .layoutWeight(1)
      .height('100%')
      .justifyContent(FlexAlign.Center)
      .alignItems(HorizontalAlign.Center)
      .backgroundColor('#A266FF')
    }
    .width('100%')
    .height('100%')
  }

  /**
   * 所有卡片共用相同的尺寸、圆角、背景和文字结构。
   */
  @Builder
  private materialCard(
    title: string,
    scene: string,
    material: uiMaterial.Material
  ) {
    Stack() {
      this.comparisonBackground()

      Column({ space: 7 }) {
        Text(title)
          .fontSize(18)
          .fontWeight(FontWeight.Bold)
          .fontColor('#17203A')

        Text(scene)
          .fontSize(13)
          .fontColor('#596179')
      }
      .width('88%')
      .height(96)
      .borderRadius(24)
      .justifyContent(FlexAlign.Center)
      .alignItems(HorizontalAlign.Center)
      .systemMaterial(material)
    }
    .width('100%')
    .height(142)
    .borderRadius(24)
    .clip(true)
  }

  @Builder
  private stateCard() {
    Column({ space: 8 }) {
      Row({ space: 12 }) {
        Text('MaterialState')
          .width('38%')
          .fontSize(14)
          .fontColor('#68708A')

        Text(this.materialStateText)
          .layoutWeight(1)
          .fontSize(14)
          .fontWeight(FontWeight.Medium)
          .fontColor(this.stateColor)
          .textAlign(TextAlign.End)
          .maxLines(2)
      }
      .width('100%')
      .alignItems(VerticalAlign.Center)

      Divider()
        .color('#E8EBF2')

      Text(this.stateDescription)
        .fontSize(13)
        .fontColor('#68708A')
        .lineHeight(20)
        .width('100%')
    }
    .width('100%')
    .padding(16)
    .backgroundColor(Color.White)
    .borderRadius(20)
  }

  build() {
    Scroll() {
      Column({ space: 18 }) {
        Column({ space: 6 }) {
          Text('HarmonyOS 7 沉浸光感')
            .fontSize(28)
            .fontWeight(FontWeight.Bold)
            .fontColor('#11182C')
            .width('100%')

          Text('五档 ImmersiveStyle 对比')
            .fontSize(16)
            .fontColor('#68708A')
            .width('100%')
        }
        .alignItems(HorizontalAlign.Start)
        .width('100%')

        this.sectionTitle(
          '当前应用状态',
          'DEFAULT 和 ENABLE 可以比较样式,DISABLE 会关闭全部材质。'
        )

        this.stateCard()

        this.sectionTitle(
          '统一条件对比',
          '五张卡片只修改 ImmersiveStyle,其余参数保持默认。'
        )

        this.materialCard(
          'ULTRA_THIN',
          '顶部悬浮区域与轻量工具栏',
          this.ultraThinMaterial
        )

        this.materialCard(
          'THIN',
          '搜索框与底部操作栏',
          this.thinMaterial
        )

        this.materialCard(
          'REGULAR',
          '普通卡片与通用场景',
          this.regularMaterial
        )

        this.materialCard(
          'THICK',
          '菜单与临时浮层',
          this.thickMaterial
        )

        this.materialCard(
          'ULTRA_THICK',
          '弹窗与半模态页面',
          this.ultraThickMaterial
        )

        Text(
          '请保持系统主题、沉浸光感强度和模拟器窗口尺寸一致。'
        )
          .fontSize(13)
          .fontColor('#747C92')
          .lineHeight(20)
          .padding({
            top: 4,
            bottom: 24
          })
          .width('100%')
      }
      .width('100%')
      .padding({
        left: 20,
        right: 20,
        top: 24,
        bottom: 24
      })
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#F4F6FB')
  }
}
Logo

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

更多推荐