鸿蒙原生开发进阶:ArkUI 空间化卡片从 0 到 1 (层级、投影与悬浮互动)

文章目录
前言:卡片设计的终极奥义在于“空间感知”
如果把现代操作系统的 UI 比作一个桌面,那么各种组件(如通知、音乐控制台、商品项)就像是散落在桌面上的实体卡片。自从以 Google 的 Material Design 为首的设计规范风靡全球后,卡片化设计 (Card-based Design) 已经成为大前端开发的绝对主流。
然而,一张完美的卡片绝不只是一块白色的矩形加上几个圆角。在 HarmonyOS NEXT 强调的“空间化设计”语境中,卡片应当具有物理厚度、悬浮层级(Elevation)、光线遮挡产生的投影,以及在用户手指触碰时表现出的弹性反馈。
本文将基于一份精炼的 ArkUI 空间化卡片实战源码,带您从零开始,逐帧拆解:如何在代码中构建 Elevation 层级系统?如何编写完美的 3D 翻转卡片?以及如何实现具有真实物理反馈的 拖拽悬浮效果?
🏔️ 一、 空间基石:层级堆叠与 translate.z
要在平面的屏幕上表现出卡片的叠放顺序,我们首先需要理解 Z 轴的概念。
1.1 核心源码拆解与分析
// ─── 一、层级卡片堆叠 ──────────────────
Stack() {
// 1. 底层
Column() { Text('底层') }
.translate({ x: 0, y: 0, z: 0 }) // Z轴为 0,紧贴屏幕
.shadow({ radius: 6, color: '#EF535040', offsetX: 0, offsetY: 4 })
// 2. 中层
Column() { Text('中层') }
.translate({ x: 12, y: -12, z: 20 }) // Z轴提升至 20
.shadow({ radius: 12, color: '#5C6BC060', offsetX: 0, offsetY: 8 })
// 3. 上层
Column() { Text('上层') }
.translate({ x: 24, y: -24, z: 40 }) // Z轴提升至 40,最高
.shadow({ radius: 20, color: '#26A69A70', offsetX: 0, offsetY: 12 })
}
1.2 UI 空间构建心法
Stack容器的覆盖法则:在Stack中,代码写在后面的组件,其视图层级(Z-Index)默认更高,会覆盖前面的组件。- 物理世界模拟:为了让错位排列的三张卡片看起来不像是画在同一张纸上,我们启用了
translate.z和递增的.shadow()。 - 上层卡片 (
z: 40):距离底层屏幕最远。根据光学原理,它在底面投下的阴影应该最虚化(散漫),所以其radius(模糊半径) 高达 20,offsetY达 12。 - 底层卡片 (
z: 0):紧贴基准面。其阴影最紧实锐利,radius仅为 6,offsetY为 4。
这种严谨的物理投影参数,是构建高级 UI 空间感的核心密钥。

📏 二、 构建系统的投影深度等级 (Elevation Levels)
在开发大型商业应用时,我们不能每次都凭感觉去写阴影参数。必须建立一套标准化的 Elevation(海拔/景深)系统。
2.1 阴影规范参考表(源码提取)
在源码的第二部分,作者非常严谨地定义了一套 4 级投影系统。这与高级设计系统的规范如出一辙。
| 层级名称 | 视觉隐喻与应用场景 | ArkUI Shadow 参数推荐配置 |
|---|---|---|
| L1: 微投影 | 紧贴页面,区分区域块边界。如:常规商品列表卡片、输入框背景。 | radius: 2, offsetY: 1, color: opacity(0.09) |
| L2: 浅投影 | 轻微浮起,表示可以点击或选中。如:底部导航栏、悬浮按钮。 | radius: 8, offsetY: 4, color: opacity(0.13) |
| L3: 中投影 | 明显的悬浮态,吸引注意力。如:下拉刷新面板、页面内的小型弹窗。 | radius: 16, offsetY: 8, color: opacity(0.18) |
| L4: 深投影 | 距离屏幕最高,阻断底层交互。如:全局 Dialog 警告框、底面抽屉。 | radius: 24, offsetY: 14, color: opacity(0.25) |
💡 核心原则:随着卡片层级(Z 轴高度)的上升,阴影的 radius(模糊度)、offsetY(下沉量)和 color的透明度系数(不透明度升高,颜色变深),必须遵循等比例的递增法则。
👆 三、 悬浮交互卡片 (Hover/Press Effect)
静态的卡片是没有灵魂的。当用户手指按下或点击时,卡片应该给予物理上的“弹压”反馈。
3.1 核心源码拆解
// ─── 三、悬浮交互卡片 ──────────────────
@State lifted: boolean = false // 核心状态变量:是否被抬起
Column() { Text('悬浮卡片') }
// 1. Z轴高度提升 与 Y轴视觉上升
.translate({ x: 0, y: this.lifted ? -20 : 0, z: this.lifted ? 40 : 0 })
// 2. 模拟拉近效果(体积变大)
.scale({ x: this.lifted ? 1.05 : 1, y: this.lifted ? 1.05 : 1 })
// 3. 阴影同步扩大
.shadow({
radius: this.lifted ? 28 : 8,
offsetY: this.lifted ? 16 : 4
})
// 4. 魔法动画绑定
.animation({ duration: 350, curve: Curve.FastOutSlowIn })
.onClick(() => { this.lifted = !this.lifted })
3.2 “状态驱动”打造极致丝滑
在传统的开发中,要让一个卡片实现点击上浮,需要写一堆 Animator 动画链。但在 ArkUI 中,这是一门艺术:
- 定义一个状态
@State lifted。 - 将
translate、scale、shadow的参数全部用三元表达式绑定到这个状态上。 - 当用户
onClick翻转状态时,底层的框架会自动计算出这三大属性的差值,配合.animation(),自动在 350 毫秒内为你渲染出一帧帧平滑的物理弹起效果。
🃏 四、 3D 翻转卡片 (Flip Card)
正反面翻转是营销活动(抽奖翻牌)、学习背诵应用中极具视觉冲击力的卡片交互形态。
4.1 核心源码拆解与 3D 数学
// ─── 四、3D翻转卡片 ──────────────────
@State flipped: boolean = false
Stack() {
// 正面
Column() { Text('正面') }
// 🔥 关键视角1:从 0 度翻向 180 度
.rotate({ x: 0, y: 1, z: 0, angle: this.flipped ? 180 : 0, perspective: 800 })
.opacity(this.flipped ? 0 : 1) // 翻转后隐藏自身
// 背面
Column() { Text('背面') }
// 🔥 关键视角2:初始在背后(-180),翻向 0 度
.rotate({ x: 0, y: 1, z: 0, angle: this.flipped ? 0 : -180, perspective: 800 })
.opacity(this.flipped ? 1 : 0) // 翻过来后显示
}
.animation({ duration: 500, curve: Curve.FastOutSlowIn })
.onClick(() => { this.flipped = !this.flipped })
4.2 为什么翻转 180 度这么难?
实现 3D 翻转卡片的核心难点在于“正反面衔接”。
- 初始状态错位:背面卡片的初始
angle必须是-180度。这意味着它此时背对着我们(即使能看见,字也是反的)。 - 同步起舞:当
flipped为 true 时,正面从0转到180,背面从-180转到0。这就好比将两张纸背靠背贴在一起,绕着中间的轴旋转。 - 视觉剔除 (Backface Culling):在真实的物理世界中,纸牌翻过去后你是看不到正面的。我们通过
.opacity与角度联动,当卡片转到背面时瞬间透明化,从而让出视线给转过来的另一面。

✋ 五、 拖拽悬浮卡片 (Drag Float):多维反馈协同
这是本篇最硬核、体验最极致的交互:当用户按住卡片在屏幕上拖拽时,卡片不仅仅是跟着手指移动,它会先“浮”起来,并且其底部的阴影会根据拖拽的方向产生动态偏移!
5.1 核心源码拆解
// ─── 六、拖拽悬浮卡片 ──────────────────
@State offsetX: number = 0
@State offsetY: number = 0
@State dragging: boolean = false
Column() { Text('按住拖拽') }
.translate({ x: this.offsetX, y: this.offsetY, z: this.dragging ? 50 : 0 }) // 跟随位移 + 抬高Z轴
.scale({ x: this.dragging ? 1.08 : 1, y: this.dragging ? 1.08 : 1 }) // 拾取放大
// 🔥 光影黑魔法:阴影偏移量跟随拖拽距离联动
.shadow({
radius: this.dragging ? 32 : 8,
offsetX: this.dragging ? this.offsetX * 0.15 : 0, // 阴影反向/同向偏移映射
offsetY: this.dragging ? 18 : 4
})
// 🔥 动画冲突规避:拖拽时无延迟,松手时弹回
.animation(this.dragging ? { duration: 0 } : { duration: 300, curve: Curve.FastOutSlowIn })
// 手势捕获
.gesture(
PanGesture({ distance: 5 })
.onActionStart(() => { this.dragging = true })
.onActionUpdate((e) => { this.offsetX = e.offsetX; this.offsetY = e.offsetY })
.onActionEnd(() => { this.dragging = false; this.offsetX = 0; this.offsetY = 0 })
)
5.2 大师级细节解析
- 光影相对论 (
offsetX: offsetX * 0.15):
当你把一个物理在桌面上向右拖动时,假设天花板上的灯光位置不变,物体在地面的投影相对物体本身会向左移动。
代码中通过极其精妙的数学映射系数0.15,让阴影的位移与卡片本身的位移产生关联,营造出了无与伦比的 3D 沉浸真实感。 - 状态拦截动画冲突:
在手势的onActionUpdate阶段,位移是非常高频的。如果此时仍然带有.animation(duration: 300),会导致卡片的移动总是“慢半拍”跟不上手指(犹如在水里拖拽)。
因此,源码使用三元表达式动态切换动画时长:拖拽中(duration: 0)要求绝对跟手;松开手归位(duration: 300)要求平滑弹回原点。这是 UI 动效处理上的神来之笔。
结语
从基础的 Z 轴叠放,到精确计算的 4 级 Elevation 投影体系;从惊艳的 3D 纸牌翻转,到包含光影连贯性的高阶拖拽反馈。卡片虽小,却折射出了整个移动端 UI 设计架构的广度与深度。
在 HarmonyOS ArkUI 强大的声明式动画引擎加持下,开发者不再需要面对晦涩的矩阵变换与触控分发拦截逻辑。你只需要梳理好“手势触发状态 -> 状态改变数据 -> 数据驱动界面”这一道美丽的流水线。
更多推荐



所有评论(0)