请添加图片描述
请添加图片描述

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(阴影颜色)

支持两种写法:

  1. Color 枚举Color.OrangeColor.BlueColor.Gray
  2. 十六进制字符串#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 引擎已经做了大量优化(硬件加速、阴影缓存、脏区域检测),但在以下场景仍需注意:

  1. 大量卡片同时渲染:如果一屏同时显示 20+ 张带阴影的卡片,需要考虑性能消耗。
  2. 阴影动画:对 shadow 参数做连续动画可能会造成帧率抖动。
  3. 超大 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 设计最佳实践清单

  1. 光源一致性:整个 App 中所有组件的阴影偏移方向应一致。通常采用「左上光源」(阴影向右下偏移)。
  2. 层次克制:同一屏幕中不同层级的组件 elevation 差值建议在 4~8 vp 之间,差距太小分不清层级,差距太大显得突兀。
  3. 颜色温度:暖色 UI 搭配暖色阴影(橙色/棕色),冷色 UI 搭配冷色阴影(蓝色/紫色),能让整体氛围更和谐。
  4. 使用 elevation 作为设计令牌(Design Token):在项目中定义统一的 elevation 层级变量,如:
    const Elevation = {
      NONE: 0,
      LOW: 2,     // 列表项
      MEDIUM: 8,  // 卡片
      HIGH: 16,   // 浮层
      MAX: 24,    // 弹窗
    };
    
  5. 阴影与交互动效结合:点击或长按时略微提高 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 是一个教学性质的示例,你可以在此基础上:

  1. 提取通用卡片组件:将 shadowStyleCardcustomShadowCard 抽象成可复用的 @Component
  2. 定义全局阴影主题:在 CommonComponents.ets 中定义各层级的 elevation 和 shadow 常量。
  3. 与路由和状态管理结合:在真实的 App 页面中使用阴影来区分不同信息层级(如普通卡片 vs 精选卡片)。

10.3 学习建议

  • 动手修改参数:打开 DevEco Studio,修改 ShadowDemo.ets 中的 radius、offset、elevation 数值,运行后观察变化——这是理解阴影系统最快的方式。
  • 阅读官方 API 文档:在 DevEco Studio 中按住 Ctrl 点击 ShadowStyleShadowOptions,可以直接跳转到 SDK 的声明文件,查看完整的枚举值和接口注释。
  • 结合真实设计稿:将你手头的一个 App 页面截图,试着用 Shadow 和 elevation 来还原它的层次结构——练习是最好的学习。

10.4 未来展望

随着 HarmonyOS NEXT 的持续迭代,阴影系统也在不断进化:

  • 内阴影支持:当前 shadow 主要产生外阴影,未来可能加入 inset 支持产生内阴影。
  • 多光源阴影:支持同时叠加多个不同方向/颜色的阴影,模拟复杂光照环境。
  • 阴影动效标准化:可能推出与 animateTo 深度整合的阴影过渡 API。
Logo

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

更多推荐