请添加图片描述

前言:告别纯平,拥抱空间计算时代

作为一名移动端开发者,回顾 UI 设计的发展史,我们经历了从早期的拟物化(Skeuomorphism)到扁平化(Flat Design),再到如今的材质设计(Material Design)和空间化设计(Spatial Design)。

随着 HarmonyOS NEXT 以及各类折叠屏、甚至 AR/VR 设备的普及,用户对屏幕的感知早已不再局限于二维的 X 轴与 Y 轴。引入 Z 轴的概念,通过光影、透视、景深和视差来构建具备三维空间感的用户界面,已经成为现代顶级 App 拔高视觉体验的必修课。

在传统的移动端开发中,要实现复杂的 3D 翻转、视差或者空间堆叠,往往需要借助底层的 OpenGL 矩阵变换,或者引入庞大的游戏引擎(如 Unity)。令人振奋的是,HarmonyOS 的 ArkTS 声明式 UI 框架为我们提供了极其强大且易用的原生空间变换 API。

本文将基于一份精心编写的 “HarmonyOS 空间化设计实战” 源码,带您从零开始,深度剖析如何通过 rotateshadowperspectivetranslatescale 这五大核心武器,将扁平的 UI 转化为充满生命力的 3D 空间。


🧭 原理基石:理解 ArkTS 的 3D 坐标系与透视投影

在深入代码之前,我们必须先建立空间坐标系的数学共识。在 ArkTS 中,屏幕的坐标系如下:

  • X 轴:水平向右为正。
  • Y 轴:垂直向下为正。
  • Z 轴:垂直于屏幕指向用户眼睛的方向为正。

当我们在屏幕上绘制 3D 效果时,实际上是在做透视投影(Perspective Projection)。近大远小的物理规律可以通过简单的数学模型来表达。假设相机的距离为 d d d(即 perspective 属性),原本在 Z Z Z 位置的物体,投影到屏幕上的缩放因子为:

S c a l e = d d − Z Scale = \frac{d}{d - Z} Scale=dZd

由此可见,当 d d d(perspective)的值越小,相机离物体越近,产生的透视形变(3D 效果)就越剧烈;当 d d d 趋近于无穷大时,透视投影就退化成了正交投影(也就是我们平时看到的 2D 纯平效果)。


一、 3D 旋转卡片:打破二维枷锁的入场券

要让一张卡片具有 3D 观感,最直接的方式就是让它绕着 X 轴或 Y 轴旋转,并辅以景深。

1.1 核心源码拆解

// ==================== 一、3D旋转卡片 ====================
Column() {
  Text('3D').fontSize(36).fontWeight(FontWeight.Bold).fontColor('#fff')
  Text('旋转').fontSize(18).fontColor('#fff').opacity(0.8)
}
.width(160).height(160)
.backgroundColor('#5C6BC0')
// 🔥 核心空间变换属性
.rotate({
  x: this.angleX,  // 绕 X 轴旋转的矢量分量
  y: this.angleY,  // 绕 Y 轴旋转的矢量分量
  z: 0,            // 绕 Z 轴旋转的矢量分量(类似平面内的旋转)
  angle: 45,       // 旋转的角度(度数)
  perspective: this.perspective // 视距 / 景深
})
.animation({ duration: 500, curve: Curve.FastOutSlowIn })
.shadow({ radius: 12, color: '#5C6BC060', offsetX: 0, offsetY: 8 })

1.2 深度解析

在上述代码中,.rotate() 接收一个对象,包含 x, y, z, angle, perspective 五个核心属性。

  1. **旋转轴矢量 [x, y, z]**:这定义了物体绕哪根轴旋转。
  • x: 1, y: 0, z: 0 时,卡片绕水平轴翻滚(Pitch),像翻日历。
  • x: 0, y: 1, z: 0 时,卡片绕垂直轴旋转(Yaw),像推开一扇门。
  1. angle(角度):旋转的绝对角度。如果是正值,根据右手螺旋定则,大拇指指向轴的正方向,四指弯曲的方向就是旋转方向。
  2. perspective(视距):这就是我们前面提到的相机距离 d d d
  • 如果不设置该值,系统默认为 0(表现为极其夸张的形变甚至不可见)。
  • 在日常开发中,设置在 800 ~ 1200 之间能获得最自然、最符合人眼视觉习惯的 3D 观感。源码中提供了一个 Slider 来动态调整该值,你可以亲自体验 perspective 对 3D 张力的影响。

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述


二、 Z 轴层级与空间阴影(Elevation):光与影的艺术

在没有 3D 眼镜的情况下,人类主要是通过光照和阴影来判断物体的远近和高低的。这是空间设计中最底层的心理学暗示。

2.1 核心源码拆解

// ==================== 二、Z轴层级与阴影 ====================
// 层级3:中阴影
Column() {
  Text('Z=2').fontSize(14).fontColor('#fff').fontWeight(FontWeight.Bold)
}
.width(80).height(80).backgroundColor('#5C6BC0').borderRadius(12)
// 🔥 核心阴影配置
.shadow({ 
  radius: 16,             // 阴影模糊半径,越大边缘越柔和
  color: '#5C6BC050',     // 阴影颜色(带有透明度的主题色)
  offsetX: 0,             // X 轴偏移量(模拟光源水平位置)
  offsetY: 8              // Y 轴偏移量(模拟光源垂直位置)
})

2.2 深度解析与最佳实践

ArkTS 提供了 .shadow() 属性来绘制弥散光影。

  • 模拟高度(Z 轴高度)的秘密
    当一个物体离桌面越近(Z 值小),它的阴影就越小、深、实radius 小,offsetY 小)。
    当一个物体被抬高(Z 值大),它的阴影就会扩散、变淡、偏移变大radius 大,offsetY 大)。
  • 高级 UI 技巧——同色系环境光
    在过去,我们习惯使用纯黑带透明度的阴影(如 rgba(0,0,0, 0.2))。但在现代空间设计中,阴影应当沾染物体本身的颜色。在源码中,背景色是 #5C6BC0,阴影色使用的是 #5C6BC050(加了透明度的原色)。这被称为彩色阴影(Colored Shadow),能让 UI 看起来更加通透、高级、有呼吸感,仿佛光线穿透了材质。

三、 3D 翻转卡片:打造惊艳的交互反馈

正反面翻转是电商营销页(翻牌抽奖)、学习类 App(单词记忆卡)中最常见的 3D 交互。

3.1 核心源码拆解

// ==================== 三、3D翻转卡片 ====================
Stack() {
  // 正面(初始角度 0 -> 翻转至 180)
  Column() { /* 正面内容 */ }
  .rotate({ x: 0, y: 1, z: 0, angle: this.flipped ? 180 : 0, perspective: 1000 })
  .opacity(this.flipped ? 0 : 1) // 翻转过去后隐藏
  .animation({ duration: 600, curve: Curve.FastOutSlowIn })

  // 背面(初始角度 -180 -> 翻转至 0)
  Column() { /* 背面内容 */ }
  .rotate({ x: 0, y: 1, z: 0, angle: this.flipped ? 0 : -180, perspective: 1000 })
  .opacity(this.flipped ? 1 : 0) // 翻转过来后显示
  .animation({ duration: 600, curve: Curve.FastOutSlowIn })
}
.gesture(TapGesture().onAction(() => { this.flipped = !this.flipped }))

3.2 深度解析:完美翻转的三要素

实现一个毫无破绽的 3D 翻转卡片,需要解决三个问题:

  1. 容器包裹:使用 Stack 组件将正反两面完全重叠放置。
  2. 角度错位设计:背面卡片的初始角度必须是 -180 度(或者 180 度)。当触发翻转时,正面从 0 转到 180,背面从 -180 转到 0。这就好比一个硬币的两面,在三维空间中同步旋转。
  3. 视觉剔除(Backface Culling):在真实世界中,卡片翻过去你就看不见正面了。由于 ArkTS 的 View 是双面渲染的,翻转 180 度后你依然能看到水平镜像(文字反过来)的正面。因此,我们必须配合 .opacity(),当翻转角度过半时,瞬间将转过去的卡片透明度设为 0,并将转过来的卡片设为 1。由于动画曲线的存在,这个交替过程在视觉上是无缝的。

在这里插入图片描述
在这里插入图片描述

四、 立体卡片排列:降维打击的等距视角 (Isometric View)

有时我们不需要真正去旋转物体,只需通过位移和阴影的组合,就能在 2D 平面上欺骗视觉,营造出 3D 的堆叠空间感。

4.1 核心源码拆解

// ==================== 四、立体卡片排列 ====================
Stack() {
  // 底层(最大偏移 + 最大阴影)
  Column() { Text('Layer 3') }
  .translate({ x: 8, y: 16, z: 0 }) // 向右下方偏移最多
  .shadow({ radius: 24, offsetX: 0, offsetY: 12 }) // 阴影最大,显得距离顶层最远

  // 中间层
  Column() { Text('Layer 2') }
  .translate({ x: 4, y: 8, z: 0 }) // 向右下方偏移适中
  .shadow({ radius: 16, offsetX: 0, offsetY: 8 })

  // 顶层(无偏移 + 小阴影)
  Column() { Text('Layer 1(顶层)') }
  .translate({ x: 0, y: 0, z: 0 }) // 原点
  .shadow({ radius: 8, offsetX: 0, offsetY: 4 }) // 贴近表面的小阴影
}

4.2 深度解析

这种手法被称为 Faux-3D(伪 3D)2.5D 设计

  • 利用 Stack 的特性,后声明的组件层级(Z-Index)最高,也就是距离用户最近。
  • 通过等比例递增 translateXY 值,卡片会在视觉上呈现向右下角斜向延伸的台阶状排列。
  • 这非常适合展示系统架构图、多层历史记录面板,或者让重要的运营卡片在一堆卡片中脱颖而出。

在这里插入图片描述

五、 视差效果 (Parallax):利用相对运动制造空间纵深

视差效应源于人类观察现实世界的方式:当我们在移动时,离我们近的物体在视网膜上滑过的速度快,而远处的山脉和天空则移动得很慢。

5.1 核心源码拆解

// ==================== 五、视差效果 ====================
// 背景层:移动最慢 (乘以 0.2 系数)
Column() { Text('背景') }
.translate({ x: this.parallaxOffset * 0.2, y: 0, z: 0 })

// 中景层:中速 (乘以 0.5 系数)
Column() { Text('中景') }
.translate({ x: this.parallaxOffset * 0.5, y: 0, z: 0 })

// 前景层:移动最快 (乘以 1.0 系数)
Column() { Text('前景') }
.translate({ x: this.parallaxOffset, y: 0, z: 0 })

5.2 深度解析与业务应用

在源码中,我们将所有图层绑定到同一个变量 parallaxOffset(比如是由 Scroll 滑动距离或者传感器重力感应产生的值)。

  • 给前景乘以 1.0 的权重,背景乘以 0.2 的权重。
  • 当滑动滑块时,图层之间发生错位滑动。即便它们在 Z 轴上完全是扁平重叠的,大脑也会自动脑补出它们之间的距离感。
  • 企业级业务场景:这被广泛应用于 App 首页顶部的大型 Banner 轮播图、个人主页的头部背景图上拉下拉视差、以及结合陀螺仪传感器的启动屏 3D 浮雕交互。

在这里插入图片描述
在这里插入图片描述

六、 空间缩放与透明度联动:构建终极沉浸感

真正的空间 UI 不仅仅是位置和角度的变化,它是多个物理量同时作用的交响乐。

6.1 核心源码拆解

// ==================== 六、空间缩放与透明度 ====================
Column() {
  Text('缩放空间感')
}
// 🔥 多维属性联动
.scale({ x: this.scaleValue, y: this.scaleValue })
.opacity(0.3 + this.scaleValue * 0.7) // 越远(scale小)越透明
.rotate({ x: 0, y: 1, z: 0, angle: (1 - this.scaleValue) * 30, perspective: 1000 }) // 远端略微侧偏
.shadow({
  radius: 8 * this.scaleValue,
  color: '#26A69A40',
  offsetY: 4 * this.scaleValue
})

6.2 深度解析

当一个物体向空间深处退去时,现实世界会发生什么?

  1. 大小:近大远小,所以 .scale() 变小。
  2. 清晰度:受空气中颗粒物影响,远处的物体会褪色(大气透视),所以 .opacity() 降低。
  3. 阴影:物体越远,投影在当前平面的阴影就越小越不可见。
  4. 视角偏转:加入微弱的 .rotate(),让卡片在后退时有一种“收入卡槽”的动态倾斜感。

只用一个 scaleValue 变量,通过数学公式映射驱动四个属性同步变化。这就是顶级动效工程师的秘密武器:状态机驱动(State-Driven Animation)


在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

📚 核心知识速查与性能调优指南

为了方便大家在实际业务中快速查阅并编写出高性能的代码,特整理以下表格与指南:

附表 1:ArkTS 空间变化 API 核心总结表

属性名称 接收参数与核心作用 适用业务场景
rotate {x, y, z, angle, perspective}。实现物体的三维旋转。 3D 翻转卡片、转盘抽奖、具有立体感的对话框弹出。
shadow {radius, color, offsetX, offsetY}。通过弥散光影模拟 Z 轴高度。 悬浮按钮 (FAB)、卡片容器、强调层级关系的弹窗。
translate {x, y, z}。实现平移。 构建视差背景、等距堆叠层、轮播图切换动效。
scale {x, y}。缩放大小。配合 opacity 可模拟 Z 轴距离。 页面路由切换、点赞/收藏的弹出动效、呼吸提示灯。

附表 2:如何选择合适的 Animation Curve(动画曲线)

在空间动效中,曲线直接决定了物体运动的“物理材质感”。

曲线类型 (Curve) 物理听觉感与体感 空间设计建议
Linear 机械、生硬、无重力 仅适用于雷达扫描、进度条等需要匀速循环的组件。不要用于 UI 元素的出现/消失
FastOutSlowIn 有质量、有阻尼感、极其丝滑 万金油曲线。适用于卡片 3D 翻转、弹窗出现,符合真实世界的抛体运动停止规律。
SpringMotion 弹性、果冻感、年轻化 适用于空间缩放(scale)、点赞等需要强反馈的交互节点,给人轻盈活泼的错觉。

⚠️ 高级预警:渲染性能优化 (Performance Tips)

在手机上渲染 3D 效果和软阴影是非常消耗 GPU 资源的,若滥用会导致帧率(FPS)下降。请务必牢记以下三点原则:

  1. 避免在列表项 (List Item) 中滥用巨型阴影。如果一个列表中有成百上千个带有复杂 .shadow() 的卡片在滑动,会引发严重的重绘(Overdraw)。对于静态列表卡片,建议直接使用设计师切好的带阴影的背景图替代代码计算。
  2. 开启离屏渲染 (renderGroup)。如果一个卡片内部嵌套了数十个子节点,且整张卡片正在进行 3D rotate 动画。请在卡片最外层容器加上 .renderGroup(true)。这会让系统先将整个卡片内容拍成一张 2D 纹理,再将纹理丢给 GPU 进行单张图片的 3D 旋转计算,能将 CPU 耗时降低 80% 以上。
  3. 透明度 (opacity) 陷阱。不要将不需要显示的组件 opacity 设为 0 就放任不管。对于彻底转到背面的组件,更好的做法是通过条件渲染 (if/else) 或 visibility(Visibility.Hidden) 将其从渲染树中剔除。

在这里插入图片描述

结语

在平面上堆砌功能只能造就一款能用的软件,而巧妙运用空间感、光影、透视与物理动效,才能打磨出一件充满灵性与呼吸感的艺术品。

HarmonyOS 的 ArkTS 框架为我们隐藏了底层图形学复杂的矩阵乘法与光栅化过程,用最为简洁的声明式语法(Declarative Syntax)赋予了每一位开发者成为空间设计师的超能力。

希望本文的逐行拆解与理论推导,能帮您打破二维平面的禁锢,在接下来的鸿蒙开发实战中,用更高级的 3D 动效去震撼您的用户!如果您觉得这篇万字长文对您有所启发,恳请点个赞、收藏并在评论区留下您的足迹,您的支持是我持续输出硬核干货的最大动力!

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐