1. 实例背景与设计思路
这一期是 HarmonyOS 应用实例里的第 178 个主题,名字叫“影子与投影模拟”。我第一次看到这个选题时也有点意外,阴影还能单独占一期?但真到了项目里才发现,卡片是否“立体”、按钮是否有“可按”的暗示、弹窗有没有“浮在页面上方”的层级感,几乎全靠阴影参数撑着。没有一套能灵活调整的投影方案,UI 做出来就是平的,交互反馈也显得很木。
1.1 场景痛点:为什么需要一套“阴影与投影模拟”方案
先讲一个我实际踩过的例子。有一次做商城首页的限时秒杀卡片,设计师给的视觉稿里,卡片下方是一个灰色半透明渐变。我当时直接把渐变图片切出来放在卡片底部,看起来倒也没什么问题,可一旦切到深色模式,这张渐变图就完全没法用了,灰底在深色背景上特别突兀。后来换成 ArkUI 原生的 shadow 属性,根据全局颜色变量动态生成投影颜色,深色模式下一行代码自动换色,问题才彻底解决。
这个经历让我想明白了一件事:在 HarmonyOS 应用里,想要把界面的立体感做稳定,必须掌握原生的投影能力,而不是依赖资源切图。本实例围绕三类高频需求展开:
- 卡片悬浮投影,比如首页瀑布流中的商品卡、天气卡、个人中心的功能卡。
- 按钮按压抬升投影,实现“按下去、松手弹回来”这种符合物理直觉的反馈。
- 落地投影,模拟一个图标或悬浮按钮距离桌面有一定高度时,底部出现的柔光渐隐阴影。
这三类需求覆盖了日常开发中至少八成和阴影相关的场景。把它们吃透,后面遇到再复杂的视觉稿,也能自己拆解成基础能力来组合。
1.2 知识点覆盖与预期效果
本项目用到 ArkUI 声明式语法中的几个核心能力:.shadow() 阴影、.scale() 缩放、.rotate() 旋转、.animation() 动画,以及 Slider 滑动组件、@State 状态管理、@Styles 公共样式。我们会用一个页面,把“静态阴影、动态阴影、可交互阴影”全部跑一遍。
最终做完的效果大概是这样的:
- 页面上方有三张示例卡片,分别使用普通阴影、多层复合阴影、落地渐变投影。
- 页面中间有一个实时参数调节区,通过三个
Slider分别控制阴影的模糊半径、纵向偏移和透明度,拖动时卡片阴影实时变化。 - 点击或长按卡片时,卡片微微缩小,阴影同时向外扩散,松开后恢复,模拟手指按下去再弹起的物理反馈。
这套东西做完以后,可以直接当模板用到自己的项目里。下面先从 API 原理讲起,再逐步给出可运行的代码。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 核心 API 与渲染原理
2.1 四个关键参数:radius、color、offsetX、offsetY
shadow 是 ArkUI 内置通用属性,支持在组件上绘制投影。最常用的写法是给一个 ShadowOptions 对象:
typescript复制.shadow({
radius: 20,
color: 'rgba(0, 0, 0, 0.3)',
offsetX: 0,
offsetY: 8
})
参数含义和注意点我用表格整理一下:
| 参数 | 类型 | 默认值 | 作用 | 实操注意点 |
|---|---|---|---|---|
| radius | number | 0 | 阴影模糊半径,值越大边缘越弥散 | 小卡片建议 8-12,大卡片建议 16-24,不要一上来就 30+ |
| color | ResourceColor | 半透明黑 | 阴影颜色 | 强烈建议用带透明度的颜色,不要用纯黑 |
| offsetX | number | 0 | 水平偏移,正方向向右 | 配合光照方向,一般右侧光源就用正值 |
| offsetY | number | 0 | 垂直偏移,正方向向下 | 顶部光源典型值是 4-12 |
这里最关键的是 radius 和 color 的 alpha 值配合。很多人第一次写阴影,把 radius 拉到 30,颜色用纯黑 #000000,出来的效果又脏又重,整个页面像蒙了一层灰。我的习惯是:颜色透明度控制在 0.15 到 0.4 之间,半径不要超过组件短边的四分之一。例如一张 120vp 宽的卡片,radius 用 12 到 18 就已经很有层次感,再大就“糊”了。
2.2 shadow 与 elevation、ShadowStyle 的差异
shadow 属性之外,ArkUI 还提供了另一个和阴影相关的属性 elevation。简单理解:
shadow是让你精确控制阴影的每一个参数,属于“自定义模式”。elevation是系统按照层级高度自动生成阴影,只需要给一个数值,适合快速实现“抬升感”。
这两者不是替代关系,而是分工不同。我在实际项目里的选型规则是:需要精细调节视觉风格、要做动态投影的时候用 shadow;只是想让按钮或卡片有点浮起感、又不想调参的时候用 elevation。比如页面右上角的“发布”悬浮按钮,我就直接 .elevation(8),一行搞定。
另外,如果你的 API 版本支持,还可以直接使用预设的 ShadowStyle,例如:
typescript复制.shadow(ShadowStyle.OUTER_DEFAULT)
这种写法方便是方便,但定制化空间很小。本实例要模拟不同维度的投影,所以主要使用 ShadowOptions 方式。
2.3 阴影的渲染边界:为什么有时候阴影是方的
理解 shadow 的渲染逻辑,能帮你避开很多坑。shadow 本质上是沿着组件本身的绘制区域,向外做一次模糊和偏移。也就是说,组件有明确的宽高、有背景色或背景图,阴影才有依附对象。如果组件本身透明、或者宽高为 0,阴影自然就无从谈起。
更常见的问题是圆角。假设你在父容器上设置了 borderRadius(16),但 shadow 却加在一个没有背景色、宽高为 0 的子节点上,出来的阴影就可能是矩形。正确做法是让 shadow 和作用了 borderRadius、backgroundColor 的节点保持同一个。ArkUI 的阴影会尽量跟随组件的圆角信
