请添加图片描述

前言:卡片设计的终极奥义在于“空间感知”

如果把现代操作系统的 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
  • translatescaleshadow 的参数全部用三元表达式绑定到这个状态上。
  • 当用户 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 大师级细节解析

  1. 光影相对论 (offsetX: offsetX * 0.15)
    当你把一个物理在桌面上向右拖动时,假设天花板上的灯光位置不变,物体在地面的投影相对物体本身会向左移动。
    代码中通过极其精妙的数学映射系数 0.15,让阴影的位移与卡片本身的位移产生关联,营造出了无与伦比的 3D 沉浸真实感。
  2. 状态拦截动画冲突
    在手势的 onActionUpdate 阶段,位移是非常高频的。如果此时仍然带有 .animation(duration: 300),会导致卡片的移动总是“慢半拍”跟不上手指(犹如在水里拖拽)。
    因此,源码使用三元表达式动态切换动画时长:拖拽中(duration: 0)要求绝对跟手;松开手归位(duration: 300)要求平滑弹回原点。这是 UI 动效处理上的神来之笔。
    在这里插入图片描述

结语

从基础的 Z 轴叠放,到精确计算的 4 级 Elevation 投影体系;从惊艳的 3D 纸牌翻转,到包含光影连贯性的高阶拖拽反馈。卡片虽小,却折射出了整个移动端 UI 设计架构的广度与深度。

在 HarmonyOS ArkUI 强大的声明式动画引擎加持下,开发者不再需要面对晦涩的矩阵变换与触控分发拦截逻辑。你只需要梳理好“手势触发状态 -> 状态改变数据 -> 数据驱动界面”这一道美丽的流水线。

Logo

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

更多推荐