Shadow:本质,在移动应用中创建阴影

作者: IT Sectr 发布日期: 2026-02-28 阅读时间: 9 分钟

Shadow(阴影)— 移动界面中的一种视觉效果,能够在元素之间营造深度和层次感的错觉。阴影显示哪个元素在Z平面上更高,因此更重要或更具交互性。根据Material Design指南(Google,2026),系统阴影使用五个关键高度(elevation):0 dp(平面)、1–4 dp(按钮)、6–12 dp(卡片)、16–24 dp(对话框)、24+ dp(模态窗口)。关于元素高度的更多信息,请阅读关于Elevation的文章

要点

  • 阴影(shadow) — 一种视觉效果,通过使元素下方的区域变暗并在边缘进行模糊处理来营造深度感。
  • 在iOS上,阴影通过CALayer的属性设置:shadowColor、shadowOffset、shadowRadius、shadowOpacity。
  • 在Android中,阴影通过elevation(Material Design)或通过OutlineProvider的自定义渲染来管理。
  • Jetpack Compose通过Modifier.shadow()以及Card/Surface中的elevation支持阴影。
  • 在Flutter中,阴影通过BoxShadow在BoxDecoration中或通过PhysicalModel实现。

什么是UI中的阴影?

阴影(shadow)在用户界面中 — 是一种模拟光照和深度的视觉效果。通过绘制元素相对于原位置偏移的深色模糊副本产生阴影。在移动设计中,阴影具有三个功能:显示层次结构(元素越高 — 阴影越长)、表明交互性(带阴影的按钮看起来可点击)、将内容与背景分离(带阴影的卡片被视为独立块)。

在Google的Material Design中,阴影与elevation(高度)的概念密不可分。每个元素在Z坐标中占据特定的高度,阴影是来自上方光照的投影。苹果的Human Interface指南没有规定严格的高度系统,但建议使用阴影在界面中创建视觉层次和深度。

根据Nielsen Norman Group(2024)的研究,正确放置的阴影可将寻找交互元素的时间缩短24%。阴影还有助于有视力障碍的用户区分元素的边界。然而,过度使用阴影(屏幕上3个以上级别)会增加认知负担并降低感知度。

iOS上的阴影:CALayer和SwiftUI

在iOS上,阴影通过CALayer的属性实现:shadowColor设置阴影颜色,shadowOffset — CGSize中的偏移量(x,y),shadowRadius — 模糊半径,shadowOpacity — 从0到1的不透明度。为提高性能,建议还设置shadowPath — 描述阴影形状的UIBezierPath,以便Core Animation不自动计算。

swift
// UIKit:通过CALayer配置阴影
import UIKit

let cardView = UIView()
cardView.backgroundColor = .systemBackground
cardView.layer.cornerRadius = 12

// 配置阴影
cardView.layer.shadowColor = UIColor.black.cgColor
cardView.layer.shadowOpacity = 0.15
cardView.layer.shadowOffset = CGSize(width: 0, height: 4)
cardView.layer.shadowRadius = 8

"> 优化:显式 shadowPath
cardView.layer.shadowPath = UIBezierPath(
    roundedRect: cardView.bounds,
    cornerRadius: 12
).cgPath

SwiftUI中,阴影通过.shadow()修饰符设置,参数包括color、radius、x、y。SwiftUI自动应用shadowPath并管理渲染性能。对于高级阴影(内阴影、多重阴影),请使用带有LinearGradient的overlay()。

swift
// SwiftUI:通过shadow修饰符设置阴影
import SwiftUI

VStack {
    Text("带阴影的卡片")
        .padding()
        .background(Color.white)
        .cornerRadius(16)
        .shadow(
            color: .black.opacity(0.15),
            radius: 10,
            x: 0,
            y: 4
        )
}

iOS上的阴影性能:如果未设置shadowPath,Core Animation会在每一帧根据图层形状计算阴影,这可能在动画期间导致卡顿。对于静态元素,始终设置shadowPath。对于动画元素,在CALayer上使用shouldRasterize = true将阴影缓存到单独的图层中。在SwiftUI中,光栅化通过.drawingGroup()管理。

Android中的阴影:elevation和自定义阴影

在Android中,Material Design中的阴影通过elevation实现 — 这是设置元素在Z坐标中高度的View属性。系统根据elevation和outlineProvider(元素轮廓)自动绘制阴影。elevation越高,阴影越大越模糊。对于不从Material Design继承的View(例如AppCompat),阴影仅在启用硬件加速时有效。

xml
<!-- XML:通过属性设置elevation -->
<androidx.cardview.widget.CardView
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    app:cardCornerRadius="12dp"
    app:cardElevation="6dp">
    <!-- 卡片内容 -->
</androidx.cardview.widget.CardView>

在Android View系统中,自定义阴影使用OutlineProvider。ViewOutlineProvider允许通过outline.setRoundRect()或outline.setPath()设置任意形状。没有OutlineProvider,阴影将根据View的矩形形状绘制,这不考虑圆角。

kotlin
// Kotlin:用于阴影的自定义OutlineProvider
import android.graphics.Outline
import android.view.View
import android.view.ViewOutlineProvider

val myView: View = findViewById(R.id.myView)
myView.outlineProvider = object : ViewOutlineProvider() {
    override fun getOutline(view: View, outline: Outline) {
        outline.setRoundRect(
            0, 0, view.width, view.height,
            12f  // 圆角半径
        )
    }
}
myView.elevation = 8f  // 阴影高度(px)
myView.translationZ = 4f  // 按下时额外高度

Jetpack Compose中,阴影通过Modifier.shadow()以及Card和Surface组件中的elevation进行管理。Modifier.shadow()接受elevation和shape。与View System不同,Compose在其自己的渲染管线中绘制阴影,这在所有Android版本上提供更可预测的行为。

kotlin
// Jetpack Compose:通过Modifier.shadow设置阴影
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp

Card(
    modifier = Modifier.shadow(
        elevation = 6.dp,
        shape = RoundedCornerShape(12.dp),
        clip = false
    ),
    colors = CardDefaults.cardColors(containerColor = Color.White)
) {
    Text("带自定义阴影的Card")
}

阴影使用建议

正确使用阴影可以改善界面感知,错误使用则会产生视觉噪声并降低性能。Material Design定义了五个elevation级别:0 dp(无阴影)、1–4 dp(按钮、芯片)、6–12 dp(卡片、菜单)、16–24 dp(对话框、底部面板)、24+ dp(模态窗口、FAB)。

级别Elevation元素阴影特征
00 dp文本、图标、背景无阴影,平面层
11–4 dp按钮、输入字段轻微阴影,小偏移
26–12 dp卡片、Snackbar中等阴影,明显模糊
316–24 dp对话框、菜单深层阴影,大模糊
424+ dp模态窗口最大阴影,元素悬浮

良好实践规则:(1)不要对不应吸引注意力的元素(背景、分隔线)使用阴影,(2)阴影高度应与元素的重要性相匹配 — 越重要越高,(3)按下按钮时动画化降低elevation以获得触觉反馈,(4)避免在深色背景上使用阴影 — 使用overlay(提亮/变暗)代替阴影。在Material You(Android 12+)中,阴影适应主题:在深色背景上,elevation被颜色层(surface tint)替代。

Flutter中的阴影:BoxShadow和PhysicalModel

在Flutter中,阴影通过BoxDecoration内部的BoxShadow实现。BoxShadow接受color、offset、blurRadius和spreadRadius。Flutter支持多重阴影:传递BoxShadow列表可以创建复杂的效果 — 例如,外部阴影+内部光照。PhysicalModel — 一种替代方案,根据形状模拟物理阴影(BeveledRectangleBorder、CircleBorder)。

dart
// Flutter:通过BoxDecoration + BoxShadow设置阴影
import 'package:flutter/material.dart';

Container(
    decoration: BoxDecoration(
        color: Colors.white,
        borderRadius: BorderRadius.circular(12.0),
        boxShadow: [
            BoxShadow(
                color: Colors.black.withOpacity(0.15),
                offset: Offset(0, 4),
                blurRadius: 8.0,
                spreadRadius: 0,
            ),
        ],
    ),
    child: Text('带阴影的容器'),
)

// 用于物理阴影的PhysicalModel
PhysicalModel(
    color: Colors.white,
    elevation: 6.0,
    shadowColor: Colors.black.withOpacity(0.2),
    borderRadius: BorderRadius.circular(12.0),
    child: Text('带阴影的物理模型'),
)

性能:带有多个阴影的BoxShadow可能在每次更改时导致重新绘制(repaint)。对于静态元素,请使用PhysicalModel — 它会将阴影缓存到单独的图层(RepaintBoundary)中。在Flutter 3.16+中,添加了对Material 3 elevation的支持,具有根据主题(浅色/深色)改变颜色和模糊度的自适应阴影。

常见问题

elevation和阴影有什么区别?

Elevation — 是元素在Z坐标中的数字高度,而shadow(阴影) — 是由该高度创建的视觉效果。在Android中,elevation是自动生成阴影的View属性。在设计领域,elevation和shadow经常作为可互换的术语使用,但从技术上讲,elevation是原因,shadow是结果。

为什么阴影在Android上不显示?

检查:(1)View是否设置了大于0的elevation,(2)是否设置了outlineProvider(如果View不是来自Material库),(3)是否启用了硬件加速(manifest中的android:hardwareAccelerated="true")。CardView自动管理outlineProvider。对于自定义View,调用view.outlineProvider = ViewOutlineProvider.BACKGROUND

如何在iOS上创建内阴影(inner shadow)?

CALayer不直接支持内阴影。使用组合:CAGradientLayer在遮罩(mask)内使用黑色和透明度,或者放置带有光栅化内阴影的UIImageView。在SwiftUI中,内阴影通过overlay与LinearGradient和blendMode(.multiply)实现。

如何在按钮按下时动画化阴影?

在Android中,使用translationZ进行动画:静止状态下elevation = 2 dp,按下时translationZ = 4 dp(总高度6 dp)。在iOS上,同时动画化shadowOffset和shadowRadius以及按钮的变换。在SwiftUI中,使用withAnimation通过Modifier.shadow()更改elevation。在Flutter中 — 使用AnimatedContainer更改BoxShadow。

一个屏幕上可以使用多少个阴影?

Material Design建议一个屏幕上不超过三个elevation级别。如果有更多带阴影的元素 — 将它们合并到具有相同高度的组中。过多的阴影数量会产生视觉噪声并降低中低端设备的渲染性能。

总结

  • 阴影(shadow) — 一种基于元素在Z坐标中elevation的视觉深度效果。
  • 在iOS上,阴影通过CALayer配置:shadowColor、shadowOffset、shadowRadius、shadowOpacity。
  • 在Android中,阴影通过elevation和用于自定义形状的OutlineProvider管理。
  • Material Design定义了五个elevation级别:0、1–4、6–12、16–24和24+ dp。
  • Jetpack Compose使用Modifier.shadow()以及Card/Surface中的elevation来管理阴影。
  • Flutter通过BoxShadow(BoxDecoration)和PhysicalModel(物理阴影)实现阴影。
  • 优化:设置shadowPath(iOS)、outlineProvider(Android)、使用PhysicalModel(Flutter)。

我们将开发一款交钥匙移动应用程序

IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。

讨论项目

另请阅读