小雨同学 2026-09-08 09:49:50 发布前言
沉浸光感放进一个新页面时,接入过程通常比较轻。页面结构简单,组件数量有限,需要材质的区域创建 ImmersiveMaterial,随后通过 systemMaterial 应用到组件,基本就能完成第一轮实验。
已有项目里的情况会复杂许多。搜索框可能已经使用了半年,底部工具栏有自己的背景和阴影,Menu 分散在几个页面中,Sheet 也有现成的主题配置。这个阶段继续在每一个页面分别创建 ImmersiveMaterial,开始时改动不大,随着接入范围扩大,材质配置也会逐渐分散。
同样是搜索区域,有的页面使用 THIN,有的页面改成了 REGULAR;部分组件增加了 colorInvert,另外一些位置又单独设置了 materialColor;设备能力判断、普通背景回退和 HDS 配置也可能慢慢进入各个业务页面。
代码能够运行,后续维护却越来越难。我现在处理这类改造时,会先把材质相关的判断收拢起来。页面继续决定哪些位置需要沉浸光感,业务组件继续负责自己的内容和交互,材质样式、应用状态、HDS 能力查询以及普通背景回退,则交给单独的策略层处理。
普通 ArkUI 容器再增加一层比较轻的 ImmersiveSurface。搜索框、悬浮工具栏这类区域,只需要告诉 Surface 当前属于什么场景,具体使用哪一种材质、材质关闭以后用什么背景,都可以留在统一策略里面。
这样改造以后,后续调整会轻松不少。搜索区域需要更换材质样式,只需要修改策略;某些环境不适合继续使用材质,Surface 可以回到普通背景;业务页面原来的搜索、按钮、状态和事件都不用重新组织。
目前我的测试设备还没有 HarmonyOS 7 实机测试权限,因此这套封装先在模拟器中检查接口、组件结构、回退分支和页面调用。材质细节、自动反色、HDS 能力以及不同设备上的最终表现,仍然需要在具备权限的真机上重新确认。
一、已有页面先梳理接入位置
面对已经运行一段时间的项目,我通常不会先创建通用组件。我更习惯把现有页面过一遍,看看材质真正会出现在哪些地方。
原因在于,沉浸光感并不存在完全统一的接入入口。普通 ArkUI 容器、Popup、Menu、Sheet 和 HDS 框架组件都有自己的配置方式。为了追求表面上的统一,把这些入口全部塞进一个组件,最终得到的往往是一层职责越来越多的封装。
所以,第一步更适合先把页面分开。
| 现有位置 | 接入方式 | 封装方式 |
|---|---|---|
| 搜索框、普通卡片 | ImmersiveMaterial + systemMaterial | 适合使用 ImmersiveSurface |
| 悬浮工具栏、操作区域 | ImmersiveMaterial + systemMaterial | 适合使用 ImmersiveSurface |
| Popup、Menu、Sheet | 各自配置中的 systemMaterial | 复用 MaterialPolicy |
| HdsNavigation、HdsTabs、MiniBar | systemMaterialEffect | 保留 HDS 自己的材质配置 |
| 普通内容区域 | 普通背景 | 延续原来的页面结构 |
这样划分以后,ImmersiveSurface 的职责就比较清楚了。它只负责普通 ArkUI 容器。
Menu 和 Sheet 已经有自己的材质入口,只需要从策略层取得材质对象;HDS 继续使用自己的 MaterialType 和 MaterialLevel。策略层可以提供能力查询和判断依据,但不会把几套组件体系重新包成一种接口。
先确认工程是否已经满足 API 条件
现有项目开始改页面之前,我会先检查工程环境。我们目前围绕 API 26 的沉浸光感能力展开,因此至少需要确认:
HarmonyOS SDK API 26↓targetAPIVersion >= 26.0.0↓entry module 中配置 UIMaterial.state工程本身还没有进入对应 API 版本时,先完成工程升级和基础验证会更加稳妥。否则页面已经改了很多,后面发现目标 API、Module 配置或者材质状态没有准备好,排查范围会明显扩大。这一步完成以后,再开始决定业务场景和材质之间的对应关系。
样式留在策略层
搜索框、工具栏、Menu 和 Sheet 可以先定义成几个稳定场景:
export enum MaterialScene { SEARCH = 0, TOOLBAR = 1, MENU = 2, SHEET = 3}策略层内部再维护当前项目采用的映射:
SEARCH→ THINTOOLBAR→ THINMENU→ THICKSHEET→ ULTRA_THICK这组映射属于示例中的项目选择,不需要理解成 API 固定要求。它的作用主要是把技术参数从业务页面里收回来。
搜索页面只需要知道当前位置属于 SEARCH,工具栏只需要知道自己属于 TOOLBAR。以后页面设计发生变化,需要调整某个场景的材质样式,修改策略层就可以覆盖所有使用位置。
业务页面里也不会到处出现:
ImmersiveStyle.THINImmersiveStyle.THICKImmersiveStyle.ULTRA_THICK阅读页面代码时,注意力仍然可以留在业务本身。
MaterialState 也交给策略层判断
材质能否正常使用,还要结合当前应用状态。
这一类判断同样没有必要散落到多个页面里。
策略层可以统一提供:
static canUseArkUiMaterial(): boolean { const info: uiMaterial.MaterialInfo = uiMaterial.getMaterialInfo(); switch (info.state) { case uiMaterial.MaterialState.DEFAULT: case uiMaterial.MaterialState.ENABLE: return true; case uiMaterial.MaterialState.DISABLE: return false; default: return false; }}页面最终只关心一个布尔状态:
允许使用沉浸材质或使用普通背景DEFAULT、ENABLE、DISABLE 的具体判断继续留在策略层。
这样处理以后,普通背景回退也有了统一的入口。后面应用切换到不使用材质的状态,业务页面不需要分别处理每一张卡片。
HDS 能力可以集中查询,配置入口继续保持独立
已有项目里还可能同时存在 HDS 页面。
这时候 MaterialPolicy 可以顺便集中查询:
hdsMaterial.getSystemMaterialTypes()把当前环境返回的 HDS 材质类型整理出来。
不过,HDS 的实际材质配置仍然保留在自己的组件体系中。
HdsNavigation、HdsTabs 和 MiniBar 继续使用 MaterialType、MaterialLevel 和 systemMaterialEffect。普通 ArkUI 的 ImmersiveSurface 不参与这些配置。
整个关系可以整理成:
业务页面↓普通 ArkUI 区域→ ImmersiveSurface→ MaterialPolicy→ ImmersiveMaterialMenu / Sheet→ MaterialPolicy→ 各自 systemMaterialHDS 页面→ HDS MaterialType / MaterialLevel材质不可用→ 普通背景我比较喜欢这样的分层,因为每一套组件仍然按照自己的方式工作,公共部分只处理真正重复的材质策略。
策略边界确定以后,下面再处理真正需要复用的普通 ArkUI 容器。
二、ImmersiveSurface 保持轻量
我对这类基础组件的要求比较简单:解决外围材质问题就够了。ImmersiveSurface 不需要理解搜索逻辑,不需要处理按钮事件,也没有必要接管 Menu、Sheet 或页面数据。
它只负责几项外层配置:
scenematerialEnabledcolorInvertradiussurfaceHeightfallbackColorcontent这里比最初版本增加了 surfaceHeight。原因来自 ArkTS 组件调用方式。Surface 使用尾随闭包接收业务内容以后,不再在调用结束后继续链式设置 .width() 和 .height(),而是把容器高度作为明确参数传入。这样调用关系会更加稳定。
业务内容通过 BuilderParam 传入
ImmersiveSurface 自己不准备默认业务内容。调用页面使用尾随闭包把自己的 Builder 传进来:
@BuilderParamcontent: () => void;Surface 只负责在外面包一层材质或普通背景。
因此,Search 仍然属于页面:
@Builderprivate searchContent() { Search({ value: '', placeholder: '搜索项目中的内容' }) .width('100%') .height(48) .backgroundColor(Color.Transparent)}工具栏按钮也继续留在页面自己的 Builder 中。通用容器如果开始知道搜索关键词、收藏状态、分享事件或者业务数据,它很快就会从材质组件变成业务组件,后续复用范围反而越来越窄。
高度通过 Surface 参数明确传入
修正后的 Surface 增加:
surfaceHeight: number = 64;搜索区域使用:
ImmersiveSurface({ scene: MaterialScene.SEARCH, materialEnabled: this.materialEnabled, radius: 24, surfaceHeight: 64, fallbackColor: MaterialPolicy.getFallbackColor( MaterialScene.SEARCH )}) { this.searchContent()}工具栏则传入另一组高度:
ImmersiveSurface({ scene: MaterialScene.TOOLBAR, materialEnabled: this.materialEnabled, radius: 24, surfaceHeight: 72, fallbackColor: MaterialPolicy.getFallbackColor( MaterialScene.TOOLBAR )}) { this.toolbarContent()}这样调整以后,Surface 仍然没有参与业务布局设计。页面决定当前区域需要多高,再把这个结果传进去。宽度继续由 Surface 占满当前父容器,页面外围的边距、最大宽度和多设备断点仍然由业务页面控制。
材质可用时保持透明背景
当前应用允许使用沉浸材质时,Surface 内部只保留必要的容器属性:
Column() { this.content()}.width('100%').height(this.surfaceHeight).backgroundColor(Color.Transparent).borderRadius(this.radius).clip(true).systemMaterial( MaterialPolicy.getArkUiMaterial( this.scene, this.colorInvert ))业务内容继续显示在容器内部,外围材质由 MaterialPolicy 根据场景提供。这里也继续保留一个习惯:尺寸、背景和圆角等普通属性先确定,systemMaterial 放在后面。
以后遇到视觉异常,属性关系会更容易检查。
材质关闭以后直接进入普通背景分支
materialEnabled 为 false 时,Surface 使用普通背景:
Column() { this.content()}.width('100%').height(this.surfaceHeight).backgroundColor(this.fallbackColor).borderRadius(this.radius).clip(true)业务内容完全不需要变化。
搜索框仍然是原来的 Search,工具栏按钮仍然保留原来的事件,页面只是少了一层沉浸材质。
这也是我觉得通用 Surface 最有价值的地方。已有项目接入新视觉能力时,回退方案能够和正常方案共用同一套业务组件,后面处理兼容性和设备差异会轻松很多。
fallbackColor 最终应该回到项目主题资源
示例里的:
'#F2FFFFFF'只是为了让 Demo 可以独立运行。
真正接入项目以后,我更建议页面传入自己的主题资源:
fallbackColor: $r('app.color.search_surface_background')随后在浅色和深色资源目录中分别维护同名资源。这样,材质与应用主题之间也不会混在一起。
可以把职责理解成:
沉浸材质可用→ systemMaterial沉浸材质关闭→ fallbackColor浅色 / 深色主题→ 应用颜色资源材质负责材质,主题负责主题。这两层分开以后,后续关闭沉浸光感时,页面仍然能够自然回到项目原有的颜色体系。
colorInvert 继续由调用页面决定
Surface 会保留:
colorInvert: boolean = false;默认状态仍然关闭。
原因来自实际业务内容。自动反色是否适合某个区域,与内部使用的 Text、Search、SymbolGlyph、Image 以及对应颜色资源都有关系。通用 Surface 只能知道外层是什么场景,无法替内部所有前景内容完成条件判断。
因此,真正需要复杂背景适配的搜索框可以明确传入:
colorInvert: true普通工具栏没有这个需求时继续保持默认值。这种处理能让通用组件保持简单,也避免所有材质区域统一打开自动反色。
Menu 和 Sheet 复用策略层
普通 ArkUI 容器通过 Surface 统一以后,Menu 和 Sheet 依然保持自己的调用方式。
Menu 可以直接取得策略层材质:
Button('打开 Menu') .bindMenu( this.menuBuilder, { systemMaterial: this.menuMaterial } )这里的:
this.menuMaterial在页面初始化时已经通过下面的方式取得。
MaterialPolicy.getArkUiMaterial( MaterialScene.MENU)Sheet 也是相同思路。它继续使用自己的 systemMaterial,普通背景回退则根据 materialEnabled 决定:
backgroundColor: this.materialEnabled ? Color.Transparent : MaterialPolicy.getFallbackColor( MaterialScene.SHEET )这时候三类组件虽然接入形式不同,材质策略仍然来自同一个地方。
- 搜索框和工具栏使用 Surface。
- Menu 和 Sheet 使用自己的材质入口。
- HDS 继续使用 HDS 接口。
页面结构没有为了统一而统一,真正重复的策略却已经被收到了同一层。
组件结构稳定以后,真正进入已有项目时,还需要控制迁移节奏。
三、现有项目更适合分批迁移
通用组件写好以后,很容易产生一种冲动:既然已经封装完成,不如把项目里能找到的卡片全部替换掉。
我现在更愿意从几个高频位置开始。顶部搜索、底部操作栏、图片工具区域,这些组件面积相对有限,层级关系也比较明确。先让这些区域运行稳定,再决定是否继续处理 Menu、Sheet 和其他页面。这样做的好处是,出现问题以后更容易判断它来自策略、Surface 还是某一个业务页面。
工程环境先通过基础检查
真正开始迁移以前,可以先确认:
| 检查项目 | 当前建议 |
|---|---|
| HarmonyOS SDK | API 26 |
targetAPIVersion | 不低于 26.0.0 |
| Module 类型 | entry |
| UIMaterial metadata | 确认当前状态 |
| 测试环境 | 区分模拟器与真机 |
这些条件属于整个项目的基础。
基础环境没有确认以前,不适合同时修改大量业务页面。
普通 ArkUI、系统浮层和 HDS 保持各自入口
迁移过程中,我会一直保留这三个方向:
普通容器→ ImmersiveSurfaceMenu / Sheet 等系统浮层→ 自己的 systemMaterialHDS Navigation / Tabs / MiniBar→ systemMaterialEffectMaterialPolicy 可以被它们共同使用,但不会把三套能力硬塞进一个组件。这种边界能够让后面的升级更加容易。某套 API 发生变化时,修改范围也更加集中。
策略数量不要提前扩得太多
当前示例只有:
SEARCHTOOLBARMENUSHEET四种场景。
对一个刚开始接入的项目来说,已经够用。
以后真的出现稳定的新场景,例如播放器控制区或者固定顶部操作区,再增加对应策略。没有必要刚开始就创建十几种预设,把每一种微小差异都变成一个枚举。
策略数量少,后面更容易回答一个很重要的问题:为什么这里使用这种材质?
自动反色继续由具体页面验证
Surface 已经支持 colorInvert,迁移时仍然不能统一打开。
真正使用之前,需要继续检查:
材质厚度↓前景组件属性↓颜色资源↓设备能力↓实际复杂背景这些条件都和具体页面有关。通用策略负责提供能力,业务页面负责决定是否使用,这种分工会更加稳妥。
普通背景回退要真正跑一遍
我觉得这一项非常重要。页面接入完成以后,可以专门测试一次材质关闭状态。
调整应用级材质状态并重新构建安装以后,逐项检查:
- 搜索框是否回到普通背景
- 工具栏文字和按钮是否仍然清楚
- Menu 是否还能正常打开
- Sheet 是否仍然具有可读背景
- 页面布局是否发生明显变化
- 原来的业务事件是否继续执行
通用组件在材质开启时表现正常,只完成了一半。材质关闭以后,页面结构仍然稳定,业务操作仍然完整,回退策略才真正具有价值。
长列表不要因为有 Surface 就全部套一遍
通用组件存在以后,还有一个比较容易出现的问题:哪里都可以套,所以哪里都开始套。
长列表尤其需要克制。
例如:
顶部搜索→ ImmersiveSurface悬浮筛选→ ImmersiveSurface普通 ListItem→ 原有背景底部操作栏→ ImmersiveSurface这种结构已经能够建立材质层级。
普通内容项继续保持原来的背景,可以减少重复材质节点,也能让悬浮区域更加容易辨认。列表自身的数据加载、组件复用和性能优化,则继续按照原来的 ArkUI 长列表方案处理。
多设备布局继续由业务页面控制
ImmersiveSurface 增加了 surfaceHeight,并不代表它应该开始负责响应式布局。
手机上的搜索区域可能占据整行,平板上可能限制最大宽度,折叠屏展开以后也可能重新安排页面结构。这些仍然属于业务页面。
Surface 只保留:
材质场景材质状态圆角高度回退背景业务内容至于:
最大宽度左右边距断点单双栏横竖屏布局继续放在页面这一层处理。我希望这个组件以后能够出现在不同页面里,所以不会把某一种设备布局规则提前写进它内部。
HDS 查询主要用于诊断和设备策略
MaterialPolicy 里保留 HDS 能力查询以后,页面可以显示当前环境返回的材质类型,也可以把结果用于测试记录。
不过,普通 ArkUI Surface 不会因为 HDS 查询结果改变自己的工作方式。
HDS 能力查询更多服务于:
确认当前测试环境记录设备结果排查 HDS 材质差异决定 HDS 是否需要更保守的等级普通组件继续使用自己的 ArkUI 材质策略。这样能够避免两套体系互相牵连。
最后再回到真机和不同窗口形态
模拟器可以帮助我们确认:
MaterialPolicy 是否能够正常工作ImmersiveSurface 是否能够编译运行BuilderParam 是否能够正常传递业务内容MaterialState 回退分支是否正确Menu 和 Sheet 是否可以继续使用自己的材质到了正式项目阶段,还需要在目标设备上继续检查:
| 验证内容 | 主要观察内容 |
|---|---|
| 材质细节 | 通透度、阴影、背景层次 |
| 自动反色 | 复杂背景中的前景可读性 |
| 交互反馈 | 按压和光感 |
| 长列表 | 滚动稳定性 |
| 动态页面 | 动画手感和帧率 |
| HDS | 材质类型与等级 |
| 手机 | 小窗口布局 |
| 折叠屏和平板 | 宽窗口布局 |
| 回退状态 | 普通背景是否完整可用 |
当前这套三文件结构只能完成模拟器阶段的第一轮验证。真机权限具备以后,还需要重新覆盖材质效果、设备差异和性能结果。
总结
已有项目接入沉浸光感以后,我越来越倾向于把通用封装控制得小一些。
MaterialPolicy 管材质场景、MaterialState、HDS 能力查询和普通背景策略。
ImmersiveSurface 管普通 ArkUI 容器的材质显示和回退。
业务页面继续管理 Search、工具栏、Menu、Sheet 以及原来的业务状态。
这样的结构不会为了沉浸光感重新组织整个项目。修正后的 ImmersiveSurface 还增加了 surfaceHeight,业务内容通过必传的 @BuilderParam 尾随闭包传入。这样既避开了 ArkTS 中不合适的链式组件调用,也让 Surface 的尺寸职责更加明确。
我也会继续控制材质策略的数量。
搜索框、工具栏、Menu 和 Sheet 已经覆盖了一批常见场景。后续出现真正稳定的新需求,再往策略层增加新的场景。材质对象保持提前创建和复用,避免进入滚动、动画和业务事件中反复生成。
自动反色、主题、多设备和 HDS 继续保留各自的边界。自动反色由具体页面判断,浅色和深色背景交给应用资源,多设备断点留在业务布局,HDS 继续使用自己的材质配置。
普通背景回退也应该一直保留。对已有项目来说,我觉得这层保障很重要。材质关闭以后,搜索、工具栏、Menu 和 Sheet 仍然能够正常工作,页面布局也没有受到影响,这套封装才真正适合进入长期维护。
当前示例可以先在 HarmonyOS 7 模拟器中检查策略、Surface、页面调用和回退分支。正式进入项目以后,仍然需要在具备权限的真机上复核材质、自动反色、HDS、长列表性能以及不同窗口形态下的实际页面表现。
完整代码
MaterialPolicy.ets
/** * HarmonyOS 7 沉浸光感通用策略 * * 负责: * 1. 普通 ArkUI 材质场景映射。 * 2. MaterialState 判断。 * 3. HDS 材质能力查询。 * 4. 普通背景回退颜色。 */import { uiMaterial } from '@kit.ArkUI';import { hdsMaterial } from '@kit.UIDesignKit';import { BusinessError } from '@kit.BasicServicesKit';export enum MaterialScene { SEARCH = 0, TOOLBAR = 1, MENU = 2, SHEET = 3}export interface HdsMaterialCapability { typesText: string; supportsImmersive: boolean; queryFailed: boolean;}export class MaterialPolicy { /** * 固定材质对象提前创建。 * 页面滚动和动画期间直接复用。 */ private static readonly searchMaterial: uiMaterial.Material = new uiMaterial.ImmersiveMaterial({ style: uiMaterial.ImmersiveStyle.THIN }); private static readonly searchInvertMaterial: uiMaterial.Material = new uiMaterial.ImmersiveMaterial({ style: uiMaterial.ImmersiveStyle.THIN, colorInvert: true }); private static readonly toolbarMaterial: uiMaterial.Material = new uiMaterial.ImmersiveMaterial({ style: uiMaterial.ImmersiveStyle.THIN }); private static readonly toolbarInvertMaterial: uiMaterial.Material = new uiMaterial.ImmersiveMaterial({ style: uiMaterial.ImmersiveStyle.THIN, colorInvert: true }); private static readonly menuMaterial: uiMaterial.Material = new uiMaterial.ImmersiveMaterial({ style: uiMaterial.ImmersiveStyle.THICK }); private static readonly sheetMaterial: uiMaterial.Material = new uiMaterial.ImmersiveMaterial({ style: uiMaterial.ImmersiveStyle.ULTRA_THICK }); /** * 判断当前应用状态是否允许普通组件主动使用材质。 */ static canUseArkUiMaterial(): boolean { const info: uiMaterial.MaterialInfo = uiMaterial.getMaterialInfo(); switch (info.state) { case uiMaterial.MaterialState.DEFAULT: case uiMaterial.MaterialState.ENABLE: return true; case uiMaterial.MaterialState.DISABLE: return false; default: return false; } } /** * 页面显示当前 MaterialState。 */ static getArkUiStateText(): string { const info: uiMaterial.MaterialInfo = uiMaterial.getMaterialInfo(); switch (info.state) { case uiMaterial.MaterialState.DEFAULT: return 'DEFAULT'; case uiMaterial.MaterialState.ENABLE: return 'ENABLE'; case uiMaterial.MaterialState.DISABLE: return 'DISABLE'; default: return `UNKNOWN(${info.state})`; } } /** * 根据业务场景返回已经创建好的材质对象。 * * 静态方法中直接使用类名访问静态成员, * 避免 standalone-this 检查错误。 */ static getArkUiMaterial( scene: MaterialScene, colorInvert: boolean = false ): uiMaterial.Material { switch (scene) { case MaterialScene.SEARCH: return colorInvert ? MaterialPolicy.searchInvertMaterial : MaterialPolicy.searchMaterial; case MaterialScene.TOOLBAR: return colorInvert ? MaterialPolicy.toolbarInvertMaterial : MaterialPolicy.toolbarMaterial; case MaterialScene.MENU: return MaterialPolicy.menuMaterial; case MaterialScene.SHEET: return MaterialPolicy.sheetMaterial; default: return MaterialPolicy.searchMaterial; } } /** * Demo 使用的普通背景回退颜色。 * * 正式项目可以替换成 base / dark 主题资源。 */ static getFallbackColor( scene: MaterialScene ): ResourceColor { switch (scene) { case MaterialScene.SEARCH: return '#F2FFFFFF'; case MaterialScene.TOOLBAR: return '#EEFFFFFF'; case MaterialScene.MENU: return '#F7FFFFFF'; case MaterialScene.SHEET: return '#F7FFFFFF'; default: return '#F2FFFFFF'; } } /** * 查询当前环境支持的 HDS 材质类型。 */ static queryHdsCapability(): HdsMaterialCapability { try { const types: Array<hdsMaterial.MaterialType> = hdsMaterial.getSystemMaterialTypes(); if (types.length === 0) { return { typesText: '当前环境未返回 HDS 材质类型', supportsImmersive: false, queryFailed: false }; } const names: Array<string> = []; let supportsImmersive: boolean = false; for ( let index: number = 0; index < types.length; index++ ) { const type: hdsMaterial.MaterialType = types[index]; switch (type) { case hdsMaterial.MaterialType.NONE: names.push('NONE'); break; case hdsMaterial.MaterialType.ADAPTIVE: names.push('ADAPTIVE'); break; case hdsMaterial.MaterialType.IMMERSIVE: names.push('IMMERSIVE'); supportsImmersive = true; break; default: names.push(`UNKNOWN(${type})`); break; } } return { typesText: names.join('、'), supportsImmersive: supportsImmersive, queryFailed: false }; } catch (error) { const businessError = error as BusinessError; return { typesText: `查询失败 ${businessError.code} ` + `${businessError.message}`, supportsImmersive: false, queryFailed: true }; } }}ImmersiveSurface.ets
/** * HarmonyOS 7 通用沉浸材质容器 * * 负责: * 1. 普通 ArkUI 区域的 systemMaterial 接入。 * 2. MaterialState 关闭后的普通背景回退。 * 3. 圆角和容器高度。 * 4. 外部业务内容插入。 */import { MaterialPolicy, MaterialScene} from './MaterialPolicy';@Componentexport struct ImmersiveSurface { /** * 当前业务场景。 */ scene: MaterialScene = MaterialScene.SEARCH; /** * 当前是否允许显示沉浸材质。 */ materialEnabled: boolean = true; /** * 自动反色默认关闭。 */ colorInvert: boolean = false; /** * Surface 圆角。 */ radius: number = 24; /** * 高度由调用页面传入。 * * Surface 自身始终占满父容器宽度, * 避免在尾随闭包外继续调用 width / height。 */ surfaceHeight: number = 64; /** * 材质关闭以后使用的普通背景。 */ fallbackColor: ResourceColor = '#F2FFFFFF'; /** * 外部业务内容。 * * 当前组件通过尾随闭包初始化, * 因此不再设置引用 this 的默认 Builder。 */ @BuilderParam content: () => void; build() { if (this.materialEnabled) { Column() { this.content() } .width('100%') .height(this.surfaceHeight) .backgroundColor(Color.Transparent) .borderRadius(this.radius) .clip(true) .systemMaterial( MaterialPolicy.getArkUiMaterial( this.scene, this.colorInvert ) ) } else { Column() { this.content() } .width('100%') .height(this.surfaceHeight) .backgroundColor(this.fallbackColor) .borderRadius(this.radius) .clip(true) } }}Main.ets
/** * HarmonyOS 7 沉浸光感深度实战 10 * * 验证环境: * HarmonyOS SDK API 26 * HarmonyOS 7 模拟器 */import { MaterialPolicy, MaterialScene, HdsMaterialCapability} from './MaterialPolicy';import { ImmersiveSurface} from './ImmersiveSurface';@Entry@Componentstruct Main { @State private materialEnabled: boolean = false; @State private materialStateText: string = '尚未读取'; @State private hdsTypesText: string = '尚未查询'; @State private hdsSupportText: string = '未确认'; @State private showSheet: boolean = false; @State private lastActionText: string = '尚未执行操作'; /** * Menu 和 Sheet 自己已经提供 systemMaterial, * 因此只从策略层取得对应材质。 */ private readonly menuMaterial = MaterialPolicy.getArkUiMaterial( MaterialScene.MENU ); private readonly sheetMaterial = MaterialPolicy.getArkUiMaterial( MaterialScene.SHEET ); aboutToAppear(): void { this.loadMaterialEnvironment(); } /** * 读取当前应用状态和 HDS 材质能力。 */ private loadMaterialEnvironment(): void { this.materialEnabled = MaterialPolicy.canUseArkUiMaterial(); this.materialStateText = MaterialPolicy.getArkUiStateText(); const hdsCapability: HdsMaterialCapability = MaterialPolicy.queryHdsCapability(); this.hdsTypesText = hdsCapability.typesText; if (hdsCapability.queryFailed) { this.hdsSupportText = '查询失败'; } else if ( hdsCapability.supportsImmersive ) { this.hdsSupportText = '支持 IMMERSIVE'; } else { this.hdsSupportText = '未确认 IMMERSIVE'; } } @Builder private sectionTitle( title: string, description: string ) { Column({ space: 4 }) { Text(title) .fontSize(21) .fontWeight(FontWeight.Bold) .fontColor('#11182C') .width('100%') Text(description) .fontSize(13) .fontColor('#68708A') .lineHeight(20) .width('100%') } .width('100%') .alignItems(HorizontalAlign.Start) } /** * 当前运行环境。 */ @Builder private environmentPanel() { Column({ space: 10 }) { Row({ space: 12 }) { Text('MaterialState') .width('40%') .fontSize(13) .fontColor('#68708A') Text(this.materialStateText) .layoutWeight(1) .fontSize(13) .fontWeight(FontWeight.Medium) .fontColor( this.materialEnabled ? '#1A8F5D' : '#D06C35' ) .textAlign(TextAlign.End) } .width('100%') Divider() .color('#E8EBF2') Row({ space: 12 }) { Text('HDS MaterialType') .width('40%') .fontSize(13) .fontColor('#68708A') Text(this.hdsTypesText) .layoutWeight(1) .fontSize(13) .fontWeight(FontWeight.Medium) .fontColor('#17203A') .textAlign(TextAlign.End) .maxLines(3) } .width('100%') Divider() .color('#E8EBF2') Row({ space: 12 }) { Text('HDS 沉浸材质') .width('40%') .fontSize(13) .fontColor('#68708A') Text(this.hdsSupportText) .layoutWeight(1) .fontSize(13) .fontWeight(FontWeight.Medium) .fontColor('#5065E8') .textAlign(TextAlign.End) .maxLines(2) } .width('100%') } .width('100%') .padding(16) .backgroundColor(Color.White) .borderRadius(20) } /** * 搜索框业务内容。 * * Surface 负责外围材质, * Search 自己继续负责搜索组件。 */ @Builder private searchContent() { Search({ value: '', placeholder: '搜索项目中的内容' }) .width('100%') .height(48) .backgroundColor(Color.Transparent) } /** * 工具栏业务内容。 */ @Builder private toolbarContent() { Row({ space: 10 }) { Button('收藏') .layoutWeight(1) .height(38) .fontSize(12) .fontColor('#5065E8') .backgroundColor('#EEF1FF') .onClick(() => { this.lastActionText = '已执行收藏'; }) Button('分享') .layoutWeight(1) .height(38) .fontSize(12) .fontColor('#5065E8') .backgroundColor('#EEF1FF') .onClick(() => { this.lastActionText = '已执行分享'; }) Button('稍后') .layoutWeight(1) .height(38) .fontSize(12) .fontColor('#5065E8') .backgroundColor('#EEF1FF') .onClick(() => { this.lastActionText = '已加入稍后处理'; }) } .width('100%') .height('100%') .padding({ left: 12, right: 12 }) .alignItems(VerticalAlign.Center) } /** * Menu 内容。 */ @Builder private menuBuilder() { Menu() { MenuItem({ content: '复制链接' }) .onClick(() => { this.lastActionText = '已选择复制链接'; }) MenuItem({ content: '加入收藏' }) .onClick(() => { this.lastActionText = '已选择加入收藏'; }) MenuItem({ content: '稍后处理' }) .onClick(() => { this.lastActionText = '已选择稍后处理'; }) } } /** * Sheet 内容。 */ @Builder private sheetBuilder() { Column({ space: 16 }) { Text('现有项目设置') .fontSize(22) .fontWeight(FontWeight.Bold) .fontColor('#17203A') .width('100%') Text( 'Sheet 继续保留自己的业务内容,' + '材质统一从 MaterialPolicy 获取。' ) .fontSize(14) .fontColor('#68708A') .lineHeight(21) .width('100%') Column({ space: 12 }) { Row({ space: 10 }) { Text('接收更新提醒') .layoutWeight(1) .fontSize(14) .fontColor('#17203A') Toggle({ type: ToggleType.Switch, isOn: true }) } .width('100%') Divider() .color('#E8EBF2') Row({ space: 10 }) { Text('保留当前筛选') .layoutWeight(1) .fontSize(14) .fontColor('#17203A') Toggle({ type: ToggleType.Switch, isOn: false }) } .width('100%') } .width('100%') .padding(16) .backgroundColor('#66FFFFFF') .borderRadius(18) Button('完成') .width('100%') .height(42) .onClick(() => { this.lastActionText = 'Sheet 操作完成'; this.showSheet = false; }) } .width('100%') .height('100%') .padding({ left: 24, right: 24, top: 18, bottom: 24 }) .alignItems(HorizontalAlign.Start) } /** * 普通业务内容继续使用普通背景。 */ @Builder private normalContentCard( index: number ) { Column({ space: 6 }) { Text(`普通内容区域 ${index}`) .fontSize(17) .fontWeight(FontWeight.Bold) .fontColor('#17203A') .width('100%') Text( '普通内容继续使用原页面样式,' + '无需全部改成沉浸材质。' ) .fontSize(13) .fontColor('#68708A') .lineHeight(20) .width('100%') } .width('100%') .height(104) .padding(16) .justifyContent(FlexAlign.Center) .alignItems(HorizontalAlign.Start) .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('通用组件与现有项目接入') .fontSize(16) .fontColor('#68708A') .width('100%') } .alignItems(HorizontalAlign.Start) .width('100%') this.sectionTitle( '当前环境', '策略层统一读取应用状态和 HDS 材质能力。' ) this.environmentPanel() this.sectionTitle( '搜索区域', 'Search 保持原业务结构,Surface 负责外围材质。' ) ImmersiveSurface({ scene: MaterialScene.SEARCH, materialEnabled: this.materialEnabled, radius: 24, surfaceHeight: 64, fallbackColor: MaterialPolicy.getFallbackColor( MaterialScene.SEARCH ) }) { this.searchContent() } this.sectionTitle( '悬浮工具栏', '按钮和业务事件继续保留在工具栏内部。' ) ImmersiveSurface({ scene: MaterialScene.TOOLBAR, materialEnabled: this.materialEnabled, radius: 24, surfaceHeight: 72, fallbackColor: MaterialPolicy.getFallbackColor( MaterialScene.TOOLBAR ) }) { this.toolbarContent() } this.sectionTitle( '普通内容', '页面主体没有必要全部使用沉浸材质。' ) this.normalContentCard(1) this.normalContentCard(2) this.sectionTitle( '系统浮层', 'Menu 和 Sheet 继续使用各自的材质入口。' ) Row({ space: 12 }) { Button('打开 Menu') .layoutWeight(1) .height(42) .bindMenu( this.menuBuilder, { systemMaterial: this.menuMaterial } ) Button( this.showSheet ? 'Sheet 已打开' : '打开 Sheet' ) .layoutWeight(1) .height(42) .onClick(() => { this.showSheet = true; this.lastActionText = 'Sheet 已打开'; }) .bindSheet( $this.showSheet, this.sheetBuilder(), { height: 360, dragBar: true, backgroundColor: this.materialEnabled ? Color.Transparent : MaterialPolicy .getFallbackColor( MaterialScene.SHEET ), systemMaterial: this.sheetMaterial } ) } .width('100%') Column({ space: 6 }) { Text('最近一次操作') .fontSize(14) .fontWeight(FontWeight.Medium) .fontColor('#17203A') .width('100%') Text(this.lastActionText) .fontSize(13) .fontColor('#68708A') .lineHeight(20) .width('100%') } .width('100%') .padding(16) .backgroundColor(Color.White) .borderRadius(20) Text( '模拟器用于确认策略、组件和回退分支,' + '最终材质与设备差异仍需真机验证。' ) .fontSize(12) .fontColor('#747C92') .lineHeight(19) .padding({ top: 4, bottom: 24 }) .width('100%') } .width('100%') .padding({ left: 20, right: 20, top: 24, bottom: 24 }) } .width('100%') .height('100%') .backgroundColor('#F4F6FB') }}暂无评论数据
发布
小雨同学
产品总监、独立开发者社群主理人、资深全栈工程师,HarmonyOS应用开发者高级认证,PMP认证,CSDN博客专家,鸿蒙极客,Trae Fellow,阿里云社区专家博主、51CTO 博客专家、OpenTiny 优秀布道师、科大讯飞荣誉讲师。
帖子
提问
粉丝
【HarmonyOS 7 沉浸光感深度实战】10 通用组件封装与现有项目接入
2026-09-08 09:49:50 发布【HarmonyOS 7 沉浸光感深度实战】 09 常见冲突、性能边界与降级策略
2026-09-06 10:39:27 发布
0
京公网安备:11010502051901号