在 Android 开发中,阴影(Shadow)是提升 UI 层次感和视觉体验的核心元素,不同视图、组件的阴影实现方式差异较大,需结合场景选择。以下从 基础视图阴影、特殊组件阴影、自定义阴影 三大维度,汇总 Android 中所有主流阴影实现方案,并对比其特性、兼容性和使用场景。
一、基础视图阴影(通用方案)
适用于 View
、TextView
、Button
等普通视图,核心通过 Z 轴高度 或 背景 Drawable 实现,是最常用的阴影方案。
1. elevation + translationZ(官方推荐,API 21+)
Android 5.0(API 21)引入 Material Design 的 Z 轴概念,通过 elevation
(静态 Z 轴高度)和 translationZ
(动态 Z 轴偏移)控制阴影,阴影的大小、模糊度会随 Z 值 自动变化(Z 值越大,阴影越扩散、越淡)。
核心特性
- 自动生成阴影:无需手动绘制,系统根据视图的
background
(需为带圆角的 Drawable,如ShapeDrawable
)生成阴影,避免阴影与视图边缘 “错位”。 - 动态可控:支持代码实时调整,适合交互场景(如点击、选中状态)。
- 兼容性:仅 API 21+ 生效,低版本无效果。
使用方式
XML 中配置
<View
android:layout_width="200dp"
android:layout_height="200dp"
<!-- 1. 静态Z轴高度:决定基础阴影大小 -->
android:elevation="4dp"
<!-- 2. 动态Z轴偏移:叠加在 elevation 上,适合临时提升层级 -->
android:translationZ="2dp"
<!-- 3. 背景需为带圆角的 Drawable(避免阴影与视图边缘错位) -->
android:background="@drawable/shape_round_white"
<!-- 4. 可选:设置阴影颜色(默认是深灰色,API 28+ 支持) -->
android:shadowColor="#80000000" /> <!-- 80 是透明度(00全透,FF不透) -->
代码中动态调整
// 1. 调整静态 elevation
view.setElevation(6f); // 单位:dp(代码中需传入 float 类型)
// 2. 调整动态 translationZ(如点击时临时提升阴影)
view.setOnClickListener(v -> {
v.setTranslationZ(8f); // 点击时阴影变大,视图层级提升
// 延迟恢复默认状态
v.postDelayed(() -> v.setTranslationZ(0f), 300);
});
// 3. API 28+ 调整阴影颜色
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
view.setShadowColor(Color.parseColor("#80FF0000")); // 红色阴影
}
配套 Drawable(shape_round_white.xml)
<!-- 带圆角的白色背景,确保阴影与视图边缘匹配 -->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="@android:color/white" />
<corners android:radius="8dp" /> <!-- 圆角需与视图一致 -->
</shape>
2. android:shadowXXX(低版本兼容方案,API 1+)
对于 API < 21 的设备,无法使用 elevation
,需通过 android:shadowColor
、android:shadowRadius
、android:shadowDx
、android:shadowDy
手动配置阴影,本质是通过 画布绘制阴影 实现。
核心特性
- 手动控制阴影参数:需明确设置阴影颜色、模糊半径、偏移量,灵活性高但配置复杂。
- 兼容性:支持所有 Android 版本,但仅对 文字(TextView) 或 设置了背景 Drawable 的 View 生效(普通 View 需配合
setLayerType(LAYER_TYPE_SOFTWARE, null)
启用软件渲染)。 - 性能问题:软件渲染(
LAYER_TYPE_SOFTWARE
)会增加绘制开销,避免在列表(RecyclerView)中大量使用。
使用方式
1. 文字阴影(TextView 专属)
直接通过 shadowXXX
属性设置,无需额外配置:
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="带阴影的文字"
android:textSize="20sp"
android:shadowColor="#80000000" <!-- 阴影颜色(带透明度) -->
android:shadowRadius="4dp" <!-- 模糊半径(值越大越模糊) -->
android:shadowDx="2dp" <!-- 水平偏移(正值向右,负值向左) -->
android:shadowDy="2dp" /> <!-- 垂直偏移(正值向下,负值向上) -->
2. 普通 View 阴影(需软件渲染)
<View
android:layout_width="200dp"
android:layout_height="200dp"
android:background="@android:color/white"
android:shadowColor="#80000000"
android:shadowRadius="6dp"
android:shadowDx="3dp"
android:shadowDy="3dp"
<!-- 关键:API < 21 需启用软件渲染才能显示阴影 -->
android:layerType="software" />
3. 背景 Drawable 内嵌阴影(自定义形状阴影)
通过 layer-list
或自定义 Drawable
手动绘制阴影,适合需要 非对称阴影(如仅底部阴影)或 复杂形状阴影(如圆形、多边形)的场景。
核心特性
- 高度自定义:可精确控制阴影的位置、大小、颜色,不受系统默认规则限制。
- 兼容性:全版本支持,无需依赖
elevation
。 - 缺点:需手动计算阴影区域,配置较繁琐。
使用方式(以底部阴影为例)
通过 layer-list
叠加 “阴影层” 和 “内容层”,阴影层用半透明颜色模拟:
<!-- drawable/shadow_bottom.xml -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<!-- 1. 阴影层:高度=阴影厚度,背景=半透明灰色 -->
<item
android:bottom="0dp"
android:left="-2dp"
android:right="-2dp"
android:top="0dp">
<shape android:shape="rectangle">
<solid android:color="#40000000" /> <!-- 阴影颜色(透明度40%) -->
<corners
android:bottomLeftRadius="8dp"
android:bottomRightRadius="8dp"
android:topLeftRadius="0dp"
android:topRightRadius="0dp" /> <!-- 仅底部圆角 -->
</shape>
</item>
<!-- 2. 内容层:向上偏移阴影厚度,覆盖阴影层顶部 -->
<item android:top="4dp"> <!-- 偏移量=阴影厚度 -->
<shape android:shape="rectangle">
<solid android:color="@android:color/white" /> <!-- 视图背景色 -->
<corners android:radius="8dp" /> <!-- 与阴影层圆角匹配 -->
</shape>
</item>
</layer-list>
在 View 中引用:
<View
android:layout_width="200dp"
android:layout_height="200dp"
android:background="@drawable/shadow_bottom" />
二、特殊组件阴影(专属方案)
部分 Material 组件或系统组件有专属阴影配置,需使用其特有的属性,而非通用的 elevation
。
1. CardView 阴影(Material 组件)
CardView
是带圆角和阴影的容器组件,其阴影由 cardElevation
控制(而非普通 elevation
),且支持配置阴影颜色和圆角。
核心特性
- 阴影与圆角绑定:避免普通 View 中 “阴影与圆角错位” 的问题(
CardView
会自动根据cardCornerRadius
生成匹配的阴影)。 - 兼容性优化:低版本(API < 21)通过
CardView
内部兼容方案显示阴影,无需额外处理。
使用方式
需先引入 Material 依赖(build.gradle)
gradle
implementation 'com.google.android.material:material:1.12.0'
XML 配置:
<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
<!-- 1. 卡片阴影大小(类似 elevation) -->
app:cardElevation="4dp"
<!-- 2. 卡片圆角大小 -->
app:cardCornerRadius="8dp"
<!-- 3. 阴影颜色(API 28+ 支持) -->
app:cardShadowColor="#80000000"
<!-- 4. 卡片背景色 -->
app:cardBackgroundColor="@android:color/white"
<!-- 5. 内边距(避免内容与卡片边缘重叠) -->
app:contentPadding="16dp">
<!-- 卡片内部内容 -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="CardView 内容" />
</com.google.android.material.card.MaterialCardView>
2. PopupWindow 阴影(弹窗阴影)
PopupWindow
的阴影分为 自身阴影 和 背景蒙层阴影,需分别配置。
1. 自身阴影(弹窗边缘阴影)
通过 setElevation()
或 setBackgroundDrawable()
实现:
// 方式1:API 21+ 用 elevation(推荐)
PopupWindow popupWindow = new PopupWindow(contentView, 300dp, 400dp);
popupWindow.setElevation(8f); // 弹窗自身阴影大小
// 方式2:全版本兼容(用背景 Drawable 带阴影)
popupWindow.setBackgroundDrawable(ContextCompat.getDrawable(this, R.drawable.shadow_popup));
2. 背景蒙层阴影(弹窗后面的半透明遮罩)
通过 setBackgroundDrawable()
设置全屏半透明背景,模拟蒙层阴影:
// 1. 设置弹窗可点击外部关闭(必须,否则蒙层不生效)
popupWindow.setOutsideTouchable(true);
popupWindow.setFocusable(true);
// 2. 设置蒙层背景(半透明黑色)
Drawable maskDrawable = new ColorDrawable(Color.parseColor("#80000000"));
popupWindow.setBackgroundDrawable(maskDrawable);
// 3. 显示弹窗(如在按钮下方)
popupWindow.showAsDropDown(button);
3. Dialog/AlertDialog 阴影
Dialog
的阴影由其 主题样式 控制,默认主题已包含阴影,可通过自定义主题修改。
自定义 Dialog 阴影
- 在
styles.xml
中定义主题:
<style name="CustomDialogStyle" parent="Theme.AppCompat.Light.Dialog">
<!-- 1. 弹窗背景(带圆角和阴影) -->
<item name="android:windowBackground">@drawable/dialog_background</item>
<!-- 2. 弹窗阴影大小(API 21+) -->
<item name="android:windowElevation">8dp</item>
<!-- 3. 弹窗边框(可选,避免边缘生硬) -->
<item name="android:windowFrame">@null</item>
<!-- 4. 取消默认标题栏 -->
<item name="windowNoTitle">true</item>
</style>
- 定义弹窗背景
dialog_background.xml
:
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="@android:color/white" />
<corners android:radius="12dp" />
</shape>
- 在代码中使用主题:
AlertDialog dialog = new AlertDialog.Builder(this, R.style.CustomDialogStyle)
.setTitle("自定义阴影 Dialog")
.setMessage("Dialog 阴影由主题控制")
.setPositiveButton("确定", null)
.create();
dialog.show();
三、自定义阴影(进阶方案)
当系统方案无法满足需求(如渐变阴影、不规则形状阴影)时,需通过 自定义 View 绘制阴影 或 使用第三方库 实现。
1. 自定义 View 绘制阴影(Canvas 绘制)
通过 Canvas
的 drawShadow()
方法手动绘制阴影,适合复杂场景。
核心代码
public class CustomShadowView extends View {
private Paint mPaint;
private RectF mRect;
public CustomShadowView(Context context) {
super(context);
init();
}
private void init() {
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setColor(Color.WHITE); // 视图本身颜色
mRect = new RectF();
// 启用阴影绘制(API 21+)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
setLayerType(LAYER_TYPE_SOFTWARE, mPaint); // 软件渲染(避免硬件加速问题)
mPaint.setShadowLayer(6f, 3f, 3f, Color.parseColor("#80000000"));
// 参数说明:阴影模糊半径 → 水平偏移 → 垂直偏移 → 阴影颜色
}
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mRect.set(20dp, 20dp, w - 20dp, h - 20dp); // 视图绘制区域
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// 绘制带阴影的矩形(阴影由 mPaint.setShadowLayer() 自动生成)
canvas.drawRoundRect(mRect, 8dp, 8dp, mPaint);
}
}
2. 第三方库(简化复杂阴影)
部分库封装了复杂阴影逻辑,适合快速实现特殊效果(如渐变阴影、多层阴影):
- ShadowLayout:支持自定义阴影颜色、方向、模糊度,兼容低版本。
- Material Components:除了
CardView
,还提供MaterialButton
、BottomSheet
等组件,自带阴影效果。
四、阴影方案对比与选型建议
方案类型 | 核心属性 / API | 兼容性 | 优势 | 劣势 | 适用场景 |
---|---|---|---|---|---|
elevation + translationZ | android:elevation | API 21+ | 系统自动生成,动态可控,性能好 | 低版本不支持 | 普通视图、交互场景(如点击提升层级) |
android:shadowXXX | shadowColor/shadowRadius | API 1+ | 全版本兼容,文字阴影便捷 | 需软件渲染,性能差 | 低版本视图、文字阴影 |
背景 Drawable 内嵌 | layer-list/shape | API 1+ | 全版本兼容,支持非对称阴影 | 配置繁琐,无法动态调整 | 固定形状阴影(如底部阴影) |
CardView 专属 | app:cardElevation | API 7+(兼容) | 阴影与圆角匹配,低版本兼容 | 仅限 CardView 组件 | 卡片式布局(如列表项、信息卡片) |
PopupWindow/Dialog 阴影 | setElevation / 主题样式 | API 21+(部分) | 弹窗蒙层 + 自身阴影一体 | 低版本需自定义 Drawable | 弹窗、对话框 |
自定义 View 绘制 | Canvas.drawShadow() | API 21+ | 支持复杂形状、渐变阴影 | 需自定义代码,学习成本高 | 不规则形状、特殊效果阴影 |
五、常见问题与解决方案
-
阴影与视图圆角错位
- 原因:普通 View 的
elevation
阴影基于background
的形状生成,若background
无圆角,阴影会是直角。 - 解决方案:给 View 设置带圆角的
background
(如shape
Drawable),或使用CardView
。
- 原因:普通 View 的
-
低版本(API < 21)阴影不显示
- 解决方案:使用
android:shadowXXX
+layerType="software"
,或背景 Drawable 内嵌阴影。
- 解决方案:使用
-
阴影显示模糊 / 过淡
- 调整
elevation
(值越大,阴影越扩散)或shadowRadius
(值越大,模糊度越高)。 - 增加阴影颜色的透明度(如
#80000000
比#40000000
更浓)。
- 调整
-
RecyclerView 中阴影性能差
- 原因:过多重叠阴影导致过度绘制。
- 解决方案:使用
elevation
(硬件加速),避免layerType="software"
;减少不必要的阴影重叠。