【共创稿事节】HarmonyOS 7Z 轴层级:深度值与视觉优先级的映射规则
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.01m | 52%(近乎随机) | 看不出差别 |
| 0.05m | 68% | 隐约有前后感 |
| 0.10m | 85% | 明确有前后 |
| 0.20m | 94% | 层次清晰 |
| 0.30m | 97% | 层次很强 |
| 0.50m | 99% | 完全分离 |
结论是 depth 差至少 0.10m 才能让多数用户明确感知层级,0.20m 以上比较稳妥。我们商品详情页最终定的是主商品 depth=0、信息卡 depth=0.25、背景 depth=-0.50,三层间距 0.25m 和 0.50m,都在安全区。
这个结论只在我们测的 8 个人上成立,且面板是纯色高对比。换到低对比或半透明面板,分离感会弱一些,depth 差要再拉大。
约束面:depth 的物理边界
depth 不是想设多远都行。三个硬约束:
- 视锥远裁剪面:API 26 默认远裁剪面 10m,depth 超过这个值的组件直接不渲染。可以调远裁剪面但拉太远会降深度精度,远处两个组件 depth 差 0.1m 可能被判同层。
- 舒适对焦距离:人眼舒适对焦距离约 0.5m~3m。depth 太近(<0.3m)逼着对焦,看久了眼疲劳。depth 太远(>5m)文字看不清(上一篇算过 3m 要 38sp 起步)。
- 近裁剪面:默认 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,最后校验渲染和交互是否一致。
这条链的每一环都对应前文的一个约束,任意一环不通过就回退重算,不要写死数值。
// 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 得在文档里标出来。
更多推荐
所有评论(0)