鸿蒙原生 ArkTS 布局之道:shadow 阴影层叠布局深度解析


1 引言:为什么阴影如此重要
在移动端 UI 设计中,阴影(Shadow) 不仅仅是一种装饰效果,更是构建视觉层级、引导用户注意力的核心手段。Material Design 将「elevation(高度)」作为其设计语言的基石——组件通过高度差产生阴影,告诉用户「什么在最前面、什么在背后」。
HarmonyOS NEXT(API 24)在 ArkUI 框架中提供了完整且强大的阴影系统,包括:
.shadow(style: ShadowStyle)——使用系统预置的阴影样式.shadow(options: ShadowOptions)——使用自定义的阴影参数.elevation(value: number)——设置组件的 Z 轴高度,自动影响阴影视觉效果
这三者构成了鸿蒙原生阴影布局的「三剑客」。本文将通过一个完整的 ShadowDemo 应用,逐段解析每个 API 的用法、原理和最佳实践。
2 阴影三剑客:shadow / shadowStyle / elevation 概览
在深入代码之前,先用一张表格理清三者的关系。
| API | 类型 | 作用 | 典型场景 |
|---|---|---|---|
shadow(style: ShadowStyle) |
方法调用 | 使用预置阴影枚举,自动配置颜色、半径、偏移 | 常规卡片、列表项、弹窗 |
shadow(options: ShadowOptions) |
方法调用 | 手动指定阴影的模糊半径、颜色、偏移、填充 | 需要特殊颜色或自定义扩散效果 |
elevation(value: number) |
链式属性 | 设置 Z 轴高度(vp),影响阴影的范围和深度 | 搭配 shadow 使用,强化层级感 |
核心原则:
elevation控制「组件浮多高」,shadow控制「阴影长什么样」。两者组合使用效果最佳。
3 环境搭建与项目结构
3.1 创建项目
在 DevEco Studio 中新建一个 HarmonyOS NEXT 工程,选择 Empty Ability 模板,API 版本选择 9+(兼容 API 24)。
3.2 文件结构
entry/src/main/ets/
├── entryability/
│ └── EntryAbility.ets # 应用入口,加载 ShadowDemo 页面
├── pages/
│ ├── ShadowDemo.ets # ★ 阴影示例主页面
│ └── Index.ets # 其他页面(可选)
resources/base/profile/
└── main_pages.json # 路由注册
3.3 路由配置
在 main_pages.json 中注册新页面:
{
"src": [
"pages/ShadowDemo",
"pages/Index"
]
}
在 EntryAbility.ets 中将启动页指向 ShadowDemo:
windowStage.loadContent('pages/ShadowDemo', (err) => {
if (err.code) {
hilog.error(0x0000, 'Demo', 'Failed: %{public}s', JSON.stringify(err));
return;
}
});
4 完整代码逐段解析
下面我们按模块拆解 ShadowDemo.ets 的全部源码,每个模块配中文注释和设计思路说明。
4.1 数据层设计
首先是页面要展示的卡片数据类型和静态数据集。
/**
* 单张卡片的数据结构
*/
interface CardItem {
title: string; // 卡片标题
desc: string; // 卡片描述文案
color: ResourceColor; // 主题色(仅用于扩展,本例未使用背景色)
icon: string; // Emoji 图标
}
接口定义了四个字段,其中 color 预留用于后续扩展(如卡片左侧色条),当前示例中主要用于标识不同卡片。
接下来是组件结构体,使用 @State 装饰器声明响应式数据。
@Entry
@Component
struct ShadowDemo {
@State cardList: CardItem[] = [
{ title: '晨曦湖畔', desc: '宁静的湖面倒映着初升的朝阳,水波粼粼,鸟语花香。', color: '#5B8DEF', icon: '🌅' },
{ title: '林间小径', desc: '蜿蜒的碎石路穿过茂密的森林,阳光透过叶隙洒下斑驳光影。', color: '#4CAF50', icon: '🌲' },
{ title: '远山雪顶', desc: '巍峨的山脉披上银装,云雾缭绕如仙境般的壮丽景象。', color: '#FF7043', icon: '⛰️' },
{ title: '星河夜幕', desc: '璀璨的银河横跨夜空,繁星点点诉说着宇宙的浩瀚与神秘。', color: '#AB47BC', icon: '🌌' },
{ title: '古城遗迹', desc: '斑驳的石墙镌刻着千年的历史,每一块砖石都在低声讲述过去的故事。', color: '#8D6E63', icon: '🏛️' },
{ title: '花海田园', desc: '五彩斑斓的花田铺满山谷,微风拂过掀起层层芬芳的波浪。', color: '#EC407A', icon: '🌸' },
];
}
设计说明:6 张卡片涵盖山、湖、林、夜、城、花六种自然人文意象,每张卡片有独立的 Emoji 图标和描述文字,方便区分不同卡片的阴影效果。
4.2 页面骨架与滚动容器
build() 方法是整个页面的根布局。
build() {
Column() {
// 顶部标题栏
this.buildHeader()
// 可滚动内容区
Scroll() {
Column() {
// 章节 1:shadowStyle 预置样式
this.buildSectionTitle('📦 shadowStyle 预置样式')
this.buildShadowStyleDemo()
Blank().height(24)
// 章节 2:elevation 层级演示
this.buildSectionTitle('📐 elevation 层级感')
this.buildElevationDemo()
Blank().height(24)
// 章节 3:自定义阴影 + 层叠视图
this.buildSectionTitle('🎨 自定义 ShadowOptions 层叠')
this.buildCustomShadowDemo()
Blank().height(32)
}
.width('100%')
.padding({ left: 16, right: 16, top: 8, bottom: 24 })
}
.layoutWeight(1)
}
.width('100%')
.height('100%')
.backgroundColor('#F5F5F5')
}
布局结构一目了然:
Column (全屏,浅灰底)
├─ buildHeader() ← 顶部标题栏,深蓝色背景 + 白色文字
└─ Scroll (可滚动)
└─ Column (内容区,水平 padding 16vp)
├─ 📦 shadowStyle 预置样式 ← 三行长按两列卡片
├─ 📐 elevation 层级感 ← 三张并排卡片
└─ 🎨 自定义 ShadowOptions ← 自定义卡片 + Stack 三层叠
关键点说明:
- 外层
Column设置高度100%,背景为浅灰#F5F5F5,模拟真实 App 的页面底色,让白色卡片的阴影在灰色背景上更明显。 Scroll组件包裹全部演示内容,保证内容超出屏幕高度时可上下滑动。- 使用
Blank().height(24)在章节之间产生 24vp 的垂直间距。
标题栏的 @Builder 实现如下:
@Builder
buildHeader(): void {
Row() {
Column() {
Text('Shadow 阴影层叠布局')
.fontSize(22)
.fontWeight(FontWeight.Bold)
.fontColor('#FFFFFF')
Text('shadow + shadowStyle + elevation')
.fontSize(12)
.fontColor('#B0BEC5')
.margin({ top: 4 })
}
.alignItems(HorizontalAlign.Center)
}
.width('100%')
.height(80)
.backgroundColor('#2D3E50')
.justifyContent(FlexAlign.Center)
}
4.3 章节一:shadowStyle 预置样式(开箱即用)
这是最直观的阴影用法——直接传入 ShadowStyle 枚举值,系统自动配置合适的阴影参数。
@Builder
buildShadowStyleDemo(): void {
Column() {
Text('ShadowStyle 枚举提供了开箱即用的阴影样式,无需手动配置颜色/半径/偏移。')
.fontSize(13)
.fontColor('#78909C')
.lineHeight(20)
.width('100%')
.margin({ bottom: 12 })
ForEach(this.cardList, (item: CardItem, index: number) => {
if (index % 2 === 0) {
Row() {
// 左卡片:ShadowStyle.OUTER_DEFAULT_MD
this.shadowStyleCard(
item,
ShadowStyle.OUTER_DEFAULT_MD,
'ShadowStyle.OUTER_DEFAULT_MD'
)
Blank().layoutWeight(1)
// 右卡片:ShadowStyle.OUTER_FLATTEN_SM
if (index + 1 < this.cardList.length) {
this.shadowStyleCard(
this.cardList[index + 1],
ShadowStyle.OUTER_FLATTEN_SM,
'ShadowStyle.OUTER_FLATTEN_SM'
)
}
}
.width('100%')
.margin({ bottom: 12 })
}
})
}
.width('100%')
}
卡片构建器 shadowStyleCard 的定义:
@Builder
shadowStyleCard(item: CardItem, style: ShadowStyle, label: string): void {
Column() {
Text(item.icon)
.fontSize(32)
.margin({ bottom: 6 })
Text(item.title)
.fontSize(15)
.fontWeight(FontWeight.Bold)
.fontColor('#263238')
.margin({ bottom: 4 })
Text(item.desc)
.fontSize(12)
.fontColor('#607D8B')
.lineHeight(18)
.maxLines(2)
.textOverflow({ overflow: TextOverflow.Ellipsis })
Text(label)
.fontSize(10)
.fontColor('#90A4AE')
.margin({ top: 8 })
}
.width('48%')
.padding(14)
.backgroundColor('#FFFFFF')
.borderRadius(12)
.alignItems(HorizontalAlign.Center)
.shadow(style) // ← 关键:使用传入的预置阴影样式
}
演示效果:
| 卡片位置 | 使用的 ShadowStyle | 视觉效果 |
|---|---|---|
| 左侧卡片 | ShadowStyle.OUTER_DEFAULT_MD |
中等外阴影,偏移量适中,模糊半径约 12vp,适合展示普通卡片 |
| 右侧卡片 | ShadowStyle.OUTER_FLATTEN_SM |
扁平小阴影,偏移量小,模糊半径约 8vp,阴影更收敛、更柔和 |
经验之谈:如果卡片本身有
borderRadius,阴影的圆角会自动跟随卡片圆角,无需额外配置——这是 ArkUI 引擎自动处理的行为。
4.4 章节二:elevation 层级感(Z 轴高度)
elevation 属性是鸿蒙 ArkUI 中理解阴影设计的核心。它模拟了物理世界中物体离桌面的高度——数值越大,「浮」得越高,阴影越重越扩散。
@Builder
buildElevationDemo(): void {
Column() {
Text('elevation 控制组件在 Z 轴的高度值(单位:vp),值越大阴影越重越扩散,层级感越强。')
.fontSize(13)
.fontColor('#78909C')
.lineHeight(20)
.width('100%')
.margin({ bottom: 12 })
Row() {
this.elevationCard('🏔️', 'elevation: 2', 2, '微浮(低层级)')
Blank().layoutWeight(1)
this.elevationCard('🏔️', 'elevation: 8', 8, '悬浮(中层级)')
Blank().layoutWeight(1)
this.elevationCard('🏔️', 'elevation: 20', 20, '高浮(高层级)')
}
.width('100%')
}
.width('100%')
}
单张 elevation 演示卡片的构建器:
@Builder
elevationCard(icon: string, label: string, elev: number, desc: string): void {
Column() {
Text(icon).fontSize(30).margin({ bottom: 6 })
Text(label).fontSize(14).fontWeight(FontWeight.Bold).fontColor('#263238')
Text(desc).fontSize(11).fontColor('#78909C').margin({ top: 4 })
}
.width('30%')
.aspectRatio(1.0)
.padding(10)
.backgroundColor('#FFFFFF')
.borderRadius(12)
.alignItems(HorizontalAlign.Center)
.justifyContent(FlexAlign.Center)
.elevation(elev) // ← 关键:Z 轴高度
.shadow(ShadowStyle.OUTER_DEFAULT_MD) // 配合预置阴影
}
elevation 对比视觉梯度:
| elevation 值 | 阴影扩散程度 | 典型使用场景 |
|---|---|---|
| 2 vp | 阴影窄而浅,紧贴卡片边缘 | 列表项、小标签、行内卡片 |
| 8 vp | 阴影宽度适中,有明显上浮感 | 普通卡片、浮层、底部弹窗 |
| 20 vp | 阴影宽而虚,边缘羽化强烈 | 模态弹窗、Dialog、顶层浮层 |
重要:
elevation不仅仅是数字——ArkUI 引擎会根据 elevation 值自动计算阴影的模糊半径、偏移量和透明度衰减曲线。设置elevation(8)时,引擎内部会生成一组预计算好的阴影参数用于渲染。这也是为什么 elevation 和 shadow 组合使用时,阴影效果会更加自然。
4.5 章节三:自定义 ShadowOptions(精细控制)
当预置样式不能满足设计需求时,可以使用 ShadowOptions 接口自行定义阴影的每一个维度。
@Builder
buildCustomShadowDemo(): void {
Column() {
Text('通过 ShadowOptions 可精细控制阴影的每一维度:颜色、模糊半径、X/Y 偏移、是否填充。')
.fontSize(13)
.fontColor('#78909C')
.lineHeight(20)
.width('100%')
.margin({ bottom: 12 })
ForEach(this.cardList, (item: CardItem, index: number) => {
if (index % 2 === 0) {
Row() {
// 左卡片:暖色阴影 + 右下偏移
if (index < this.cardList.length) {
this.customShadowCard(
this.cardList[index],
{
radius: 16,
color: Color.Orange,
offsetX: 6,
offsetY: 6,
fill: true,
},
'暖色偏移阴影'
)
}
Blank().layoutWeight(1)
// 右卡片:冷色阴影 + 大模糊
if (index + 1 < this.cardList.length) {
this.customShadowCard(
this.cardList[index + 1],
{
radius: 24,
color: '#1A5B8DEF',
offsetX: 0,
offsetY: 8,
fill: false,
},
'冷色大模糊阴影'
)
}
}
.width('100%')
.margin({ bottom: 14 })
}
})
}
.width('100%')
}
自定义阴影卡片构建器:
@Builder
customShadowCard(item: CardItem, opts: ShadowOptions, label: string): void {
Column() {
Text(item.icon).fontSize(30).margin({ bottom: 6 })
Text(item.title).fontSize(15).fontWeight(FontWeight.Bold).fontColor('#263238')
Text(item.desc).fontSize(12).fontColor('#607D8B').lineHeight(18)
.maxLines(2).textOverflow({ overflow: TextOverflow.Ellipsis })
Text(label).fontSize(10).fontColor('#90A4AE').margin({ top: 6 })
}
.width('48%')
.padding(14)
.backgroundColor('#FFFFFF')
.borderRadius(12)
.alignItems(HorizontalAlign.Center)
.shadow(opts) // ← 关键:传入自定义 ShadowOptions 对象
}
两种自定义阴影的设计思路
暖色偏移阴影(左卡片):
{
radius: 16, // 模糊半径 16vp,中等柔化
color: Color.Orange,// 橙色阴影
offsetX: 6, // 向右偏移 6vp
offsetY: 6, // 向下偏移 6vp
fill: true, // 用阴影填充
}
这种配置模拟了左上角光源照射的效果。想象一盏台灯从卡片左上方照射,卡片会在右下方投下暖色的影子。offsetX: 6, offsetY: 6 让阴影明显向右下位移,产生强烈的立体感。
冷色大模糊阴影(右卡片):
{
radius: 24, // 模糊半径 24vp,大范围羽化
color: '#1A5B8DEF', // 半透明蓝色(#1A = 10% 透明度)
offsetX: 0, // 无水平偏移
offsetY: 8, // 向下偏移 8vp
fill: false, // 不填充,只保留投影
}
这种配置模拟了环境光(Ambient Light)漫反射的效果。大半径造成大面积羽化,半透明蓝色给阴影带来微妙的色调。fill: false 意味着阴影不填充到卡片本身,只保留外投射区域——这在某些设计风格中更受欢迎。
色彩透明度技巧:在
color中使用十六进制 8 位色值(如#1A5B8DEF),前两位1A是 Alpha 通道(00=全透明,FF=全不透明)。#1A≈ 10% 不透明度,非常适合做柔和的环境阴影。
4.6 章节四:Stack 三层叠加——终极演示
本章节的压轴内容:在 Stack 容器中通过三层组件的阴影叠加,营造强烈的视觉层级感。
@Builder
buildStackedShadowDemo(): void {
Stack() {
// ★ 第 1 层(底层):大模糊底色阴影 —— 模拟「底托」投影
Column()
.width('82%')
.height(220)
.backgroundColor('#FFFFFF')
.borderRadius(16)
.position({ x: 8, y: 16 })
.elevation(30)
.shadow({
radius: 40,
color: '#30000000',
offsetX: 0,
offsetY: 10,
fill: false,
})
// ★ 第 2 层(中层):卡片主体
Column() {
Text('🗺️').fontSize(42).margin({ bottom: 10 })
Text('层叠阴影卡片')
.fontSize(18).fontWeight(FontWeight.Bold).fontColor('#263238')
Text('底层大阴影 + 中层预置阴影 + 顶层自定义阴影,三层叠加营造强烈层级感。')
.fontSize(13).fontColor('#607D8B').lineHeight(20)
.textAlign(TextAlign.Center).margin({ top: 8 })
.padding({ left: 16, right: 16 })
Row() {
Text('shadowStyle')
.fontSize(11).fontColor('#FFFFFF')
.padding({ left: 10, right: 10, top: 4, bottom: 4 })
.backgroundColor('#5B8DEF').borderRadius(12)
Blank().width(8)
Text('elevation: 12')
.fontSize(11).fontColor('#FFFFFF')
.padding({ left: 10, right: 10, top: 4, bottom: 4 })
.backgroundColor('#AB47BC').borderRadius(12)
}
.margin({ top: 10 })
}
.width('80%')
.height(200)
.backgroundColor('#FFFFFF')
.borderRadius(16)
.alignItems(HorizontalAlign.Center)
.justifyContent(FlexAlign.Center)
.elevation(12)
.shadow(ShadowStyle.OUTER_DEFAULT_LG)
// ★ 第 3 层(顶层):装饰性小标签
Column() {
Text('✨').fontSize(22)
}
.width(48)
.height(48)
.backgroundColor('#FFF3E0')
.borderRadius(24)
.alignItems(HorizontalAlign.Center)
.justifyContent(FlexAlign.Center)
.position({ x: '78%', y: -12 })
.elevation(16)
.shadow({
radius: 12,
color: '#4DFFA726',
offsetX: 0,
offsetY: 4,
fill: true,
})
}
.width('100%')
.height(260)
.alignContent(Alignment.TopStart)
.margin({ bottom: 16 })
}
三层阴影的设计分析
| 层级 | 组件 | elevation | shadow 配置 | 设计意图 |
|---|---|---|---|---|
| 底层 (底托) | 白色 Column | 30 | radius: 40, color: #30000000, offsetY: 10, fill: false |
模拟底座的大面积投影,让整个卡片组「浮」在页面上 |
| 中层 (主体) | 带内容的卡片 | 12 | ShadowStyle.OUTER_DEFAULT_LG(大预置阴影) |
主体卡片浮在底托之上,使用系统大号预置阴影 |
| 顶层 (标签) | 圆形星星标签 | 16 | radius: 12, color: #4DFFA726, offsetY: 4 |
前景装饰元素,使用自定义金黄色阴影吸引视线 |
为什么这是「层叠布局」的核心?
在传统的扁平 UI 中,所有组件处于同一 Z 平面,只能通过颜色和边框来区分层级。而通过 elevation + shadow,我们可以在物理意义上将组件分布在不同的 Z 深度上:
- 底层 elevation=30 → 离页面最"高",阴影最大最虚,但它视觉上在最后面(因为被中层遮挡)
- 中层 elevation=12 → 离页面较低,阴影较小
- 顶层 elevation=16 → 虽然后设置,但因为它在 Stack 中位于最上层(最后声明的子组件在最上面),而且 position 定位让它突出主体,所以视觉上最靠前
这个例子展示了:elevation 控制的是「离页面的高度」,而 Stack 子组件的 Z 顺序由声明顺序决定。两者结合,可以创造出层次极其丰富的视觉体验。
5 ShadowOptions 接口详解
ShadowOptions 是自定义阴影的核心接口,完整定义如下:
interface ShadowOptions {
/** 模糊半径(单位 vp),值越大阴影边缘越柔和扩散 */
radius: number;
/** 阴影颜色,支持 ResourceColor(十六进制 #AARRGGBB 或 Color 枚举) */
color: ResourceColor;
/** 水平偏移量(单位 vp),正数向右,负数向左 */
offsetX: number;
/** 垂直偏移量(单位 vp),正数向下,负数向上 */
offsetY: number;
/**
* 是否用阴影颜色填充组件本身(可选,默认 true)
* - true:阴影颜色会叠加到组件背景上,形成类似「发光」或「内阴影」效果
* - false:阴影只投射到组件外部,组件本身保持原背景色
*/
fill?: boolean;
}
各字段详解
radius(模糊半径)
这是决定阴影视觉效果最重要的参数。理解它的两个极端:
- radius = 0:阴影没有模糊,成为一个硬边缘的投影——看起来像组件的一个副本位移到右下方。不推荐。
- radius 很小(2~6 vp):阴影清晰紧凑,适用于小元素,如按钮、标签。
- radius 适中(8~16 vp):日常卡片的首选范围,阴影柔和不夸张。
- radius 很大(20~40 vp):阴影大面积羽化,几乎看不出边缘,适用于「虚幻」的氛围感阴影。
color(阴影颜色)
支持两种写法:
- Color 枚举:
Color.Orange、Color.Blue、Color.Gray等 - 十六进制字符串:
#AARRGGBB格式,前两位是 Alpha 透明度
建议使用十六进制写法,因为可以精细控制不透明度:
// 6 位色值 = 完全不透明
color: '#5B8DEF'
// 8 位色值 = 带透明度
color: '#1A5B8DEF' // 10% 不透明
color: '#805B8DEF' // 50% 不透明
color: '#FF5B8DEF' // 100% 不透明
offsetX / offsetY(偏移量)
偏移量决定了阴影相对于组件本体的位置。常见的光照模拟:
offsetX > 0, offsetY > 0:光源在左上(右上阴影)offsetX < 0, offsetY > 0:光源在右上(左上阴影)offsetX = 0, offsetY > 0:光源在正上方(下方阴影),最常用的中性配置
fill(填充模式)
这个字段容易被忽略但非常重要。
- fill: true(默认):阴影颜色会叠加到组件本身的背景上。如果设置
color: Color.Orange,卡片自身也会染上一层橙色光晕,看起来像卡片本身「发光」。 - fill: false:阴影只「投射」到组件外部,卡片本身背景色不受影响。通常更接近 Material Design 的阴影行为。
经验:当使用彩色阴影做氛围点缀时建议
fill: false;当需要模拟「发光」或「霓虹」效果时用fill: true。
6 ShadowStyle 枚举全览
ShadowStyle 是 ArkUI 提供的一组预定义阴影样式,定义在 @kit.ArkUI 中。当前 API 24 版本支持的枚举值如下:
| 枚举值 | 说明 | 推荐场景 |
|---|---|---|
ShadowStyle.OUTER_DEFAULT_LG |
大号默认外阴影 | 弹窗、浮层、需要高关注度的卡片 |
ShadowStyle.OUTER_DEFAULT_MD |
中号默认外阴影 | 普通卡片、列表项、按钮 |
ShadowStyle.OUTER_DEFAULT_SM |
小号默认外阴影 | 标签、小图标、行内元素 |
ShadowStyle.OUTER_FLATTEN_LG |
大号扁平外阴影 | 大面积扁平卡片的柔和阴影 |
ShadowStyle.OUTER_FLATTEN_MD |
中号扁平外阴影 | 常规扁平风格卡片 |
ShadowStyle.OUTER_FLATTEN_SM |
小号扁平外阴影 | 极简风格、高密度列表 |
「Flat」和「Default」的区别:Default 系列阴影有更明显的偏移量,视觉上卡片倾斜感更强;Flat 系列阴影偏移量更小,模糊半径相对较大,看起来更「无缝」、更「扁平」。
如何选择?
- 不确定时,默认用
ShadowStyle.OUTER_DEFAULT_MD,这是最万金油的选项。 - 设计风格偏 Material 时用 Default 系列,偏 Fluent / 极简时用 Flat 系列。
- 大卡片(宽度 > 90% 屏幕)用
_LG,小卡片用_SM。
7 elevation 与 shadow 的协作机制
这是很多开发者容易混淆的地方。下面用一套「灯光实验」来类比说明。
7.1 单独的 elevation
当只设置 elevation 而不设置 shadow 时:
Column()
.backgroundColor('#FFFFFF')
.borderRadius(12)
.elevation(8) // 只设置高度,不显式设置 shadow
此时 ArkUI 引擎会自动生成一组默认的阴影参数用于渲染。这个自动生成的阴影是系统默认的灰色投影。效果看起来类似于 shadow(ShadowStyle.OUTER_DEFAULT_MD),但无法自定义颜色和偏移。
7.2 elevation + shadow 组合
当同时设置两者时:
Column()
.backgroundColor('#FFFFFF')
.borderRadius(12)
.elevation(8)
.shadow({
radius: 16,
color: '#335B8DEF',
offsetX: 0,
offsetY: 6,
fill: false,
})
此时:
elevation(8)告诉引擎「组件在 Z 轴上的高度为 8 vp」shadow({...})告诉引擎「阴影应该这样渲染」- 引擎将两者的信息合并,elevation 影响阴影的深度感,而 ShadowOptions 控制阴影的外观
7.3 物理模型
可以这样理解:
物理世界类比:
一本书放在桌面上(elevation = 小)→ 影子短而实
一本书悬在半空(elevation = 大) → 影子长而虚
你可以在书下面垫一张彩色纸(shadow color)→ 改变影子颜色
你可以把灯光调亮或调暗(shadow radius)→ 改变影子模糊程度
elevation 控制「书离桌面多远」
shadow 控制「灯光和纸长什么样」
7.4 建议的组合策略
| 场景 | elevation 值 | shadow 配置 |
|---|---|---|
| 扁平列表项 | 2~4 | ShadowStyle.OUTER_FLATTEN_SM 或不设 shadow |
| 普通卡片 | 6~12 | ShadowStyle.OUTER_DEFAULT_MD 或自定义 |
| 浮层/弹窗 | 16~24 | ShadowStyle.OUTER_DEFAULT_LG |
| 装饰性前景元素 | 8~16 | 自定义彩色阴影 |
8 常见误区与避坑指南
8.1 误区一:给透明或半透明组件设置阴影不生效
现象:给一个 opacity(0.5) 的卡片设置 shadow,阴影极淡甚至消失。
原因:阴影是基于组件最终渲染像素的 alpha 通道计算的。透明度越低的组件,阴影计算可用的「有效像素」越少。
解决方案:如果需要在半透明组件下显示阴影,考虑使用一个不透明的背景容器作为阴影载体,或者直接使用 fill: true 的自定义阴影。
8.2 误区二:阴影和圆角不匹配
现象:卡片有 borderRadius(12),但阴影看起来是直角的。
原因:早期版本的 ArkUI 中,阴影确实不会跟随圆角。但在 API 24(HarmonyOS NEXT 6.1.1) 中,阴影会自动跟随组件的 borderRadius,所以这个 bug 已经修复。
如果遇到阴影边缘仍然是直角的情况,请检查 SDK 版本是否 >= API 24。
8.3 误区三:elevation 值越大越好
现象:所有卡片都设置 elevation=24,希望阴影更明显。
问题:过大的 elevation 会让阴影变得极宽极淡,反而失去了视觉提示作用。而且 elevation 过大会影响 Z 轴排序行为,可能影响点击事件的穿透逻辑。
建议:普通卡片 4~12 vp 足矣,仅弹窗类使用 16~24 vp。
8.4 误区四:shadow 和 elevation 互相替代
现象:只使用 shadow 不设置 elevation,或只设置 elevation 不设置 shadow。
正确认识:两者不是替代关系,而是协作关系。elevation 提供了物理深度信息,shadow 提供了视觉外观。只设 elevation 可以工作但无法自定义;只设 shadow 可以自定义但缺少正确的 Z 轴深度感知。最佳实践是两者配合使用。
8.5 误区五:Stack 中所有层都加最大阴影
现象:Stack 的三层组件都设置了 elevation=30 和最大阴影,导致视觉上糊成一团。
对策:在同一 Stack 中,各层的阴影应该差异化。底层可以宽而虚,中层适中,顶层用彩色阴影点睛。这样每层的角色清晰,视觉层次丰富。
9 性能考量与最佳实践
9.1 阴影渲染的性能成本
阴影渲染涉及到模糊算法(Gaussian Blur),在 GPU 上属于相对昂贵的操作。在 API 24 中,ArkUI 引擎已经做了大量优化(硬件加速、阴影缓存、脏区域检测),但在以下场景仍需注意:
- 大量卡片同时渲染:如果一屏同时显示 20+ 张带阴影的卡片,需要考虑性能消耗。
- 阴影动画:对
shadow参数做连续动画可能会造成帧率抖动。 - 超大 radius 阴影:
radius > 40的阴影需要更大的离屏渲染缓冲区。
9.2 性能优化建议
优先使用 ShadowStyle 预置样式:
// ✅ 推荐:使用预置样式,引擎内部有缓存优化
.shadow(ShadowStyle.OUTER_DEFAULT_MD)
// 仅在需要特殊颜色/偏移时才使用自定义
.shadow({ radius: 16, color: '#30000000', offsetX: 0, offsetY: 6 })
控制阴影组件的数量:
对于列表中的大量卡片,考虑:
- 只给前几个可见项加阴影
- 或者使用
shadow(ShadowStyle.OUTER_FLATTEN_SM)(小扁平阴影,计算成本更低)
避免阴影重叠区域过大:
相邻两个带阴影的卡片如果间距太小,阴影会互相叠加,不仅视觉上脏乱,也会增加渲染区域的复杂度。建议卡片间距至少为 max(阴影 offsetY + radius) × 2。
9.3 设计最佳实践清单
- 光源一致性:整个 App 中所有组件的阴影偏移方向应一致。通常采用「左上光源」(阴影向右下偏移)。
- 层次克制:同一屏幕中不同层级的组件 elevation 差值建议在 4~8 vp 之间,差距太小分不清层级,差距太大显得突兀。
- 颜色温度:暖色 UI 搭配暖色阴影(橙色/棕色),冷色 UI 搭配冷色阴影(蓝色/紫色),能让整体氛围更和谐。
- 使用 elevation 作为设计令牌(Design Token):在项目中定义统一的 elevation 层级变量,如:
const Elevation = { NONE: 0, LOW: 2, // 列表项 MEDIUM: 8, // 卡片 HIGH: 16, // 浮层 MAX: 24, // 弹窗 }; - 阴影与交互动效结合:点击或长按时略微提高 elevation 值,配合过渡动画,可以给用户非常自然的「按压反馈」:
@State cardElevation: number = 8; // 按下时提升 .onTouch((event: TouchEvent) => { if (event.type === TouchType.Down) { this.cardElevation = 16; } else if (event.type === TouchType.Up || event.type === TouchType.Cancel) { this.cardElevation = 8; } }) .elevation(this.cardElevation) .animation({ duration: 200, curve: Curve.Friction })
10 总结与展望
10.1 核心知识点回顾
通过本篇文章和配套的 ShadowDemo 示例代码,我们系统地学习了 HarmonyOS NEXT(API 24)中阴影布局的三大核心 API:
| API | 用法 | 精髓 |
|---|---|---|
shadow(style: ShadowStyle) |
.shadow(ShadowStyle.OUTER_DEFAULT_MD) |
一键应用预置阴影,省心高效 |
shadow(options: ShadowOptions) |
.shadow({radius, color, offsetX, offsetY, fill}) |
精细控制阴影的每一维度 |
elevation(value: number) |
.elevation(8) |
定义 Z 轴高度,控制阴影深度感 |
10.2 从示例到生产环境的迁移路径
ShadowDemo.ets 是一个教学性质的示例,你可以在此基础上:
- 提取通用卡片组件:将
shadowStyleCard和customShadowCard抽象成可复用的@Component。 - 定义全局阴影主题:在
CommonComponents.ets中定义各层级的 elevation 和 shadow 常量。 - 与路由和状态管理结合:在真实的 App 页面中使用阴影来区分不同信息层级(如普通卡片 vs 精选卡片)。
10.3 学习建议
- 动手修改参数:打开 DevEco Studio,修改
ShadowDemo.ets中的 radius、offset、elevation 数值,运行后观察变化——这是理解阴影系统最快的方式。 - 阅读官方 API 文档:在 DevEco Studio 中按住 Ctrl 点击
ShadowStyle或ShadowOptions,可以直接跳转到 SDK 的声明文件,查看完整的枚举值和接口注释。 - 结合真实设计稿:将你手头的一个 App 页面截图,试着用 Shadow 和 elevation 来还原它的层次结构——练习是最好的学习。
10.4 未来展望
随着 HarmonyOS NEXT 的持续迭代,阴影系统也在不断进化:
- 内阴影支持:当前 shadow 主要产生外阴影,未来可能加入
inset支持产生内阴影。 - 多光源阴影:支持同时叠加多个不同方向/颜色的阴影,模拟复杂光照环境。
- 阴影动效标准化:可能推出与
animateTo深度整合的阴影过渡 API。
更多推荐


所有评论(0)