【HarmonyOS 7 沉浸光感深度实战】 04 interactive、lightEffect 与完整参数实验

文章目录

前言

沉浸材质真正开始难调,通常出现在参数从一个变成一组之后。

一张卡片可以同时设置材质样式、材质赋色、自动反色、系统阴影、按压形变和触点光感。所有参数一起打开,页面看起来会更丰富,可一旦某个效果没有达到预期,大家很难马上判断问题出在颜色、阴影还是交互反馈上。

我第一次集中测试这些参数时,也经历过这种混乱。卡片颜色变了,文字对比度也跟着变化;按下卡片以后,形变和光感又同时出现。页面能够运行,却很难说清每个参数究竟做了什么。

后来我把它改成了一张可以反复操作的实验页面。卡片的位置、尺寸、圆角和背景始终不变,页面只通过开关替换材质参数。先看颜色、文字和阴影,再按住卡片观察形变,最后移动触点检查光感。每次只增加一个变量,页面上的变化也更容易解释。

当前实验固定使用 THIN 材质,并让卡片同时覆盖深色与浅色背景。卡片内部会显示当前参数状态,下方则提供五个开关和两个光感颜色选项。大家可以直接在页面中完成整轮实验,无需准备多组 Demo。

目前我的测试设备还没有 HarmonyOS 7 实机测试权限,因此当前页面先在模拟器中运行。模拟器适合核对接口、开关和基础显示;按压形变、触点光感及其响应节奏仍需要在具备权限的真机上复核。

一、六个参数可以沿着两条线理解

ImmersiveMaterial 的构造函数接收 ImmersiveOptions。当前可以配置 stylematerialColorcolorInvertapplyShadowinteractivelightEffect

没有传入参数时,系统会使用下面这组默认值:

ts 复制代码
{
  style: uiMaterial.ImmersiveStyle.REGULAR,
  materialColor: undefined,
  colorInvert: false,
  applyShadow: true,
  interactive: false,
  lightEffect: undefined
}

因此,一个没有额外配置的 ImmersiveMaterial 会使用 REGULAR 样式和系统材质阴影,赋色、自动反色、按压形变与触点光感则保持关闭。

六个参数放在一起时容易显得杂乱。按照它们在页面中的作用,可以先分成静态外观和动态反馈两组。

参数 默认值 页面中的作用 实验时怎样观察
style REGULAR 决定材质的整体样式 保持背景不变,切换样式
materialColor undefined 改变材质整体色调 来回切换赋色开关
colorInvert false 调整子树中的文字和图标颜色 让卡片横跨深浅背景
applyShadow true 影响卡片与背景之间的层级 观察圆角四周和下方
interactive false 提供按压时的交互形变 按下、保持,然后松开
lightEffect undefined 提供触点附近的光感反馈 按住卡片并缓慢移动触点

前四项主要改变组件停留在页面上的状态,后两项发生在操作过程中。实验顺序也可以顺着这两条线展开:先把颜色、文字和阴影看清楚,再加入按压形变与触点光感。

当前页面统一使用 THIN,主要是为了满足自动反色的条件。colorInvert 需要使用 THINULTRA_THIN,文字和图标还要通过 $r() 等资源接口设置颜色。直接写入 Color.White 或十六进制颜色时,自动反色不会按照资源颜色的路径生效。

把这些前提固定以后,页面中的主要变量就只剩五个开关和一个光感颜色。大家可以沿着同一套背景逐项操作,参数之间也不会互相遮挡。

二、用同一张卡片完成整轮参数实验

参数分组以后,页面结构还要尽量稳定。

每项参数使用一张独立卡片时,组件的位置、背景和尺寸也会随之变化。最后看到的差异可能同时来自材质参数和页面布局,很难继续判断。

当前页面只保留一张材质卡片。它横跨深蓝色和浅黄色两块背景,位置、尺寸和圆角始终不变。所有开关只负责重新创建卡片使用的 ImmersiveMaterial

先确认应用级状态

页面启动后先调用 getMaterialInfo(),读取当前应用的材质状态:

ts 复制代码
private loadMaterialState(): void {
  const info: uiMaterial.MaterialInfo =
    uiMaterial.getMaterialInfo();

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

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

页面读取到 DISABLE 时,应用中的沉浸式系统材质会被关闭。此时继续切换其他参数,也不会显示对应材质效果。DEFAULTENABLE 都允许普通组件主动设置材质。

所以,开始实验前可以先看页面顶部的 MaterialState。这个步骤很简单,却能避免把应用级配置问题误判成参数问题。

一张状态对象负责替换材质

页面通过 @State 保存当前材质:

ts 复制代码
@State private currentMaterial: uiMaterial.Material =
  new uiMaterial.ImmersiveMaterial({
    style: uiMaterial.ImmersiveStyle.THIN
  });

任意开关发生变化后,refreshMaterial() 都会按照当前参数重新创建一个 ImmersiveMaterial

关闭 materialColor 时,代码直接省略这个字段,让它保持 undefined。关闭 lightEffect 时,也会省略整个光感配置。这样可以保留接口的默认状态,无需使用临时颜色或空对象模拟关闭。

ts 复制代码
private refreshMaterial(): void {
  if (this.tintEnabled) {
    this.currentMaterial =
      new uiMaterial.ImmersiveMaterial({
        style: uiMaterial.ImmersiveStyle.THIN,
        materialColor: '#665B7CFF',
        colorInvert: this.colorInvertEnabled,
        applyShadow: this.applyShadowEnabled,
        interactive: this.interactiveEnabled
      });
    return;
  }

  this.currentMaterial =
    new uiMaterial.ImmersiveMaterial({
      style: uiMaterial.ImmersiveStyle.THIN,
      colorInvert: this.colorInvertEnabled,
      applyShadow: this.applyShadowEnabled,
      interactive: this.interactiveEnabled
    });
}

第一步先看基础材质

实验开始时,页面保持下面这组状态:

text 复制代码
style = THIN
materialColor = undefined
colorInvert = false
applyShadow = true
interactive = false
lightEffect = undefined

这组配置相当于整轮实验的基准。大家可以先观察深浅背景透过卡片后的表现,再看圆角四周和卡片下方有没有系统阴影。

基础状态确认后,后面的每一步都只改变一个参数。

第二步打开 materialColor

materialColor 会在材质滤镜上继续混合一层颜色。它可以给搜索框、工具栏或操作卡片加入轻量的品牌色。

颜色需要保留透明度。完全不透明的颜色会遮住材质滤镜,组件容易变成一块普通色板。官方常见问题建议为 materialColor 传入带透明度的颜色。

当前实验使用下面的蓝紫色:

ts 复制代码
new uiMaterial.ImmersiveMaterial({
  style: uiMaterial.ImmersiveStyle.THIN,
  materialColor: '#665B7CFF'
});

打开开关以后,可以观察卡片整体色调有没有变化,同时看左右两侧的背景还能保留多少信息。

背景仍然能够透出,说明材质还保留着原来的空间关系。颜色明显盖住背景文字和分区以后,可以在实际项目中继续降低 alpha。

赋色观察完成后,再关闭 materialColor。深浅背景恢复清楚,下一步查看文字变化会更容易。

第三步检查 colorInvert

colorInvert 用于让材质节点子树中的文字和图标根据背景调整颜色。搜索框、悬浮工具栏和跨越图片内容的浮动操作区,都可能用到这项能力。

自动反色需要同时满足几个条件:

  • 材质使用 THINULTRA_THIN
  • 文字与图标颜色通过资源接口设置。
  • 卡片下方存在明显的明暗变化。
  • 系统沉浸光感强度会影响反色触发阈值。

REGULARTHICKULTRA_THICK 不会进入相同的自动反色路径,硬编码颜色也不会触发相同的适配逻辑。

页面中的文字使用资源颜色:

ts 复制代码
Text('THIN 参数实验')
  .fontColor($r('app.color.material_demo_text'))

材质对象再打开自动反色:

ts 复制代码
new uiMaterial.ImmersiveMaterial({
  style: uiMaterial.ImmersiveStyle.THIN,
  colorInvert: true
});

切换开关以后,可以直接观察标题和参数摘要在深色、浅色两侧的表现。

当前模拟器中没有出现明显变化时,大家可以依次检查材质样式、资源颜色和系统沉浸光感强度。上述条件都满足以后,仍然可以在具备权限的真机上继续复核。

第四步切换 applyShadow

applyShadow 默认开启,系统会为沉浸材质提供阴影。

组件已经设置 .shadow() 时,两套阴影可能叠加,卡片边缘容易显得偏重。项目已有统一阴影体系时,可以关闭系统材质阴影,继续使用原来的组件阴影。

ts 复制代码
new uiMaterial.ImmersiveMaterial({
  style: uiMaterial.ImmersiveStyle.THIN,
  applyShadow: false
});

当前实验没有增加自定义阴影。切换 applyShadow 时,只需要观察卡片下方、左右边缘和圆角区域。

阴影关闭以后,卡片与背景之间的距离感可能减弱。模拟器中的差异较小时,也可以直接回到基础状态来回切换,两种状态之间会更容易比较。

第五步单独打开 interactive

静态外观确认以后,可以保持 lightEffect 关闭,只打开 interactive

interactive 用于启用系统提供的交互形变。按下组件、保持片刻再松开,可以观察卡片在操作过程中的变化。

ts 复制代码
new uiMaterial.ImmersiveMaterial({
  style: uiMaterial.ImmersiveStyle.THIN,
  interactive: true
});

操作顺序可以保持简单:

text 复制代码
按下卡片
↓
保持片刻
↓
松开

页面中的点击次数只能确认组件已经接收到操作。形变的幅度和恢复过程仍然要直接观察材质卡片。

第六步加入 lightEffect

lightEffect 用于配置触点附近的光感反馈。设置 lightEffect 后,color 可以指定光感颜色;color 未填写时,系统使用默认白色。

使用白色光感:

ts 复制代码
new uiMaterial.ImmersiveMaterial({
  style: uiMaterial.ImmersiveStyle.THIN,
  interactive: true,
  lightEffect: {
    color: Color.White
  }
});

切换为蓝紫色:

ts 复制代码
new uiMaterial.ImmersiveMaterial({
  style: uiMaterial.ImmersiveStyle.THIN,
  interactive: true,
  lightEffect: {
    color: '#FF77A0FF'
  }
});

打开光感以后,可以按住卡片并缓慢移动触点,然后再松开:

text 复制代码
按下卡片
↓
缓慢移动触点
↓
观察光感位置和颜色
↓
松开并查看恢复

我更习惯先使用白色,确认触点附近有没有光感;随后切换蓝紫色,在相同位置重复操作。背景和操作方式保持一致以后,颜色配置是否生效会更容易辨认。

实验页面会在打开 lightEffect 时同步打开 interactive,关闭 interactive 时同步关闭光感。这个处理只用于减少无效的开关组合,两个参数在接口层面仍然保持独立。

三、把参数组合带回现有项目

实验页面解决了每个参数的作用,真正放进项目时,还需要结合组件的位置、内容和交互频率做取舍。

我更倾向于从一组简单配置开始。页面先稳定运行,再根据实际问题逐项增加参数。这样做虽然慢一点,却能避免颜色、阴影和交互同时变化后再回头排查。

轻量工具栏先处理文字适配

跨越复杂背景的顶部或底部工具栏,可以先使用:

ts 复制代码
new uiMaterial.ImmersiveMaterial({
  style: uiMaterial.ImmersiveStyle.THIN,
  colorInvert: true,
  applyShadow: true
});

文字和图标需要通过资源接口设置颜色。静态效果稳定以后,再决定是否加入 interactive

工具栏操作较少时,按压反馈可以保持克制。工具栏中包含多个高频按钮时,再逐个判断哪些区域需要形变反馈。

带品牌色的操作卡片先控制透明度

材质需要带有轻量品牌色时,可以加入透明赋色:

ts 复制代码
new uiMaterial.ImmersiveMaterial({
  style: uiMaterial.ImmersiveStyle.THIN,
  materialColor: '#665B7CFF',
  applyShadow: true,
  interactive: true
});

这类组件需要同时检查浅色模式、深色模式和图片背景。品牌色在纯色页面上看起来合适,换到复杂图片后可能显得偏重。

我通常会优先调整透明度,等颜色与背景达到平衡,再考虑增加光感。这样能够避免品牌色和触点光晕同时抢占注意力。

高频操作组件再加入光感

操作区域较大、交互频率较高时,可以同时加入形变和光感:

ts 复制代码
new uiMaterial.ImmersiveMaterial({
  style: uiMaterial.ImmersiveStyle.THIN,
  interactive: true,
  lightEffect: {
    color: Color.White
  }
});

页面中已经存在较多动画时,光感颜色和使用范围可以适当收敛。多个高强度反馈集中出现,组件之间容易互相争夺注意力。

项目已有阴影时先明确归属

项目已经有自己的阴影规范时,可以关闭材质阴影:

ts 复制代码
new uiMaterial.ImmersiveMaterial({
  style: uiMaterial.ImmersiveStyle.THIN,
  applyShadow: false
});

随后继续使用原来的 .shadow()

阴影由系统材质负责,或者由组件样式负责,需要在接入时确定下来。同一个组件保留两套未经比较的阴影,页面层级很难保持统一。

整个调整过程可以沿着下面的路径推进:

text 复制代码
确定 style
↓
决定是否加入 materialColor
↓
检查资源颜色与 colorInvert
↓
明确阴影来源
↓
最后加入 interactive 与 lightEffect

每一步只增加一个变量。页面出现问题时,也可以直接回到最近一次修改,不需要从整组参数中重新排查。

总结

把六个参数放进同一张实验页面以后,原本容易混在一起的变化会清楚很多。

materialColor 改变材质整体色调,透明度决定了颜色和背景之间的平衡。colorInvert 需要薄材质、资源颜色和明显的明暗背景。applyShadow 则要和项目原有的阴影体系明确分工。

按下卡片以后,interactive 负责交互形变,lightEffect 负责触点附近的光感。先观察纯形变,再加入光感和颜色,整个过程更容易理解。

我更推荐先把静态外观调稳定,再增加动态反馈。颜色、文字和阴影确认以后,按压形变与光感才有清楚的观察基础。

目前我的测试设备还没有 HarmonyOS 7 实机测试权限,因此当前页面先在模拟器中运行,可以确认接口调用、参数切换和基础表现。正式接入项目前,大家仍应优先在具备权限的真机上复核自动反色、按压形变、触点光感和性能表现。

完整代码

resources/base/element/color.json

json 复制代码
{
  "color": [
    {
      "name": "material_demo_text",
      "value": "#FF17203A"
    },
    {
      "name": "material_demo_subtext",
      "value": "#FF596179"
    }
  ]
}

Main.ets

ts 复制代码
/**
 * HarmonyOS 7 沉浸光感深度实战 04
 *
 * 验证环境:
 * 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 tintEnabled: boolean = false;

  @State private colorInvertEnabled: boolean = false;

  @State private applyShadowEnabled: boolean = true;

  @State private interactiveEnabled: boolean = false;

  @State private lightEffectEnabled: boolean = false;

  /**
   * 0 表示白色光感。
   * 1 表示蓝紫色光感。
   */
  @State private lightColorMode: number = 0;

  @State private pressCount: number = 0;

  /**
   * 页面始终使用 THIN。
   * 参数变化后,refreshMaterial 会替换当前材质。
   */
  @State private currentMaterial: uiMaterial.Material =
    new uiMaterial.ImmersiveMaterial({
      style: uiMaterial.ImmersiveStyle.THIN
    });

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

  /**
   * 读取应用当前采用的材质状态。
   */
  private loadMaterialState(): void {
    const info: uiMaterial.MaterialInfo =
      uiMaterial.getMaterialInfo();

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

    this.stateColor =
      this.getMaterialStateColor(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';
    }
  }

  /**
   * 按照当前开关状态重新创建材质对象。
   *
   * materialColor 和 lightEffect 关闭时直接省略字段,
   * 保持接口默认的 undefined 状态。
   */
  private refreshMaterial(): void {
    const lightColor: ResourceColor =
      this.lightColorMode === 0
        ? Color.White
        : '#FF77A0FF';

    if (this.tintEnabled) {
      if (this.lightEffectEnabled) {
        this.currentMaterial =
          new uiMaterial.ImmersiveMaterial({
            style: uiMaterial.ImmersiveStyle.THIN,
            materialColor: '#665B7CFF',
            colorInvert: this.colorInvertEnabled,
            applyShadow: this.applyShadowEnabled,
            interactive: this.interactiveEnabled,
            lightEffect: {
              color: lightColor
            }
          });
      } else {
        this.currentMaterial =
          new uiMaterial.ImmersiveMaterial({
            style: uiMaterial.ImmersiveStyle.THIN,
            materialColor: '#665B7CFF',
            colorInvert: this.colorInvertEnabled,
            applyShadow: this.applyShadowEnabled,
            interactive: this.interactiveEnabled
          });
      }

      return;
    }

    if (this.lightEffectEnabled) {
      this.currentMaterial =
        new uiMaterial.ImmersiveMaterial({
          style: uiMaterial.ImmersiveStyle.THIN,
          colorInvert: this.colorInvertEnabled,
          applyShadow: this.applyShadowEnabled,
          interactive: this.interactiveEnabled,
          lightEffect: {
            color: lightColor
          }
        });
    } else {
      this.currentMaterial =
        new uiMaterial.ImmersiveMaterial({
          style: uiMaterial.ImmersiveStyle.THIN,
          colorInvert: this.colorInvertEnabled,
          applyShadow: this.applyShadowEnabled,
          interactive: this.interactiveEnabled
        });
    }
  }

  /**
   * 将页面恢复到基础实验状态。
   */
  private resetExperiment(): void {
    this.tintEnabled = false;
    this.colorInvertEnabled = false;
    this.applyShadowEnabled = true;
    this.interactiveEnabled = false;
    this.lightEffectEnabled = false;
    this.lightColorMode = 0;
    this.pressCount = 0;
    this.refreshMaterial();
  }

  private getCurrentConfigText(): string {
    const tintText: string =
      this.tintEnabled ? '赋色开' : '赋色关';

    const invertText: string =
      this.colorInvertEnabled ? '反色开' : '反色关';

    const shadowText: string =
      this.applyShadowEnabled ? '阴影开' : '阴影关';

    const interactiveText: string =
      this.interactiveEnabled ? '形变开' : '形变关';

    const lightText: string =
      this.lightEffectEnabled ? '光感开' : '光感关';

    return `${tintText} · ${invertText} · ${shadowText} · `
      + `${interactiveText} · ${lightText}`;
  }

  private getOperationHint(): string {
    if (this.lightEffectEnabled) {
      return '按住卡片并缓慢移动触点,再松手观察恢复';
    }

    if (this.interactiveEnabled) {
      return '按住卡片片刻,再松手观察形变与恢复';
    }

    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() {
    Stack() {
      Row() {
        Column({ space: 8 }) {
          Text('DARK')
            .fontSize(26)
            .fontWeight(FontWeight.Bold)
            .fontColor('#99FFFFFF')

          Text('深色背景')
            .fontSize(12)
            .fontColor('#BFFFFFFF')
        }
        .width('50%')
        .height('100%')
        .justifyContent(FlexAlign.Center)
        .alignItems(HorizontalAlign.Center)
        .backgroundColor('#17203A')

        Column({ space: 8 }) {
          Text('LIGHT')
            .fontSize(26)
            .fontWeight(FontWeight.Bold)
            .fontColor('#6617203A')

          Text('浅色背景')
            .fontSize(12)
            .fontColor('#9917203A')
        }
        .layoutWeight(1)
        .height('100%')
        .justifyContent(FlexAlign.Center)
        .alignItems(HorizontalAlign.Center)
        .backgroundColor('#F4C95D')
      }
      .width('100%')
      .height('100%')

      Row() {
        Circle()
          .width(58)
          .height(58)
          .fill('#4B62FF')

        Blank()

        Circle()
          .width(58)
          .height(58)
          .fill('#A266FF')
      }
      .width('100%')
      .padding({
        left: 24,
        right: 24
      })
    }
    .width('100%')
    .height('100%')
  }

  /**
   * 同一张卡片承载全部参数实验。
   *
   * 文字使用资源颜色,为 colorInvert 提供观察条件。
   */
  @Builder
  private materialPreview() {
    Stack() {
      this.comparisonBackground()

      Column({ space: 9 }) {
        Text('THIN 参数实验')
          .fontSize(21)
          .fontWeight(FontWeight.Bold)
          .fontColor(
            $r('app.color.material_demo_text')
          )
          .textAlign(TextAlign.Center)
          .width('100%')

        Text(this.getCurrentConfigText())
          .fontSize(12)
          .fontColor(
            $r('app.color.material_demo_subtext')
          )
          .textAlign(TextAlign.Center)
          .maxLines(2)
          .width('100%')

        Text(this.getOperationHint())
          .fontSize(12)
          .fontColor(
            $r('app.color.material_demo_subtext')
          )
          .textAlign(TextAlign.Center)
          .maxLines(2)
          .width('100%')
          .margin({ top: 4 })

        Text(`完成点击:${this.pressCount} 次`)
          .fontSize(11)
          .fontColor(
            $r('app.color.material_demo_subtext')
          )
      }
      .width('88%')
      .height(158)
      .padding({
        left: 12,
        right: 12
      })
      .backgroundColor(Color.Transparent)
      .borderRadius(30)
      .justifyContent(FlexAlign.Center)
      .alignItems(HorizontalAlign.Center)
      .onClick(() => {
        this.pressCount += 1;
      })
      .systemMaterial(this.currentMaterial)
    }
    .width('100%')
    .height(228)
    .borderRadius(30)
    .clip(true)
  }

  @Builder
  private materialStateCard() {
    Row({ space: 12 }) {
      Text('MaterialState')
        .width('42%')
        .fontSize(14)
        .fontColor('#68708A')

      Text(this.materialStateText)
        .layoutWeight(1)
        .fontSize(14)
        .fontWeight(FontWeight.Medium)
        .fontColor(this.stateColor)
        .textAlign(TextAlign.End)
    }
    .width('100%')
    .padding(16)
    .backgroundColor(Color.White)
    .borderRadius(20)
  }

  @Builder
  private parameterPanel() {
    Column() {
      Row({ space: 12 }) {
        Column({ space: 3 }) {
          Text('materialColor')
            .fontSize(15)
            .fontWeight(FontWeight.Medium)
            .fontColor('#17203A')

          Text('叠加带透明度的蓝紫色')
            .fontSize(12)
            .fontColor('#747C92')
        }
        .layoutWeight(1)
        .alignItems(HorizontalAlign.Start)

        Toggle({
          type: ToggleType.Switch,
          isOn: this.tintEnabled
        })
          .onChange((isOn: boolean) => {
            this.tintEnabled = isOn;
            this.refreshMaterial();
          })
      }
      .width('100%')
      .padding({ top: 12, bottom: 12 })
      .alignItems(VerticalAlign.Center)

      Divider()
        .color('#E8EBF2')

      Row({ space: 12 }) {
        Column({ space: 3 }) {
          Text('colorInvert')
            .fontSize(15)
            .fontWeight(FontWeight.Medium)
            .fontColor('#17203A')

          Text('使用资源颜色观察自动反色')
            .fontSize(12)
            .fontColor('#747C92')
        }
        .layoutWeight(1)
        .alignItems(HorizontalAlign.Start)

        Toggle({
          type: ToggleType.Switch,
          isOn: this.colorInvertEnabled
        })
          .onChange((isOn: boolean) => {
            this.colorInvertEnabled = isOn;
            this.refreshMaterial();
          })
      }
      .width('100%')
      .padding({ top: 12, bottom: 12 })
      .alignItems(VerticalAlign.Center)

      Divider()
        .color('#E8EBF2')

      Row({ space: 12 }) {
        Column({ space: 3 }) {
          Text('applyShadow')
            .fontSize(15)
            .fontWeight(FontWeight.Medium)
            .fontColor('#17203A')

          Text('控制材质自带的系统阴影')
            .fontSize(12)
            .fontColor('#747C92')
        }
        .layoutWeight(1)
        .alignItems(HorizontalAlign.Start)

        Toggle({
          type: ToggleType.Switch,
          isOn: this.applyShadowEnabled
        })
          .onChange((isOn: boolean) => {
            this.applyShadowEnabled = isOn;
            this.refreshMaterial();
          })
      }
      .width('100%')
      .padding({ top: 12, bottom: 12 })
      .alignItems(VerticalAlign.Center)

      Divider()
        .color('#E8EBF2')

      Row({ space: 12 }) {
        Column({ space: 3 }) {
          Text('interactive')
            .fontSize(15)
            .fontWeight(FontWeight.Medium)
            .fontColor('#17203A')

          Text('控制按压时的交互形变')
            .fontSize(12)
            .fontColor('#747C92')
        }
        .layoutWeight(1)
        .alignItems(HorizontalAlign.Start)

        Toggle({
          type: ToggleType.Switch,
          isOn: this.interactiveEnabled
        })
          .onChange((isOn: boolean) => {
            this.interactiveEnabled = isOn;

            if (!isOn) {
              this.lightEffectEnabled = false;
            }

            this.refreshMaterial();
          })
      }
      .width('100%')
      .padding({ top: 12, bottom: 12 })
      .alignItems(VerticalAlign.Center)

      Divider()
        .color('#E8EBF2')

      Row({ space: 12 }) {
        Column({ space: 3 }) {
          Text('lightEffect')
            .fontSize(15)
            .fontWeight(FontWeight.Medium)
            .fontColor('#17203A')

          Text('控制触点附近的光感反馈')
            .fontSize(12)
            .fontColor('#747C92')
        }
        .layoutWeight(1)
        .alignItems(HorizontalAlign.Start)

        Toggle({
          type: ToggleType.Switch,
          isOn: this.lightEffectEnabled
        })
          .onChange((isOn: boolean) => {
            this.lightEffectEnabled = isOn;

            if (isOn) {
              this.interactiveEnabled = true;
            }

            this.refreshMaterial();
          })
      }
      .width('100%')
      .padding({ top: 12, bottom: 12 })
      .alignItems(VerticalAlign.Center)
    }
    .width('100%')
    .padding({
      left: 16,
      right: 16
    })
    .backgroundColor(Color.White)
    .borderRadius(20)
  }

  @Builder
  private lightColorPanel() {
    Column({ space: 10 }) {
      Text('光感颜色')
        .fontSize(15)
        .fontWeight(FontWeight.Medium)
        .fontColor('#17203A')
        .width('100%')

      Row({ space: 12 }) {
        Button('白色')
          .layoutWeight(1)
          .height(40)
          .fontSize(13)
          .fontColor(
            this.lightColorMode === 0
              ? Color.White
              : '#5065E8'
          )
          .backgroundColor(
            this.lightColorMode === 0
              ? '#5065E8'
              : '#EEF1FF'
          )
          .onClick(() => {
            this.lightColorMode = 0;
            this.refreshMaterial();
          })

        Button('蓝紫色')
          .layoutWeight(1)
          .height(40)
          .fontSize(13)
          .fontColor(
            this.lightColorMode === 1
              ? Color.White
              : '#5065E8'
          )
          .backgroundColor(
            this.lightColorMode === 1
              ? '#5065E8'
              : '#EEF1FF'
          )
          .onClick(() => {
            this.lightColorMode = 1;
            this.refreshMaterial();
          })
      }
      .width('100%')

      Text(
        '光感关闭时,颜色选择会保留,页面不会显示触点光感。'
      )
        .fontSize(12)
        .fontColor('#747C92')
        .lineHeight(19)
        .width('100%')

      Button('恢复默认参数')
        .width('100%')
        .height(42)
        .fontSize(14)
        .fontColor('#5065E8')
        .backgroundColor('#EEF1FF')
        .margin({ top: 4 })
        .onClick(() => {
          this.resetExperiment();
        })
    }
    .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('ImmersiveOptions 参数实验')
            .fontSize(16)
            .fontColor('#68708A')
            .width('100%')
        }
        .alignItems(HorizontalAlign.Start)
        .width('100%')

        this.sectionTitle(
          '当前应用状态',
          'DISABLE 状态会关闭页面中的沉浸式系统材质。'
        )

        this.materialStateCard()

        this.sectionTitle(
          '材质实验区',
          '切换参数后,直接观察卡片的颜色、文字、阴影和按压反馈。'
        )

        this.materialPreview()

        this.sectionTitle(
          '参数开关',
          '页面固定使用 THIN,每次只修改一项参数。'
        )

        this.parameterPanel()

        this.lightColorPanel()

        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')
  }
}
相关推荐
北墨NoLimit6 小时前
DevEco Code:在终端里用 AI 写鸿蒙应用
harmonyos
智塑未来8 小时前
打开快、切换顺、游戏稳:鸿蒙的日常流畅表现
游戏·华为·harmonyos
智塑未来1 天前
鸿蒙游戏体验手册:四种能力从性能到玩法逐一解锁
游戏·华为·harmonyos
math_hongfan1 天前
鸿蒙离线数据缓存高级架构:弱网预加载/离线数据优先级/同步冲突解决/上线后数据合并策略
学习·缓存·华为·架构·harmonyos·鸿蒙
math_hongfan1 天前
鸿蒙企业级数据存储高级架构:从读写分离到冷热数据分层/归档策略/数据生命周期管理最佳实践
人工智能·学习·华为·架构·harmonyos·鸿蒙
math_hongfan1 天前
鸿蒙存储异常高级排查:文件损坏检测/数据恢复/读写失败重试/磁盘空间预警系统性根治方案
学习·华为·harmonyos·鸿蒙
lilian2331 天前
Harmony os 技术实战|拼豆制图10:把取消、解析失败和保存失败写成可恢复状态机
java·javascript·华为·harmonyos
2501_919749031 天前
华为鸿蒙美缝剂实用APP—小羊美缝
华为·harmonyos
2501_919749031 天前
华为鸿蒙积攒年度高光APP—小羊高光
华为·harmonyos
智塑未来1 天前
鸿蒙7碰一碰智感交互——碰哪儿传哪儿
华为·harmonyos