【共创稿事节】HarmonyOS 7设计师的 3D 思维养成:从平面构图到空间叙事
HarmonyOS 7设计师的 3D 思维养成:从平面构图到空间叙事
做平面 UI 久了,会养成一套肌肉记忆:先画框、再排内容、最后调留白和对齐。这套方法在 2D 里非常高效,但把它原样搬到空间里,做出来的东西往往"有纵深、没逻辑"——元素都浮起来了,用户却不知道自己该看哪、该往哪走。问题不在工具,在思维。
这一篇拆三个最需要补的思维转换,每个都配一个能直接落到 ArkUI 里的做法。
构图变成空间叙事:先想动线,再想画面
平面构图的核心目标是"一屏之内,信息读得顺"。空间设计的核心目标变成了"一段时间内,视角推得动"。
区别在于,平面作品是可以一眼看全的静态画面,空间作品是被观看的过程。你画的不是一张图,是一段路径:用户从哪进、先看到什么、被什么吸引往前走、最后停在哪。
落到实际操作上,平面稿通常先问"这块放什么";空间稿要先问三个问题:
- 用户的起始视点在哪?默认朝向是什么?
- 主任务在哪个焦平面?它需要独占还是和上下文共存?
- 从起始点到主任务,中间要不要给缓冲(一眼就看到全部,反而没有探索感)?
这三个问题决定了纵深怎么分层。把答案画成一张"叙事线"图,比直接摆元素有用得多。
图层变成空间层级:zIndex 不等于 Z 轴
设计工具里,图层是堆叠顺序;3D 工具里,层级是空间事实。这是最容易混淆的一处。
在 Figma 里把一层移到最上面,只是"盖住别的"。在空间里把元素放到 z 更大的位置,它同时获得了:更近的透视、更大的视觉尺寸(近大远小)、更强的注意力权重,以及可能的遮挡关系。一次赋值,四种后果。
所以在空间稿里,图层命名习惯要改。别再叫"背景层/内容层/弹窗层"这种按绘制顺序起的名字,改成按空间职责命名:
| 平面命名习惯 | 空间命名建议 | 含义 |
|---|---|---|
| 背景层 | 环境层 Environment | 提供氛围,不参与交互,通常 z 最远 |
| 内容层 | 主体层 Focus | 承当前主任务,占据舒适焦平面 |
| 浮层 | 上下文层 Context | 与主体相关的补充信息,略靠前或靠后 |
| 弹窗层 | 叠加层 Overlay | 需要打断当下的临时信息,最近处 |
改名字看着是小事,但它逼你在动手前就想清楚每个元素在纵深里的身份。改完名字还说不清"它在哪一层",说明这个元素的空间位置还没设计好。
像素变成体素:尺寸不再由画布决定
像素是绝对的,画布多大就是多大。体素是相对的,同一个元素在不同距离上,占屏尺寸不同。
这带来一个必须适应的约束:同一个组件,在不同 z 位置需要不同的视觉尺寸,但它的设计尺寸(比如 150vp 宽)应该不变。 尺寸变化交给透视和 scale,而不是改组件宽高。否则你会发现同一张卡片在近处清晰、远处文字糊成一团——因为透视缩放不会重排文字,只会把渲染结果整体缩掉。
实践里的做法是给每一层定一个"设计距离",再反推它需要的初始尺寸。近景按 1.0 倍设计,中景按 0.85 倍,远景按 0.7 倍,然后统一交给透视去处理。
案例:瀑布流商品页到空间货架
拿最常见的瀑布流商品页做对照。2D 版本是双列瀑布流,用户纵向滚动。空间版本做成一条弧形货架:中间商品正对用户、尺寸最大,两侧商品沿 Y 轴向外旋转并后退,形成"能走进去"的纵深感。
先看 2D 版本的结构(WaterFlow 双列),再把它改造成空间货架:
@Entry
@Component
struct SpatialShelfDemo {
@State private goods: string[] = ['箱包', '鞋靴', '配饰', '数码', '户外', '美妆', '家居', '运动'];
// 计算每个卡片相对中心的偏置:0 表示正中
private offsetOf(index: number): number {
const center = (this.goods.length - 1) / 2;
return index - center;
}
build() {
Column() {
// 空间货架:用 Stack 承载纵深,Row 决定横向顺序
Stack({ alignContent: Alignment.Center }) {
ForEach(this.goods, (item: string, index: number) => {
// offset 越大离中心越远,旋转角度、后退距离、缩放同步增大
const offset = this.offsetOf(index);
const angle = offset * -9; // 两侧向用户方向内收
const dz = -Math.abs(offset) * 34; // 越远越后退
const scale = 1 - Math.abs(offset) * 0.08; // 越远越小
Column() {
Row()
.width('100%')
.aspectRatio(0.8)
.backgroundColor('#DCE6F2')
.borderRadius(12)
Text(item).fontSize(14).margin({ top: 6 })
}
.width(120)
.padding(8)
.backgroundColor(Color.White)
.borderRadius(14)
.shadow({ radius: 10, color: '#12000000', offsetY: 3 })
// 关键:横向位移 + 纵深位移 + 绕自身 Y 轴旋转,三者用同一个 perspective
.translate({ x: offset * 96, y: 0, z: dz })
.rotate({ x: 0, y: 1, angle: angle, perspective: 1000 })
.scale({ x: scale, y: scale })
.zIndex(100 - Math.abs(offset)) // 越靠中心越靠前(仅影响绘制顺序)
}, (item: string) => item)
}
.width('100%')
.height(300)
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
}
}
跑起来会看到一条内收的弧线。这里有几个设计上的取舍要交代:
angle取负值让两侧卡片朝用户转,模仿真实货架把商品面向顾客的角度;如果取正值,就变成"背过去"的感觉,适合做"离场/次要"的语义。dz用绝对值,所以纵深的顶点在正中,两侧对称后退,符合"中心是主角"的叙事。- 所有卡片的
perspective统一为 1000,不统一的话远近视距不同,弧线会歪。
如果项目需要更真实的货架(比如能左右滑动浏览),可以把 offset 换成受手势控制的状态值,滑动时整体偏移,中间卡片被"选中"后放大。这样货架就从静态排布变成了可操作的导航结构。
平面思维和空间思维,六处对照
| 维度 | 平面思维 | 空间思维 |
|---|---|---|
| 组织逻辑 | 网格与分栏 | 纵深与焦平面 |
| 单位 | 像素/点(绝对) | 距离与角度(相对) |
| 层级 | 图层堆叠顺序 | 空间位置与遮挡 |
| 阅读 | 一眼看全 | 随时间推进 |
| 反馈 | 视觉样式切换 | 视觉 + 空间位移 + 声音 |
| 主角 | 构图中心 | 舒适焦平面上的焦点 |
叙事结构图
这张图讲的其实是一条动线。平面稿里同类关系通常画成"页面 A → 页面 B"的跳转图;空间稿里则应画成"视点如何移动、层级如何切换"的过程图。改掉画图习惯,思维才真的转过来。
几条经验
- 先画动线再定构图。空间设计里,顺序本身就是信息。
- 图层命名按空间职责,别按绘制顺序。命名不清,说明位置没想清。
- 尺寸交给透视,别手动改组件宽高。同一组件在不同
z上用scale区分。 - 同屏统一透视值。
perspective不一致,纵深关系就不可信。 - 别让每个元素都"3D"。真正的空间感来自少数几个关键层级,其余的保持安静。
注意一下下哦
- 把纵深当成装饰堆料。每个卡片都转角度、都加阴影,结果是满屏噪声,用户找不到焦点。
- 用
zIndex代替真实 Z。视觉上"看起来分层了",但透视、遮挡全对不上,一动就穿帮。 - 忽略文字的可读性。旋转角度超过约 15 度后,小字号文本会明显变形,别让语义关键信息处于大角度上。
- 纵深方向和交互方向打架。左右滑动却让元素在 Z 轴上跑,用户的物理直觉会抗拒。
- 只设计静态画面。空间稿没有过渡状态就等于没设计,进出场、选中、复位都得画出来。
#鸿蒙 #HarmonyOS 7(API 26)
更多推荐



所有评论(0)