Shadow(阴影)— 移动界面中的一种视觉效果,能够在元素之间营造深度和层次感的错觉。阴影显示哪个元素在Z平面上更高,因此更重要或更具交互性。根据Material Design指南(Google,2026),系统阴影使用五个关键高度(elevation):0 dp(平面)、1–4 dp(按钮)、6–12 dp(卡片)、16–24 dp(对话框)、24+ dp(模态窗口)。关于元素高度的更多信息,请阅读关于Elevation的文章。
要点
阴影(shadow)在用户界面中 — 是一种模拟光照和深度的视觉效果。通过绘制元素相对于原位置偏移的深色模糊副本产生阴影。在移动设计中,阴影具有三个功能:显示层次结构(元素越高 — 阴影越长)、表明交互性(带阴影的按钮看起来可点击)、将内容与背景分离(带阴影的卡片被视为独立块)。
在Google的Material Design中,阴影与elevation(高度)的概念密不可分。每个元素在Z坐标中占据特定的高度,阴影是来自上方光照的投影。苹果的Human Interface指南没有规定严格的高度系统,但建议使用阴影在界面中创建视觉层次和深度。
根据Nielsen Norman Group(2024)的研究,正确放置的阴影可将寻找交互元素的时间缩短24%。阴影还有助于有视力障碍的用户区分元素的边界。然而,过度使用阴影(屏幕上3个以上级别)会增加认知负担并降低感知度。
在iOS上,阴影通过CALayer的属性实现:shadowColor设置阴影颜色,shadowOffset — CGSize中的偏移量(x,y),shadowRadius — 模糊半径,shadowOpacity — 从0到1的不透明度。为提高性能,建议还设置shadowPath — 描述阴影形状的UIBezierPath,以便Core Animation不自动计算。
// 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()。
// 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中,Material Design中的阴影通过elevation实现 — 这是设置元素在Z坐标中高度的View属性。系统根据elevation和outlineProvider(元素轮廓)自动绘制阴影。elevation越高,阴影越大越模糊。对于不从Material Design继承的View(例如AppCompat),阴影仅在启用硬件加速时有效。
<!-- 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:用于阴影的自定义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版本上提供更可预测的行为。
// 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 | 元素 | 阴影特征 |
|---|---|---|---|
| 0 | 0 dp | 文本、图标、背景 | 无阴影,平面层 |
| 1 | 1–4 dp | 按钮、输入字段 | 轻微阴影,小偏移 |
| 2 | 6–12 dp | 卡片、Snackbar | 中等阴影,明显模糊 |
| 3 | 16–24 dp | 对话框、菜单 | 深层阴影,大模糊 |
| 4 | 24+ dp | 模态窗口 | 最大阴影,元素悬浮 |
良好实践规则:(1)不要对不应吸引注意力的元素(背景、分隔线)使用阴影,(2)阴影高度应与元素的重要性相匹配 — 越重要越高,(3)按下按钮时动画化降低elevation以获得触觉反馈,(4)避免在深色背景上使用阴影 — 使用overlay(提亮/变暗)代替阴影。在Material You(Android 12+)中,阴影适应主题:在深色背景上,elevation被颜色层(surface tint)替代。
在Flutter中,阴影通过BoxDecoration内部的BoxShadow实现。BoxShadow接受color、offset、blurRadius和spreadRadius。Flutter支持多重阴影:传递BoxShadow列表可以创建复杂的效果 — 例如,外部阴影+内部光照。PhysicalModel — 一种替代方案,根据形状模拟物理阴影(BeveledRectangleBorder、CircleBorder)。
// 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 — 是元素在Z坐标中的数字高度,而shadow(阴影) — 是由该高度创建的视觉效果。在Android中,elevation是自动生成阴影的View属性。在设计领域,elevation和shadow经常作为可互换的术语使用,但从技术上讲,elevation是原因,shadow是结果。
检查:(1)View是否设置了大于0的elevation,(2)是否设置了outlineProvider(如果View不是来自Material库),(3)是否启用了硬件加速(manifest中的android:hardwareAccelerated="true")。CardView自动管理outlineProvider。对于自定义View,调用view.outlineProvider = ViewOutlineProvider.BACKGROUND。
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级别。如果有更多带阴影的元素 — 将它们合并到具有相同高度的组中。过多的阴影数量会产生视觉噪声并降低中低端设备的渲染性能。
总结
我们将开发一款交钥匙移动应用程序
IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。