2D 界面排层级靠 z-index,数值大的盖小的,规则简单粗暴。到了空间界面,层级变成物理深度——一个组件在 Z 轴上离用户多远,决定了它被看见的先后、被交互命中的优先级。两套逻辑映射不好,要么该在上面的被挡住,要么该优先响应的收不到事件。我们做空间商品详情页第一版,信息卡和主商品的 depth 差设小了,转角度时信息卡时隐时现,QA 提了 14 个 bug 全是层级问题。这篇把 depth 值到视觉/交互优先级的映射规则量化下来,给一张能直接用的排布表。

能力面:depth 属性做了什么

ArkUI 空间组件的 depth 属性单位是米,正值朝向用户,负值远离。渲染顺序按 depth 从远到近排序,远的先画近的后画——这就是画家算法。交互优先级反过来,近的先命中。

两件事看起来对称,实际有个坑:depth 差 0.05m 以下时渲染排序和交互命中可能不一致。渲染管线按 depth 排序时做了量化精度优化,差值太小会判为同层,但交互射线求交用的是原始浮点 depth,精度没丢。结果就是两个 depth 差 0.02m 的面板,渲染看着是 A 盖 B,点上去命中的是 B。这个坑我们撞了两次才定位到,官方文档没提精度边界。

API 26 提供 SpatialNode.depth 直接设,也支持 SpatialContainer.arrangeDepth(spacing) 按间距自动排。后者适合列表场景,前者适合精确定位。我们商品详情页用的是前者——主商品、信息卡、背景三层 depth 要精确控,自动排不好使。

depth 到视觉分离感的关系

depth 差多大才能让用户感知到"前面/后面"?我们做了个主观测试:两个同样大小的白色面板,depth 差从 0.01m 到 0.5m 递增,让 8 个同事判断"哪个在前"。

depth 差判断正确率主观分离感
0.01m52%(近乎随机)看不出差别
0.05m68%隐约有前后感
0.10m85%明确有前后
0.20m94%层次清晰
0.30m97%层次很强
0.50m99%完全分离

结论是 depth 差至少 0.10m 才能让多数用户明确感知层级,0.20m 以上比较稳妥。我们商品详情页最终定的是主商品 depth=0、信息卡 depth=0.25、背景 depth=-0.50,三层间距 0.25m 和 0.50m,都在安全区。

这个结论只在我们测的 8 个人上成立,且面板是纯色高对比。换到低对比或半透明面板,分离感会弱一些,depth 差要再拉大。

约束面:depth 的物理边界

depth 不是想设多远都行。三个硬约束:

  1. 视锥远裁剪面:API 26 默认远裁剪面 10m,depth 超过这个值的组件直接不渲染。可以调远裁剪面但拉太远会降深度精度,远处两个组件 depth 差 0.1m 可能被判同层。
  2. 舒适对焦距离:人眼舒适对焦距离约 0.5m~3m。depth 太近(<0.3m)逼着对焦,看久了眼疲劳。depth 太远(>5m)文字看不清(上一篇算过 3m 要 38sp 起步)。
  3. 近裁剪面:默认 0.1m,depth 比这还近的组件被裁掉。调近裁剪面到 0.01m 能贴脸显示,但会引入渲染伪影。

我们踩过一个坑:信息卡 depth 设 0.15m 想让用户"凑近看详情",结果 3 个同事测了 10 分钟都说眼睛累。0.15m 在近裁剪面以内但超出舒适对焦区,眼睛被迫强对焦。后来挪到 0.5m 才舒服。

跨设备的 FOV 差异

不同设备 FOV 不一样,同样的 depth 值在不同设备上视觉位置不同。Mate 70 Pro FOV 约 94°,某款眼镜 FOV 约 110°。depth=2m 的组件在 94° FOV 下占视野中央,在 110° FOV 下偏小偏边缘。

硬编码 depth 值会跨设备出问题。我们的做法是按 FOV 做归一化:effectiveDepth = rawDepth × (referenceFOV / deviceFOV),referenceFOV 取 94°。这样同一个 depth=2m 在不同设备上视觉位置接近。这个归一化不是官方方案,是我们自己凑的,在测过的 3 台设备上误差 <8%。

场景落地:空间商品详情页的三层排布

商品详情页要同时展示 3D 商品模型、价格信息卡、推荐列表。三者的 depth 排布:

层内容depth理由
主层3D 商品模型0m用户聚焦对象,居中
前层价格/规格信息卡+0.25m需要最优先交互,浮在商品前
后层推荐列表/背景-0.50m不抢注意力,需要时才看

交互优先级:前层 > 主层 > 后层。用户凝视价格卡时,即便视线射线也穿过商品模型,命中的是价格卡(depth 更近)。

把上面的排布规则串成一条可复用的判定链:先定层级角色,再算 depth,最后校验渲染和交互是否一致。

否

是

否

是

确定层级角色:前层/主层/后层

查 depth 差基线

按设备 FOV 归一化 effectiveDepth

depth 落在舒适对焦区 0.3m~5m?

写入 SpatialNode.depth

depth 差够大且渲染与交互一致?

定稿:前层优先,主层次之,后层最后

这条链的每一环都对应前文的一个约束,任意一环不通过就回退重算,不要写死数值。

// entry/src/main/ets/pages/SpatialProductDetail.ets
import { SpatialContainer, SpatialNode } from '@kit.ArkUI';

@Entry
@Component
struct SpatialProductDetail {
  build() {
    SpatialContainer() {
      // 后层:推荐列表,depth=-0.50,不抢焦点
      SpatialNode({ depth: -0.50 }) {
        RecommendList({ items: this.recommendations })
      }

      // 主层:3D 商品模型,depth=0,用户聚焦
      SpatialNode({ depth: 0 }) {
        ProductModel({ modelUri: this.product.modelUri })
      }

      // 前层:信息卡,depth=+0.25,最优先交互
      SpatialNode({ depth: 0.25 }) {
        PriceInfoCard({
          price: this.product.price,
          specs: this.product.specs
        })
      }
    }
  }
}

注意三层 depth 差:前层到主层 0.25m,主层到后层 0.50m。前层到主层差 0.25m 是因为信息卡和商品需要明确层级关系,用户要能一眼看出"价格浮在商品前面"。主层到后层差 0.50m 更大,是因为推荐列表不需要强层级感,放远一点不干扰主商品即可。

踩坑与取舍

坑一:depth 差太小导致层级闪烁

第一版信息卡 depth 设 0.05m,和主商品差 0.05m。用户转角度时,由于头部微动导致 depth 相对值波动 ±0.03m,信息卡和商品的 depth 差在 0.02m~0.08m 之间跳。渲染排序在"信息卡在前"和"同层"之间切换,信息卡时隐时现。

修复是把 depth 差拉到 0.25m,波动 ±0.03m 远小于间距,排序稳定。代价是信息卡离用户更近,遮挡商品更多。我们内部吵了一架——设计师想信息卡贴着商品不挡,我坚持要拉远保稳定。最后用半透明信息卡折中,depth 差 0.25m 但 alpha=0.85,能透过信息卡看到商品轮廓。

坑二:交互穿透到后层

推荐列表 depth=-0.50m,正常情况下被主商品遮挡不可交互。但用户手势射线从下往上划时,射线角度陡的时候会绕过主商品命中后层推荐列表,导致翻页。

API 26 的 SpatialNode.interactive 属性可以关掉后层交互,但关了之后推荐列表完全不可点,要看推荐得先把商品挪开。我们最终用的是 occlusionAwareInteractive:组件被遮挡时不响应交互,没被遮挡时正常响应。这样用户从侧面看推荐列表时能交互,正面看商品时推荐被挡不误触。

被放弃的方案:用 z-index 模拟 depth

一开始想偷懒,用 2D 的 z-index 给空间组件排层级,不设 depth。结果 z-index 在空间渲染管线里不参与深度排序,只影响同 depth 组件的绘制顺序。两个 z-index 差很大但 depth 相同的组件,渲染时确实一个盖一个,但交互射线求交时 depth 相同会返回先创建的那个,和 z-index 无关。z-index 模拟 depth 这条路走不通,老老实实用 depth。

注意一下下

  • depth 差至少 0.10m 用户才感知层级,0.20m 以上稳妥
  • depth 差 0.05m 以下渲染排序和交互命中可能不一致
  • depth 范围限在 0.3m~5m(舒适对焦区),太近眼累太远看不清
  • 远裁剪面默认 10m,别拉太远会降深度精度
  • FOV 跨设备不同,depth 值要按 FOV 归一化
  • 后层组件用 occlusionAwareInteractive 防交互穿透

下一步

目前 depth 排布是写死的,换一个页面要重新调。下一步打算把三层 depth 差做成主题变量,配合设备 FOV 自动算 effectiveDepth,让业务页面只声明"这是前层/主层/后层"不关心具体数值。另外那个 depth 差 0.05m 以下渲染和交互不一致的精度边界,想找官方确认一下是不是 bug 还是 by design,如果是 by design 得在文档里标出来。

Logo

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

更多推荐