列表滚动时,内容已经变了,顶部导航却像贴在玻璃外面;切到深色模式后,分割线又突然比标题更抢眼。沉浸光感要解决的不是“加一层半透明”,而是导航材质、层级和滚动反馈的一致性。本文使用 HarmonyOS 6.1.1 Release SDK(API 24)创建可运行 Demo,把界面状态、业务规则和系统能力边界分开验证。

验证范围:模拟器确认页面、状态机、异常提示和降级路径;需要传感器、受限权限、系统入口或 AGC 服务的部分,必须在支持的真机环境复验。本文不会把模拟状态描述成硬件能力已通过。

沉浸光感导航运行效果

为什么要改

导航层级扁平、滚动时标题和内容互相争抢注意力。如果页面直接依赖一次系统回调,回调迟到、重复或缺失都会把 UI 推到不可预测的状态。因此 Demo 先把视觉状态归一化为四种稳定状态:空闲、运行中、成功和降级。页面只消费快照,系统 Kit、网络或传感器被放在适配层。

关注点 直接调用方案 状态机方案
页面刷新 回调中零散修改多个字段 单一快照驱动
异常处理 每个按钮各写一套 统一进入 fallback
可测试性 依赖真实设备 业务规则可在模拟器复现
生命周期 容易遗漏解绑 页面退出统一清理

先定义状态而不是堆组件

本例的边界很明确:材质不可见时回退为高对比纯色导航。系统能力负责提供事实,业务层负责决定怎样展示和怎样继续主流程。这样做的价值是即使设备暂时不具备对应 SysCap,用户仍能完成核心任务,而不是看到一个永久转圈的页面。

场景 处理策略 用户可见结果
正常返回 更新快照并推进进度 显示当前视觉状态
权限拒绝 不重复骚扰请求 显示原因和设置入口
设备不支持 切换兼容分支 材质不可见时回退为高对比纯色导航
页面退出 停止任务、注销监听 再次进入从一致状态开始

核心实现

第一段代码定义纯业务状态。它不引用 UI,也不持有 Context,因此可以独立测试。

export enum VisualState {
  IDLE = 'idle', RUNNING = 'running', SUCCESS = 'success', FALLBACK = 'fallback'
}

export interface VisualSnapshot {
  state: VisualState;
  progress: number;
  message: string;
  updatedAt: number;
}

export function reduceVisual(current: VisualSnapshot, event: string): VisualSnapshot {
  if (event === 'START') {
    return { state: VisualState.RUNNING, progress: 25, message: '视觉状态已启动', updatedAt: Date.now() };
  }
  if (event === 'COMPLETE') {
    return { state: VisualState.SUCCESS, progress: 100, message: '视觉状态验证完成', updatedAt: Date.now() };
  }
  if (event === 'UNSUPPORTED' || event === 'DENIED') {
    return { state: VisualState.FALLBACK, progress: current.progress, message: '材质不可见时回退为高对比纯色导航', updatedAt: Date.now() };
  }
  return current;
}

第二段把快照直接绑定到 ArkUI。注意先更新可观察状态,再执行日志、缓存等副作用,避免按钮已点击但界面要切换页面后才刷新。

@Component
struct VisualPanel {
  @State snapshot: VisualSnapshot = {
    state: VisualState.IDLE,
    progress: 0,
    message: '等待操作',
    updatedAt: 0
  };

  build() {
    Column({ space: 12 }) {
      Text('沉浸光感导航').fontSize(24).fontWeight(FontWeight.Bold)
      Text(this.snapshot.message).fontSize(20).fontColor('#5B5CE2')
      Progress({ value: this.snapshot.progress, total: 100 })
      Button('执行验证').onClick(() => {
        this.snapshot = reduceVisual(this.snapshot, 'START');
      })
      Button('模拟不支持').onClick(() => {
        this.snapshot = reduceVisual(this.snapshot, 'UNSUPPORTED');
      })
    }.padding(20).width('100%')
  }
}

第三段覆盖开始、完成和不支持三条路径。真实项目可把断言接入项目测试框架,本文保留为最小可阅读示例。

function verifyVisualFlow(): string[] {
  const logs: string[] = [];
  let state: VisualSnapshot = {
    state: VisualState.IDLE, progress: 0, message: '等待操作', updatedAt: 0
  };
  state = reduceVisual(state, 'START');
  logs.push(state.state === VisualState.RUNNING ? 'PASS: start' : 'FAIL: start');
  state = reduceVisual(state, 'COMPLETE');
  logs.push(state.progress === 100 ? 'PASS: complete' : 'FAIL: complete');
  state = reduceVisual(state, 'UNSUPPORTED');
  logs.push(state.state === VisualState.FALLBACK ? 'PASS: fallback' : 'FAIL: fallback');
  return logs;
}

异常路径

最容易被忽略的并不是成功路径,而是恢复策略。重复事件要幂等,过期事件不能覆盖新状态,权限拒绝后不能循环弹窗,页面销毁后也不能继续写入旧组件。对于沉浸光感导航,建议额外记录 API 版本、设备类型、能力检测结果和错误码,但不要记录手机号、图像、关键点原始数据等敏感信息。

另外,Demo 中的“模拟不支持”按钮不是伪造系统结果,而是主动注入错误事件,用来证明主流程存在可见的降级出口。最终发布前仍需将适配层替换为官方 Kit 调用,并在支持设备上记录真实返回值。

模拟器验证结果

本地使用 HarmonyOS 6.1.1(API 24)Release SDK完成构建,在 1080×2340 的 API 24 Phone 模拟器上安装运行。实测页面可以进入、按钮会推进状态、进度条随快照刷新,异常分支能够回到明确的降级文案。构建与模拟器证明的是应用侧实现,不等价于设备专属能力认证。

验收时至少执行以下检查:正常路径连续触发两次;中途注入不支持事件;返回首页后再次进入;快速点击不产生越界状态;大字体下标题和按钮不被裁切;深浅色背景保持可读;页面退出后没有旧任务继续更新。

为了让结果可以被别人复现,建议在文章中同时记录四类证据:编译使用的 SDK 版本、运行设备与分辨率、触发步骤、可观察结果。只放一张最终页面截图无法证明异常分支存在;只贴构建成功也无法证明交互正确。本文把运行截图放在开头,让读者先确认 Demo 确实启动,再通过状态表和代码定位每一步由谁负责。

如果准备把示例迁入生产项目,不要直接复制页面文件。更稳妥的顺序是先复制状态类型和 reducer,为现有 Service 增加适配接口,再让现有页面订阅快照,最后接系统 Kit。这样每一步都能独立构建,遇到问题也能快速判断是 UI、业务规则、权限、设备能力还是服务配置导致。

结论

沉浸光感导航真正值得复用的不是一段调用代码,而是“系统事实—业务快照—ArkUI渲染—失败降级”的分层。先把状态和验收标准写清楚,再接入 Kit,可以明显减少只能在真机上反复猜测的问题。

建议把本文的验收表保留在项目评审中,并在每次 SDK 升级后重新执行正常、拒绝和不支持三条路径。

这里的结果也应与产品验收分开记录:工程验收关注可构建、可运行和可降级,产品验收还要关注文案是否准确、操作是否打断用户,以及能力带来的收益是否真实可感知。

官方资料:ArkUI相关开发文档

Logo

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

更多推荐