阴影与光照效果:让你的 HarmonyOS 6.0 界面真正"浮"起来

做过 UI 的都知道,一个界面有没有质感,往往不是差在颜色上,而是差在"影"上。纯平的卡片看着像纸片,加了阴影的卡片才像是浮在屏幕上方的东西。HarmonyOS 6.0 的 ArkUI 给我们提供了 shadow 属性,参数不多,但真正用好的开发者没几个——要么阴影太硬像黑框,要么太散像雾气,要么干脆看不出效果。

今天这篇文章,我就把 shadow 的每个参数掰开揉碎讲清楚,再配合实际场景给你讲怎么用出"设计感",而不是"加了个寂寞"。


在这里插入图片描述

一、shadow 属性全解析:四个参数各自管什么

ArkUI 的 shadow 接口签名如下:

shadow(value: ShadowOptions | ShadowStyle)

其中 ShadowOptions 是一个对象,包含四个字段:

参数 类型 必填 默认值 说明
radius number | Resource - 模糊半径
color Color | string | Resource 黑色半透明 阴影颜色
offsetX number | Resource 0 X 轴偏移量
offsetY number | Resource 0 Y 轴偏移量

还有一个 ShadowStyle 枚举类型,提供系统预设的阴影样式,比如 ShadowStyle.OUTER_DEFAULT_SMShadowStyle.OUTER_DEFAULT_MD 等,适合快速使用但定制性有限。

先看一个最基础的用法:

Column() {
  Text('基础阴影')
    .fontSize(16)
    .fontColor('#333333')
}
.width(200)
.height(100)
.backgroundColor(Color.White)
.borderRadius(12)
.shadow({
  radius: 20,
  color: '#66000000',
  offsetX: 0,
  offsetY: 4
})

这段代码的效果是:一个白底圆角卡片,下方有一道柔和的灰色阴影,视觉上卡片微微"浮起"。接下来逐个参数说清楚。


二、模糊半径 radius:阴影的"软硬"开关

radiusshadow 唯一的必填参数,它控制阴影边缘的模糊程度。值越大,阴影越柔和、扩散越远;值越小,阴影越锐利、越像一条硬边。

让我用代码对比一下不同 radius 的效果:

@Entry
@Component
struct ShadowRadiusDemo {
  build() {
    Column({ space: 20 }) {
      // radius = 0:几乎没有模糊,阴影像一条硬边
      Column() {
        Text('radius: 0')
          .fontSize(14)
          .fontColor('#333333')
      }
      .width(180)
      .height(80)
      .backgroundColor(Color.White)
      .borderRadius(8)
      .shadow({
        radius: 0,
        color: '#99000000',
        offsetX: 0,
        offsetY: 6
      })

      // radius = 10:轻微模糊,边缘开始变得柔和
      Column() {
        Text('radius: 10')
          .fontSize(14)
          .fontColor('#333333')
      }
      .width(180)
      .height(80)
      .backgroundColor(Color.White)
      .borderRadius(8)
      .shadow({
        radius: 10,
        color: '#99000000',
        offsetX: 0,
        offsetY: 6
      })

      // radius = 30:中等模糊,阴影自然扩散
      Column() {
        Text('radius: 30')
          .fontSize(14)
          .fontColor('#333333')
      }
      .width(180)
      .height(80)
      .backgroundColor(Color.White)
      .borderRadius(8)
      .shadow({
        radius: 30,
        color: '#99000000',
        offsetX: 0,
        offsetY: 6
      })

      // radius = 60:大范围模糊,阴影变成一层淡淡的雾
      Column() {
        Text('radius: 60')
          .fontSize(14)
          .fontColor('#333333')
      }
      .width(180)
      .height(80)
      .backgroundColor(Color.White)
      .borderRadius(8)
      .shadow({
        radius: 60,
        color: '#99000000',
        offsetX: 0,
        offsetY: 6
      })
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#F5F5F5')
    .padding(20)
  }
}

实际经验:大多数卡片场景,radius 在 15~30 之间效果最自然。低于 10 容易显得生硬,高于 50 就像一团雾气——除非你刻意要做那种毛玻璃远景的雾化效果,否则别开太大。

另外注意一点:radius 的单位是 px(物理像素),不是 vp。在高分辨率设备上,同样的数值视觉上会更细腻。如果你在不同设备上发现阴影粗细不一致,可能就是这个原因。


三、阴影颜色 color:#AARRGGBB 格式的坑

color 参数支持 Color 枚举、字符串和 Resource 三种写法,但最常用的是十六进制字符串。这里有个关键点:ArkUI 的颜色字符串格式是 #AARRGGBB,alpha 通道在前面,这和 CSS 的 #RRGGBBAA 是反的。

// 正确:AA 在前,RRGGBB 在后
shadow({ radius: 20, color: '#33000000' })  // 约20%透明度的黑色
shadow({ radius: 20, color: '#66000000' })  // 约40%透明度的黑色
shadow({ radius: 20, color: '#99000000' })  // 约60%透明度的黑色

// 错误:如果按CSS习惯写,透明度就跑到最后了
// '#00000033' 在ArkUI里会被解析为AA=00(全透明),阴影完全不可见

不同透明度的阴影效果差异极大:

@Entry
@Component
struct ShadowColorAlphaDemo {
  build() {
    Column({ space: 20 }) {
      Column() {
        Text('alpha: 10% (#1A000000)')
          .fontSize(14)
          .fontColor('#333333')
      }
      .width(180)
      .height(80)
      .backgroundColor(Color.White)
      .borderRadius(8)
      .shadow({ radius: 25, color: '#1A000000', offsetX: 0, offsetY: 4 })

      Column() {
        Text('alpha: 30% (#4D000000)')
          .fontSize(14)
          .fontColor('#333333')
      }
      .width(180)
      .height(80)
      .backgroundColor(Color.White)
      .borderRadius(8)
      .shadow({ radius: 25, color: '#4D000000', offsetX: 0, offsetY: 4 })

      Column() {
        Text('alpha: 50% (#80000000)')
          .fontSize(14)
          .fontColor('#333333')
      }
      .width(180)
      .height(80)
      .backgroundColor(Color.White)
      .borderRadius(8)
      .shadow({ radius: 25, color: '#80000000', offsetX: 0, offsetY: 4 })
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#F5F5F5')
    .padding(20)
  }
}

实际经验:浅色背景上的卡片阴影,alpha 值控制在 0.1~0.3 之间最舒服。超过 0.5 就会显得太重,像卡片底下压了一块黑砖。深色背景上可以适当提高到 0.3~0.5,因为深色背景下浅色阴影不容易显出来,需要更强的对比。

常见 alpha 值速查:

透明度 十六进制值 完整写法
5% 0D #0D000000
10% 1A #1A000000
20% 33 #33000000
30% 4D #4D000000
40% 66 #66000000
50% 80 #80000000
60% 99 #99000000
70% B3 #B3000000
80% CC #CC000000
90% E6 #E6000000

四、偏移量 offsetX/offsetY:模拟光源方向

offsetXoffsetY 控制阴影相对于组件的偏移方向,本质上是模拟光源的位置。如果光从左上方打过来,影子就该往右下方投射。

  • offsetX > 0:阴影向右偏移
  • offsetX < 0:阴影向左偏移
  • offsetY > 0:阴影向下偏移
  • offsetY < 0:阴影向上偏移
@Entry
@Component
struct ShadowOffsetDemo {
  build() {
    Column({ space: 20 }) {
      // 光源在左上:阴影向右下偏移
      Column() {
        Text('offsetX: 8, offsetY: 8')
          .fontSize(14)
          .fontColor('#333333')
      }
      .width(180)
      .height(80)
      .backgroundColor(Color.White)
      .borderRadius(8)
      .shadow({ radius: 20, color: '#33000000', offsetX: 8, offsetY: 8 })

      // 光源在正上方:阴影只向下偏移
      Column() {
        Text('offsetX: 0, offsetY: 6')
          .fontSize(14)
          .fontColor('#333333')
      }
      .width(180)
      .height(80)
      .backgroundColor(Color.White)
      .borderRadius(8)
      .shadow({ radius: 20, color: '#33000000', offsetX: 0, offsetY: 6 })

      // 光源在右上:阴影向左下偏移
      Column() {
        Text('offsetX: -8, offsetY: 8')
          .fontSize(14)
          .fontColor('#333333')
      }
      .width(180)
      .height(80)
      .backgroundColor(Color.White)
      .borderRadius(8)
      .shadow({ radius: 20, color: '#33000000', offsetX: -8, offsetY: 8 })
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#F5F5F5')
    .padding(20)
  }
}

实际经验:大多数 App 的设计规范里,光源默认在左上方或正上方。所以 offsetX 一般取 0 或小正值(2~8),offsetY 取正值(4~12)。偏移量太大(超过 20)会让阴影和组件脱节,看着像影子被风吹走了。

还有一点要注意:偏移量的单位也是 px,不是 vp。不同设备像素密度不一样,同样的偏移值在 2x 屏幕上视觉偏移只有 1x 屏幕的一半。如果你的设计稿标注了 vp 单位的偏移,记得做换算。


五、彩色阴影:用组件同色的半透明阴影做品牌感

默认的黑色阴影大家都在用,看多了难免审美疲劳。一个提升界面质感的小技巧:用组件自身颜色的半透明版本作为阴影色。这在 Material Design 3 和很多现代 UI 设计中被大量使用。

比如你有一个蓝色主题的按钮,不用黑色阴影,而是用蓝色半透明阴影:

@Entry
@Component
struct ColoredShadowDemo {
  build() {
    Column({ space: 30 }) {
      // 黑色阴影:普通但无聊
      Row() {
        Text('普通阴影')
          .fontSize(16)
          .fontColor(Color.White)
      }
      .width(200)
      .height(56)
      .backgroundColor('#1976D2')
      .borderRadius(28)
      .justifyContent(FlexAlign.Center)
      .shadow({ radius: 20, color: '#33000000', offsetX: 0, offsetY: 4 })

      // 蓝色半透明阴影:和按钮同色系,更有整体感
      Row() {
        Text('品牌色阴影')
          .fontSize(16)
          .fontColor(Color.White)
      }
      .width(200)
      .height(56)
      .backgroundColor('#1976D2')
      .borderRadius(28)
      .justifyContent(FlexAlign.Center)
      .shadow({ radius: 24, color: '#4D1976D2', offsetX: 0, offsetY: 6 })

      // 绿色按钮 + 绿色阴影
      Row() {
        Text('成功状态')
          .fontSize(16)
          .fontColor(Color.White)
      }
      .width(200)
      .height(56)
      .backgroundColor('#2E7D32')
      .borderRadius(28)
      .justifyContent(FlexAlign.Center)
      .shadow({ radius: 24, color: '#4D2E7D32', offsetX: 0, offsetY: 6 })

      // 橙色按钮 + 橙色阴影
      Row() {
        Text('警告状态')
          .fontSize(16)
          .fontColor(Color.White)
      }
      .width(200)
      .height(56)
      .backgroundColor('#E65100')
      .borderRadius(28)
      .justifyContent(FlexAlign.Center)
      .shadow({ radius: 24, color: '#4DE65100', offsetX: 0, offsetY: 6 })
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#F5F5F5')
    .padding(20)
    .justifyContent(FlexAlign.Center)
  }
}

彩色阴影的关键点:alpha 值要低(0.2~0.4),否则彩色阴影会显得很脏。阴影的 radius 可以比黑色阴影稍大一点,让颜色扩散得更柔和,看起来像是从按钮本身"散发"出来的光晕效果。

这种手法在品牌色鲜明的 App 里特别好用——比如网易云音乐的红、支付宝的蓝、微信的绿,用同色系阴影会让整个界面更有品牌辨识度。


六、多层阴影叠加:用多个 shadow 制造深度感

单层阴影的问题在于:它要么柔和但不够清晰,要么清晰但不够柔和。专业设计里经常用"多层阴影"来解决这个矛盾——一层小 radius 的深色阴影提供清晰的轮廓感,一层大 radius 的浅色阴影提供柔和的扩散感。

ArkUI 的 shadow 属性支持传入数组,实现多层阴影叠加:

@Entry
@Component
struct MultiShadowDemo {
  build() {
    Column({ space: 30 }) {
      // 单层阴影:要么清晰要么柔和,难以两全
      Column() {
        Text('单层阴影')
          .fontSize(16)
          .fontColor('#333333')
      }
      .width(240)
      .height(120)
      .backgroundColor(Color.White)
      .borderRadius(16)
      .justifyContent(FlexAlign.Center)
      .shadow({ radius: 20, color: '#1A000000', offsetX: 0, offsetY: 4 })

      // 双层阴影:小radius深色 + 大radius浅色,轮廓和扩散兼顾
      Column() {
        Text('双层阴影')
          .fontSize(16)
          .fontColor('#333333')
      }
      .width(240)
      .height(120)
      .backgroundColor(Color.White)
      .borderRadius(16)
      .justifyContent(FlexAlign.Center)
      .shadow({
        radius: 8,
        color: '#1A000000',
        offsetX: 0,
        offsetY: 2
      })
      .shadow({
        radius: 30,
        color: '#0D000000',
        offsetX: 0,
        offsetY: 8
      })

      // 三层阴影:极致深度感
      Column() {
        Text('三层阴影')
          .fontSize(16)
          .fontColor('#333333')
      }
      .width(240)
      .height(120)
      .backgroundColor(Color.White)
      .borderRadius(16)
      .justifyContent(FlexAlign.Center)
      .shadow({
        radius: 4,
        color: '#26000000',
        offsetX: 0,
        offsetY: 1
      })
      .shadow({
        radius: 12,
        color: '#1A000000',
        offsetX: 0,
        offsetY: 4
      })
      .shadow({
        radius: 40,
        color: '#0D000000',
        offsetX: 0,
        offsetY: 12
      })
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#F5F5F5')
    .padding(20)
    .justifyContent(FlexAlign.Center)
  }
}

多层阴影的搭配逻辑:

  • 近层阴影:小 radius(4~8),小 offsetY(1~3),较高透明度,模拟卡片边缘紧贴的阴影
  • 中层阴影:中 radius(12~20),中 offsetY(4~8),中等透明度,提供主要的浮起感
  • 远层阴影:大 radius(30~60),大 offsetY(8~16),低透明度,营造大范围的深度扩散

三层叠加的效果远比单层自然,很多设计系统(包括 Material Design)在 Sketch/Figma 里的阴影预设就是多层叠加出来的。


七、卡片浮动效果:shadow + borderRadius 黄金组合

阴影最经典的用途就是卡片浮动效果。一个圆角卡片加上恰到好处的阴影,是几乎所有现代 App 的标配。但这里有个细节:borderRadius 的圆角和阴影的扩散方向要协调。如果 borderRadius 很大但阴影是方形的,视觉上会非常违和。

@Entry
@Component
struct FloatingCardDemo {
  build() {
    Scroll() {
      Column({ space: 24 }) {
        // 小圆角卡片:干净利落
        Column() {
          Text('信息卡片')
            .fontSize(18)
            .fontWeight(FontWeight.Medium)
            .fontColor('#1A1A1A')
            .margin({ bottom: 8 })
          Text('这是一段卡片内容,展示小圆角配合紧凑阴影的效果。')
            .fontSize(14)
            .fontColor('#666666')
            .lineHeight(22)
        }
        .width('100%')
        .padding(20)
        .backgroundColor(Color.White)
        .borderRadius(8)
        .shadow({ radius: 16, color: '#1A000000', offsetX: 0, offsetY: 2 })

        // 大圆角卡片:柔和亲切
        Column() {
          Text('推荐卡片')
            .fontSize(18)
            .fontWeight(FontWeight.Medium)
            .fontColor('#1A1A1A')
            .margin({ bottom: 8 })
          Text('更大的圆角需要更柔和的阴影,radius也要相应增大。')
            .fontSize(14)
            .fontColor('#666666')
            .lineHeight(22)
        }
        .width('100%')
        .padding(20)
        .backgroundColor(Color.White)
        .borderRadius(20)
        .shadow({ radius: 28, color: '#1A000000', offsetX: 0, offsetY: 4 })

        // 超大圆角胶囊卡片
        Column() {
          Text('胶囊卡片')
            .fontSize(18)
            .fontWeight(FontWeight.Medium)
            .fontColor('#1A1A1A')
            .margin({ bottom: 8 })
          Text('borderRadius接近高度一半时,阴影需要大radius才协调。')
            .fontSize(14)
            .fontColor('#666666')
            .lineHeight(22)
        }
        .width('100%')
        .padding(20)
        .backgroundColor(Color.White)
        .borderRadius(40)
        .shadow({ radius: 36, color: '#1A000000', offsetX: 0, offsetY: 6 })

        // 带交互反馈的浮动卡片:按下时阴影收缩
        Column() {
          Text('可点击卡片')
            .fontSize(18)
            .fontWeight(FontWeight.Medium)
            .fontColor('#1A1A1A')
            .margin({ bottom: 8 })
          Text('点击时阴影收缩模拟按下效果,松开恢复。')
            .fontSize(14)
            .fontColor('#666666')
            .lineHeight(22)
        }
        .width('100%')
        .padding(20)
        .backgroundColor(Color.White)
        .borderRadius(16)
        .shadow({ radius: 24, color: '#1A000000', offsetX: 0, offsetY: 6 })
        .gesture(
          GestureGroup(GestureMode.Exclusive,
            LongPressGesture()
              .onAction(() => {})
          )
        )
      }
      .padding(20)
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#F0F0F0')
  }
}

一条经验法则radius 的值大约设为 borderRadius 的 1.2~1.8 倍比较协调。圆角越大,阴影越需要"铺开"才自然。


八、Material Design 阴影层级与 shadow 参数对应

Material Design 定义了一套阴影层级系统,用 dp(density-independent pixels)来划分组件的"海拔"。虽然 HarmonyOS 不直接使用 dp,但设计规范是相通的。下面给出各层级的 shadow 参数参考:

层级 典型组件 elevation shadow参数参考
0dp Flat button 0 无阴影
1dp Card (rest) 1 radius:8, color:#1A000000, offsetX:0, offsetY:1
2dp Card (raised) 2 radius:10, color:#1A000000, offsetX:0, offsetY:2
3dp Refresh indicator 3 radius:14, color:#1A000000, offsetX:0, offsetY:3
4dp Floating button 4 radius:16, color:#26000000, offsetX:0, offsetY:4
6dp SnackBar 6 radius:20, color:‘#26000000’, offsetX:0, offsetY:6
8dp Menu 8 radius:24, color:‘#33000000’, offsetX:0, offsetY:8
12dp Dialog 12 radius:32, color:‘#33000000’, offsetX:0, offsetY:12
16dp Bottom sheet 16 radius:40, color:‘#33000000’, offsetX:0, offsetY:16
24dp Modal bottom sheet 24 radius:50, color:‘#33000000’, offsetX:0, offsetY:24

实现一个 Material 风格的对话框阴影:

@Entry
@Component
struct MaterialDialogShadow {
  build() {
    Column() {
      Column() {
        Text('对话框标题')
          .fontSize(20)
          .fontWeight(FontWeight.Medium)
          .fontColor('#1A1A1A')
          .margin({ bottom: 12 })

        Text('这是一个 Material Design 风格的对话框,使用了 12dp 级别的阴影参数。')
          .fontSize(14)
          .fontColor('#666666')
          .lineHeight(22)
          .margin({ bottom: 24 })

        Row({ space: 12 }) {
          Row() {
            Text('取消')
              .fontSize(14)
              .fontColor('#1976D2')
          }
          .padding({ left: 20, right: 20, top: 10, bottom: 10 })
          .borderRadius(8)
          .backgroundColor('#E3F2FD')

          Row() {
            Text('确认')
              .fontSize(14)
              .fontColor(Color.White)
          }
          .padding({ left: 20, right: 20, top: 10, bottom: 10 })
          .borderRadius(8)
          .backgroundColor('#1976D2')
        }
        .justifyContent(FlexAlign.End)
      }
      .width(280)
      .padding(24)
      .backgroundColor(Color.White)
      .borderRadius(16)
      .shadow({ radius: 32, color: '#33000000', offsetX: 0, offsetY: 12 })
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#B3000000')
    .justifyContent(FlexAlign.Center)
  }
}

当然你也可以用 ShadowStyle 枚举快速设置系统预设阴影,写法更简洁:

Column() {
  Text('系统预设阴影')
}
.shadow(ShadowStyle.OUTER_DEFAULT_SM)

ShadowStyle 提供了 OUTER_DEFAULT_SMOUTER_DEFAULT_MDOUTER_DEFAULT_LG 等预设,对应小/中/大三级阴影。优点是跟着系统主题走,深色模式下自动适配;缺点是无法微调参数,定制性不足。


九、性能注意:大量阴影组件的渲染开销

阴影不是免费的。每个带阴影的组件都需要额外的离屏渲染(offscreen rendering)来完成模糊计算。渲染流程大致是:先绘制组件的轮廓到离屏缓冲区,然后对缓冲区做高斯模糊,再与原图合成。当组件数量多、阴影参数大时,GPU 负载会显著增加。

性能影响排序(从大到小):

  1. radius 值——模糊半径是计算量最大的因素,radius 翻倍,计算量大约翻四倍
  2. 组件数量——列表里100个卡片各带阴影,开销远超单个卡片
  3. 多层阴影——每个 .shadow() 调用都会触发一次离屏渲染
  4. 阴影区域大小——组件越大,模糊区域也越大

实际建议

// 反面教材:List里每个item都带复杂阴影
@Entry
@Component
struct BadListShadowDemo {
  private dataArr: number[] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

  build() {
    List({ space: 12 }) {
      ForEach(this.dataArr, (item: number) => {
        ListItem() {
          Column() {
            Text('Item ' + item.toString())
              .fontSize(16)
              .fontColor('#333333')
          }
          .width('100%')
          .height(80)
          .backgroundColor(Color.White)
          .borderRadius(12)
          // 三层阴影在列表里 = 性能灾难
          .shadow({ radius: 4, color: '#26000000', offsetX: 0, offsetY: 1 })
          .shadow({ radius: 12, color: '#1A000000', offsetX: 0, offsetY: 4 })
          .shadow({ radius: 40, color: '#0D000000', offsetX: 0, offsetY: 12 })
          .padding(16)
        }
      }, (item: number) => item.toString())
    }
    .width('100%')
    .height('100%')
    .padding(16)
  }
}

// 正面教材:列表item用简单阴影,只在关键卡片用多层阴影
@Entry
@Component
struct GoodListShadowDemo {
  private dataArr: number[] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

  build() {
    List({ space: 12 }) {
      ForEach(this.dataArr, (item: number) => {
        ListItem() {
          Column() {
            Text('Item ' + item.toString())
              .fontSize(16)
              .fontColor('#333333')
          }
          .width('100%')
          .height(80)
          .backgroundColor(Color.White)
          .borderRadius(12)
          // 列表item只用单层轻量阴影
          .shadow({ radius: 8, color: '#1A000000', offsetX: 0, offsetY: 2 })
          .padding(16)
        }
      }, (item: number) => item.toString())
    }
    .width('100%')
    .height('100%')
    .padding(16)
  }
}

如果你确实需要长列表带阴影,还有几个优化思路:

  1. renderGroup(true)——将组件及其子组件先整体离屏渲染再合成,减少重复计算
  2. 复用阴影参数对象——把 shadow 参数定义为常量,避免每次渲染都创建新对象
  3. 考虑用图片替代——对于固定大小的卡片,可以预渲染带阴影的 9-patch 图片
  4. LazyForEach 替代 ForEach——懒加载减少同时渲染的阴影组件数
// renderGroup 优化示例
Column() {
  Text('优化后的卡片')
}
.width(200)
.height(100)
.backgroundColor(Color.White)
.borderRadius(12)
.shadow({ radius: 20, color: '#1A000000', offsetX: 0, offsetY: 4 })
.renderGroup(true)

十、阴影与模糊的区别:shadow 是外部投影,blur 是内部模糊

这两个效果经常被新手混淆,但它们本质完全不同:

维度 shadow blur
作用位置 组件外部,投射到背景上 组件内部,模糊自身内容
视觉效果 组件"浮"起来,下方有投影 组件"虚"掉,内容看不清
参数 radius + color + offsetX + offsetY radius(一个参数)
常见用途 卡片浮动、按钮层级、对话框深度 隐藏敏感信息、毛玻璃遮罩、景深效果
对组件内容影响 不影响组件内容的清晰度 组件内容变模糊
@Entry
@Component
struct ShadowVsBlurDemo {
  build() {
    Column({ space: 30 }) {
      // shadow:卡片内容清晰,外部有投影
      Column() {
        Text('Shadow 效果')
          .fontSize(16)
          .fontColor('#333333')
        Text('内容依然清晰')
          .fontSize(14)
          .fontColor('#666666')
          .margin({ top: 4 })
      }
      .width(240)
      .height(120)
      .backgroundColor(Color.White)
      .borderRadius(12)
      .justifyContent(FlexAlign.Center)
      .shadow({ radius: 24, color: '#33000000', offsetX: 0, offsetY: 8 })

      // blur:卡片内容变模糊,没有外部投影
      Column() {
        Text('Blur 效果')
          .fontSize(16)
          .fontColor('#333333')
        Text('内容被模糊了')
          .fontSize(14)
          .fontColor('#666666')
          .margin({ top: 4 })
      }
      .width(240)
      .height(120)
      .backgroundColor(Color.White)
      .borderRadius(12)
      .justifyContent(FlexAlign.Center)
      .blur(10)

      // backdropBlur:模糊的是组件背后的内容,不是自身
      Column() {
        Text('BackdropBlur 效果')
          .fontSize(16)
          .fontColor(Color.White)
        Text('模糊背景内容')
          .fontSize(14)
          .fontColor('#FFFFFFCC')
          .margin({ top: 4 })
      }
      .width(240)
      .height(120)
      .backgroundColor('#80FFFFFF')
      .borderRadius(12)
      .justifyContent(FlexAlign.Center)
      .backdropBlur(20)
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#F0F0F0')
    .padding(20)
    .justifyContent(FlexAlign.Center)
  }
}

还有一个容易混淆的概念是 backdropBlur——它模糊的是组件背后的内容,相当于毛玻璃效果,和 shadow 完全不是一回事。backdropBlur 最经典的场景就是半透明遮罩层下面看到模糊的背景。

三者的使用场景总结:

  • shadow:需要"浮起来"的感觉——卡片、按钮、弹窗
  • blur:需要"看不清"的效果——敏感信息遮挡、失焦状态
  • backdropBlur:需要"毛玻璃"效果——半透明遮罩、底部弹窗背景

实战:一个完整的卡片列表页面

把前面讲的内容综合起来,写一个实际可用的卡片列表页面:

interface CardItem {
  title: string
  subtitle: string
  bgColor: string
  shadowColor: string
}

@Entry
@Component
struct CardListPage {
  private cardDataArr: CardItem[] = [
    { title: '今日推荐', subtitle: '为你精选的热门内容', bgColor: '#FFFFFF', shadowColor: '#1A000000' },
    { title: '限时优惠', subtitle: '距离结束还有2小时', bgColor: '#FFFFFF', shadowColor: '#4D1976D2' },
    { title: '新功能上线', subtitle: '探索最新版本更新', bgColor: '#FFFFFF', shadowColor: '#4D2E7D32' },
    { title: '热门话题', subtitle: '当前最火的讨论', bgColor: '#FFFFFF', shadowColor: '#1A000000' },
    { title: '精选课程', subtitle: '高质量学习资源', bgColor: '#FFFFFF', shadowColor: '#4DE65100' },
  ]

  build() {
    Column() {
      Text('阴影效果实战')
        .fontSize(24)
        .fontWeight(FontWeight.Bold)
        .fontColor('#1A1A1A')
        .width('100%')
        .padding({ left: 20, right: 20, top: 20, bottom: 16 })

      List({ space: 16 }) {
        ForEach(this.cardDataArr, (item: CardItem, index: number) => {
          ListItem() {
            Row() {
              Column() {
                Text(item.title)
                  .fontSize(18)
                  .fontWeight(FontWeight.Medium)
                  .fontColor('#1A1A1A')
                Text(item.subtitle)
                  .fontSize(14)
                  .fontColor('#888888')
                  .margin({ top: 6 })
              }
              .layoutWeight(1)
              .alignItems(HorizontalAlign.Start)

              Text('>')
                .fontSize(20)
                .fontColor('#CCCCCC')
            }
            .width('100%')
            .padding(20)
            .backgroundColor(item.bgColor)
            .borderRadius(16)
            .shadow({
              radius: 20,
              color: item.shadowColor,
              offsetX: 0,
              offsetY: 4
            })
          }
          .padding({ left: 20, right: 20 })
        }, (item: CardItem, index: number) => index.toString())
      }
      .layoutWeight(1)
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#F5F5F5')
  }
}

在这里插入图片描述


常见问题与避坑指南

问题 原因 解决方案
阴影完全看不到 radius 为 0,或 color 的 alpha 为 00 确保 radius > 0,color 格式为 #AARRGGBB 且 AA 不为 00
阴影颜色和预期不一致 把 #AARRGGBB 写成了 CSS 的 #RRGGBBAA ArkUI 颜色格式 alpha 在前,如 #33000000 是 20%透明黑色
阴影被截断,右侧或底部消失 父容器 overflow 裁剪了超出边界的阴影 给父容器加 padding 留出阴影空间,或确认 clip 属性未开启
阴影方向不对 offsetX/offsetY 正负方向理解反了 offsetX 正值向右,offsetY 正值向下
列表滚动卡顿 每个 ListItem 都有多层大 radius 阴影 列表项用单层小 radius 阴影,多层阴影只用于重点卡片
borderRadius 和阴影不协调 大圆角配小 radius 阴影,阴影看起来是方形的 radius 值设为 borderRadius 的 1.2~1.8 倍
同样的 shadow 参数在不同设备上粗细不一样 shadow 参数单位是 px 不是 vp 高密度屏上视觉更细,需要根据设备 density 做适配
shadow 和 blur 分不清 shadow 是外部投影,blur 是内部模糊 需要"浮起"用 shadow,需要"模糊"用 blur,需要"毛玻璃"用 backdropBlur
阴影在深色背景上几乎不可见 深色背景 + 黑色阴影 = 对比度不足 深色背景上提高阴影 alpha 或改用浅色阴影
ShadowStyle 预设不够灵活 枚举值只有几档,无法微调 需要精细控制时用 ShadowOptions 对象替代 ShadowStyle 枚举
Logo

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

更多推荐