Jetpack Compose 阴影效果
目录
一、阴影基础概念
1.1 阴影在Compose中的工作原理
Jetpack Compose中的阴影是通过** elevation(海拔/高度)**系统实现的。阴影效果模拟了光线照射到 elevated 组件时产生的投影,是Material Design设计语言的核心视觉元素之一。
光线方向(默认左上)
↘
╔═══════╗
║ ║ ← elevated 组件
╚═══════╝
↘
╲ 阴影区域
1.2 阴影渲染层级
Compose阴影渲染流程:
┌─────────────────────────────────────┐
│ 1. 确定组件形状 (Shape) │
├─────────────────────────────────────┤
│ 2. 计算阴影路径 (Shadow Path) │
├─────────────────────────────────────┤
│ 3. 应用模糊效果 (Blur) │
├─────────────────────────────────────┤
│ 4. 偏移阴影位置 (Offset) │
├─────────────────────────────────────┤
│ 5. 合成到画布 (Canvas Rendering) │
└─────────────────────────────────────┘
1.3 关键属性概览
| 属性 | 说明 | 默认值 | 适用API |
|---|---|---|---|
elevation | 海拔高度,决定阴影大小 | 0.dp | 全版本 |
shadowElevation | 阴影海拔(明确指定阴影) | 0.dp | Compose 1.3+ |
ambientColor | 环境光阴影颜色 | Color.Black | Compose 1.2+ |
spotColor | 点光源阴影颜色 | Color.Black | Compose 1.2+ |
shape | 阴影形状 | RectangleShape | 全版本 |
clip | 是否裁剪阴影 | false | 全版本 |
1.4 系统默认行为
// 默认阴影行为
Box(
modifier = Modifier
.size(100.dp)
.shadow(elevation = 8.dp) // 默认黑色阴影,自动偏移
)
默认行为特点:
- 阴影颜色:纯黑色 (
Color.Black) - 阴影偏移:根据 elevation 自动计算(右下方向)
- 模糊半径:与 elevation 成正比
- 形状:使用组件的 shape 或默认矩形
二、阴影使用指南
2.1 基础阴影添加
2.1.1 使用 shadow Modifier
@Composable
fun BasicShadowExample() {
Column(
modifier = Modifier.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
// 基础阴影
Box(
modifier = Modifier
.size(120.dp)
.shadow(elevation = 4.dp)
.background(Color.White)
)
// 中等阴影
Box(
modifier = Modifier
.size(120.dp)
.shadow(elevation = 8.dp)
.background(Color.White)
)
// 强阴影
Box(
modifier = Modifier
.size(120.dp)
.shadow(elevation = 16.dp)
.background(Color.White)
)
}
}
2.1.2 带形状的阴影
@Composable
fun ShapedShadowExample() {
Row(
modifier = Modifier.padding(16.dp),
horizontalArrangement = Arrangement.spacedBy(16.dp)
) {
// 圆形阴影
Box(
modifier = Modifier
.size(100.dp)
.shadow(
elevation = 8.dp,
shape = CircleShape
)
.background(Color.Blue, CircleShape)
)
// 圆角矩形阴影
Box(
modifier = Modifier
.size(100.dp)
.shadow(
elevation = 8.dp,
shape = RoundedCornerShape(16.dp)
)
.background(Color.Green, RoundedCornerShape(16.dp))
)
// 自定义形状阴影
val triangleShape = GenericShape { size, _ ->
moveTo(size.width / 2f, 0f)
lineTo(size.width, size.height)
lineTo(0f, size.height)
close()
}
Box(
modifier = Modifier
.size(100.dp)
.shadow(
elevation = 8.dp,
shape = triangleShape
)
.background(Color.Red, triangleShape)
)
}
}
2.2 调整阴影效果
2.2.1 阴影颜色定制(Compose 1.2+)
@Composable
fun ColoredShadowExample() {
Column(
modifier = Modifier.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
// 彩色阴影 - Material 3风格
Box(
modifier = Modifier
.size(120.dp)
.shadow(
elevation = 12.dp,
spotColor = Color(0xFF6200EE).copy(alpha = 0.5f),
ambientColor = Color(0xFF6200EE).copy(alpha = 0.3f),
shape = RoundedCornerShape(16.dp)
)
.background(Color.White, RoundedCornerShape(16.dp))
)
// 暖色调阴影
Box(
modifier = Modifier
.size(120.dp)
.shadow(
elevation = 12.dp,
spotColor = Color(0xFFFF6B35).copy(alpha = 0.4f),
ambientColor = Color(0xFFFF6B35).copy(alpha = 0.2f),
shape = RoundedCornerShape(16.dp)
)
.background(Color(0xFFFFF5F0), RoundedCornerShape(16.dp))
)
// 冷色调阴影
Box(
modifier = Modifier
.size(120.dp)
.shadow(
elevation = 12.dp,
spotColor = Color(0xFF00BCD4).copy(alpha = 0.4f),
ambientColor = Color(0xFF00BCD4).copy(alpha = 0.2f),
shape = RoundedCornerShape(16.dp)
)
.background(Color(0xFFE0F7FA), RoundedCornerShape(16.dp))
)
}
}
2.2.2 裁剪与非裁剪阴影
@Composable
fun ClipShadowExample() {
Row(
modifier = Modifier.padding(16.dp),
horizontalArrangement = Arrangement.spacedBy(24.dp)
) {
// 裁剪阴影(默认)- 阴影被形状边界裁剪
Box(
modifier = Modifier
.size(100.dp)
.shadow(
elevation = 12.dp,
shape = CircleShape,
clip = true // 默认值
)
.background(Color.Blue, CircleShape)
) {
Text(
text = "Clip",
color = Color.White,
modifier = Modifier.align(Alignment.Center)
)
}
// 非裁剪阴影 - 阴影超出形状边界
Box(
modifier = Modifier
.size(100.dp)
.shadow(
elevation = 12.dp,
shape = CircleShape,
clip = false // 不裁剪
)
.background(Color.Red, CircleShape)
) {
Text(
text = "No Clip",
color = Color.White,
modifier = Modifier.align(Alignment.Center)
)
}
}
}
2.3 移除阴影效果
@Composable
fun RemoveShadowExample() {
Column(
modifier = Modifier.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
// 方式1:设置elevation为0
Box(
modifier = Modifier
.size(100.dp)
.shadow(elevation = 0.dp)
.background(Color.White)
)
// 方式2:使用graphicsLayer移除阴影
Box(
modifier = Modifier
.size(100.dp)
.graphicsLayer {
shadowElevation = 0f
}
.background(Color.White)
)
// 方式3:直接不使用shadow modifier
Box(
modifier = Modifier
.size(100.dp)
.background(Color.White)
)
}
}
三、阴影设置选项详解
3.1 shadow Modifier完整参数
fun Modifier.shadow(
elevation: Dp,
shape: Shape = RectangleShape,
clip: Boolean = elevation > 0.dp,
ambientColor: Color = DefaultShadowColor, // Compose 1.2+
spotColor: Color = DefaultShadowColor // Compose 1.2+
): Modifier
3.2 参数详细说明
3.2.1 elevation(海拔高度)
@Composable
fun ElevationShowcase() {
val elevations = listOf(0.dp, 2.dp, 4.dp, 8.dp, 16.dp, 32.dp)
Column(
modifier = Modifier.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
elevations.forEach { elevation ->
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(16.dp)
) {
Box(
modifier = Modifier
.size(80.dp)
.shadow(
elevation = elevation,
shape = RoundedCornerShape(8.dp)
)
.background(Color.White, RoundedCornerShape(8.dp))
)
Text(
text = "${elevation.value.toInt()}dp",
fontSize = 16.sp,
fontWeight = FontWeight.Medium
)
}
}
}
}
elevation与视觉效果对应关系:
| Elevation | Material Design层级 | 适用场景 |
|---|---|---|
| 0.dp | 基础层 | 背景、静态内容 |
| 1-2.dp | 卡片悬停 | 轻微凸起 |
| 4-6.dp | 卡片、按钮 | 可交互元素 |
| 8-12.dp | 应用栏、浮动按钮 | 重要操作元素 |
| 16-24.dp | 对话框、抽屉 | 模态内容 |
3.2.2 shape(形状)
@Composable
fun ShapeShadowShowcase() {
Column(
modifier = Modifier.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
// 矩形
ShadowShapeBox(
shape = RectangleShape,
name = "RectangleShape"
)
// 圆角矩形
ShadowShapeBox(
shape = RoundedCornerShape(16.dp),
name = "RoundedCornerShape(16.dp)"
)
// 圆形
ShadowShapeBox(
shape = CircleShape,
name = "CircleShape"
)
// 胶囊形
ShadowShapeBox(
shape = RoundedCornerShape(50.dp),
name = "Capsule Shape"
)
// 切角形状
val cutCornerShape = CutCornerShape(
topStart = 16.dp,
topEnd = 0.dp,
bottomEnd = 16.dp,
bottomStart = 0.dp
)
ShadowShapeBox(
shape = cutCornerShape,
name = "CutCornerShape"
)
// 自定义形状
val customShape = GenericShape { size, _ ->
val path = Path().apply {
moveTo(0f, 0f)
lineTo(size.width * 0.8f, 0f)
lineTo(size.width, size.height * 0.5f)
lineTo(size.width * 0.8f, size.height)
lineTo(0f, size.height)
close()
}
addPath(path)
}
ShadowShapeBox(
shape = customShape,
name = "Custom Shape"
)
}
}
@Composable
private fun ShadowShapeBox(shape: Shape, name: String) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(16.dp)
) {
Box(
modifier = Modifier
.size(80.dp)
.shadow(
elevation = 8.dp,
shape = shape
)
.background(Color(0xFF6200EE), shape)
)
Text(
text = name,
fontSize = 14.sp,
color = Color.Gray
)
}
}
3.2.3 阴影颜色(ambientColor & spotColor)
@Composable
fun ShadowColorDeepDive() {
Column(
modifier = Modifier.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(24.dp)
) {
// 环境光 vs 点光源阴影对比
Text(
text = "Ambient vs Spot Shadow",
fontSize = 18.sp,
fontWeight = FontWeight.Bold
)
Row(
horizontalArrangement = Arrangement.spacedBy(16.dp)
) {
// 仅环境光阴影
ShadowColorBox(
ambientColor = Color.Red.copy(alpha = 0.5f),
spotColor = Color.Transparent,
label = "Ambient Only"
)
// 仅点光源阴影
ShadowColorBox(
ambientColor = Color.Transparent,
spotColor = Color.Red.copy(alpha = 0.5f),
label = "Spot Only"
)
// 组合阴影
ShadowColorBox(
ambientColor = Color.Red.copy(alpha = 0.3f),
spotColor = Color.Red.copy(alpha = 0.5f),
label = "Combined"
)
}
// 透明度影响
Text(
text = "Alpha Channel Impact",
fontSize = 18.sp,
fontWeight = FontWeight.Bold
)
Row(
horizontalArrangement = Arrangement.spacedBy(16.dp)
) {
ShadowColorBox(
ambientColor = Color.Blue.copy(alpha = 0.1f),
spotColor = Color.Blue.copy(alpha = 0.2f),
label = "Alpha 0.1-0.2"
)
ShadowColorBox(
ambientColor = Color.Blue.copy(alpha = 0.3f),
spotColor = Color.Blue.copy(alpha = 0.5f),
label = "Alpha 0.3-0.5"
)
ShadowColorBox(
ambientColor = Color.Blue.copy(alpha = 0.6f),
spotColor = Color.Blue.copy(alpha = 0.8f),
label = "Alpha 0.6-0.8"
)
}
}
}
@Composable
private fun ShadowColorBox(
ambientColor: Color,
spotColor: Color,
label: String
) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
Box(
modifier = Modifier
.size(80.dp)
.shadow(
elevation = 12.dp,
shape = RoundedCornerShape(8.dp),
ambientColor = ambientColor,
spotColor = spotColor
)
.background(Color.White, RoundedCornerShape(8.dp))
)
Text(
text = label,
fontSize = 12.sp,
color = Color.Gray
)
}
}
3.3 使用 graphicsLayer 实现高级阴影
@Composable
fun GraphicsLayerShadowExample() {
Column(
modifier = Modifier.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
// 基础 graphicsLayer 阴影
Box(
modifier = Modifier
.size(120.dp)
.graphicsLayer {
shadowElevation = 20.dp.toPx()
shape = RoundedCornerShape(16.dp)
clip = true
}
.background(Color.White)
)
// 带透明度的阴影
Box(
modifier = Modifier
.size(120.dp)
.graphicsLayer {
shadowElevation = 20.dp.toPx()
alpha = 0.9f
shape = CircleShape
clip = true
}
.background(Color(0xFF6200EE))
)
// 组合变换与阴影
Box(
modifier = Modifier
.size(120.dp)
.graphicsLayer {
shadowElevation = 30.dp.toPx()
rotationZ = 15f
scaleX = 1.1f
scaleY = 1.1f
shape = RoundedCornerShape(20.dp)
clip = true
}
.background(
brush = Brush.linearGradient(
colors = listOf(Color(0xFF667eea), Color(0xFF764ba2))
),
shape = RoundedCornerShape(20.dp)
)
)
}
}
四、自定义阴影实现
4.1 自定义阴影绘制逻辑
@Composable
fun CustomShadowDrawing() {
Column(
modifier = Modifier.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(24.dp)
) {
// 自定义模糊阴影
CustomBlurShadow()
// 内阴影效果
InnerShadow()
// 多层阴影
MultiLayerShadow()
// 渐变阴影
GradientShadow()
}
}
/**
* 自定义模糊阴影 - 使用BlurMaskFilter
*/
@Composable
private fun CustomBlurShadow() {
Box(
modifier = Modifier
.size(150.dp)
.drawBehind {
val paint = Paint().apply {
color = Color(0xFF6200EE).copy(alpha = 0.3f)
asFrameworkPaint().apply {
maskFilter = BlurMaskFilter(
30f,
BlurMaskFilter.Blur.NORMAL
)
}
}
// 绘制阴影(偏移)
drawContext.canvas.nativeCanvas.apply {
translate(10f, 10f)
drawRoundRect(
0f, 0f, size.width - 20f, size.height - 20f,
20f, 20f,
paint.asFrameworkPaint()
)
}
}
.background(
Color.White,
RoundedCornerShape(16.dp)
),
contentAlignment = Alignment.Center
) {
Text("Custom Blur Shadow")
}
}
/**
* 内阴影效果
*/
@Composable
private fun InnerShadow() {
Box(
modifier = Modifier
.size(150.dp)
.drawWithContent {
drawContent()
// 绘制内阴影
drawRect(
brush = Brush.verticalGradient(
colors = listOf(
Color.Black.copy(alpha = 0.2f),
Color.Transparent,
Color.Transparent,
Color.Black.copy(alpha = 0.2f)
)
),
blendMode = BlendMode.DstIn
)
}
.background(
Color(0xFFF5F5F5),
RoundedCornerShape(16.dp)
)
.border(
width = 1.dp,
color = Color.LightGray,
shape = RoundedCornerShape(16.dp)
),
contentAlignment = Alignment.Center
) {
Text("Inner Shadow")
}
}
/**
* 多层阴影效果
*/
@Composable
private fun MultiLayerShadow() {
Box(
modifier = Modifier
.size(150.dp)
.drawBehind {
val frameworkPaint = Paint().asFrameworkPaint()
// 第一层 - 大范围浅阴影
frameworkPaint.apply {
color = android.graphics.Color.parseColor("#1A000000")
maskFilter = BlurMaskFilter(60f, BlurMaskFilter.Blur.NORMAL)
}
drawContext.canvas.nativeCanvas.drawRoundRect(
-10f, -10f, size.width + 10f, size.height + 10f,
30f, 30f, frameworkPaint
)
// 第二层 - 中等范围阴影
frameworkPaint.apply {
color = android.graphics.Color.parseColor("#26000000")
maskFilter = BlurMaskFilter(30f, BlurMaskFilter.Blur.NORMAL)
}
drawContext.canvas.nativeCanvas.drawRoundRect(
0f, 0f, size.width, size.height,
20f, 20f, frameworkPaint
)
// 第三层 - 小范围深阴影
frameworkPaint.apply {
color = android.graphics.Color.parseColor("#33000000")
maskFilter = BlurMaskFilter(10f, BlurMaskFilter.Blur.NORMAL)
}
drawContext.canvas.nativeCanvas.drawRoundRect(
5f, 5f, size.width - 5f, size.height - 5f,
16f, 16f, frameworkPaint
)
}
.background(
Color.White,
RoundedCornerShape(16.dp)
),
contentAlignment = Alignment.Center
) {
Text("Multi-Layer Shadow")
}
}
/**
* 渐变阴影效果
*/
@Composable
private fun GradientShadow() {
Box(
modifier = Modifier
.size(150.dp)
.drawBehind {
// 创建渐变阴影路径
val shadowPath = Path().apply {
addRoundRect(
RoundRect(
rect = Rect(
offset = Offset(8f, 8f),
size = Size(size.width - 16f, size.height - 16f)
),
cornerRadius = CornerRadius(20f, 20f)
)
)
}
// 绘制渐变阴影
drawPath(
path = shadowPath,
brush = Brush.radialGradient(
colors = listOf(
Color(0xFF6200EE).copy(alpha = 0.4f),
Color(0xFF6200EE).copy(alpha = 0.1f),
Color.Transparent
),
center = Offset(size.width / 2, size.height / 2),
radius = size.width * 0.7f
)
)
}
.background(
Color.White,
RoundedCornerShape(16.dp)
),
contentAlignment = Alignment.Center
) {
Text("Gradient Shadow")
}
}
4.2 复杂形状阴影实现
@Composable
fun ComplexShapeShadows() {
Column(
modifier = Modifier.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(24.dp)
) {
// 气泡对话框阴影
ChatBubbleShadow()
// 不规则图形阴影
IrregularShapeShadow()
// 文字阴影
TextShadow()
}
}
/**
* 聊天气泡带阴影
*/
@Composable
private fun ChatBubbleShadow() {
val bubbleShape = GenericShape { size, _ ->
val cornerRadius = 20f
val triangleHeight = 30f
val triangleWidth = 40f
// 主体圆角矩形
moveTo(cornerRadius, 0f)
lineTo(size.width - cornerRadius, 0f)
arcTo(
rect = Rect(
offset = Offset(size.width - cornerRadius * 2, 0f),
size = Size(cornerRadius * 2, cornerRadius * 2)
),
startAngleDegrees = 270f,
sweepAngleDegrees = 90f,
forceMoveTo = false
)
lineTo(size.width, size.height - cornerRadius - triangleHeight)
arcTo(
rect = Rect(
offset = Offset(size.width - cornerRadius * 2, size.height - cornerRadius * 2 - triangleHeight),
size = Size(cornerRadius * 2, cornerRadius * 2)
),
startAngleDegrees = 0f,
sweepAngleDegrees = 90f,
forceMoveTo = false
)
// 底部三角形
lineTo(size.width / 2 + triangleWidth / 2, size.height - triangleHeight)
lineTo(size.width / 2, size.height)
lineTo(size.width / 2 - triangleWidth / 2, size.height - triangleHeight)
lineTo(cornerRadius, size.height - triangleHeight)
arcTo(
rect = Rect(
offset = Offset(0f, size.height - cornerRadius * 2 - triangleHeight),
size = Size(cornerRadius * 2, cornerRadius * 2)
),
startAngleDegrees = 90f,
sweepAngleDegrees = 90f,
forceMoveTo = false
)
lineTo(0f, cornerRadius)
arcTo(
rect = Rect(
offset = Offset(0f, 0f),
size = Size(cornerRadius * 2, cornerRadius * 2)
),
startAngleDegrees = 180f,
sweepAngleDegrees = 90f,
forceMoveTo = false
)
close()
}
Box(
modifier = Modifier
.width(200.dp)
.height(100.dp)
.shadow(
elevation = 8.dp,
shape = bubbleShape,
clip = false
)
.background(Color(0xFF6200EE), bubbleShape)
.padding(16.dp),
contentAlignment = Alignment.Center
) {
Text(
text = "Chat Bubble",
color = Color.White,
fontSize = 16.sp
)
}
}
/**
* 不规则图形阴影
*/
@Composable
private fun IrregularShapeShadow() {
val irregularShape = GenericShape { size, _ ->
val path = Path().apply {
// 创建波浪形路径
moveTo(0f, size.height * 0.3f)
// 上边波浪
cubicTo(
size.width * 0.3f, 0f,
size.width * 0.7f, size.height * 0.6f,
size.width, size.height * 0.3f
)
// 右边
lineTo(size.width, size.height * 0.8f)
// 下边波浪
cubicTo(
size.width * 0.7f, size.height,
size.width * 0.3f, size.height * 0.4f,
0f, size.height * 0.8f
)
close()
}
addPath(path)
}
Box(
modifier = Modifier
.size(150.dp)
.shadow(
elevation = 12.dp,
shape = irregularShape,
clip = false,
spotColor = Color(0xFF00BCD4).copy(alpha = 0.5f),
ambientColor = Color(0xFF00BCD4).copy(alpha = 0.3f)
)
.background(
brush = Brush.linearGradient(
colors = listOf(Color(0xFF00BCD4), Color(0xFF00838F))
),
shape = irregularShape
)
)
}
/**
* 文字阴影效果
*/
@Composable
private fun TextShadow() {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
// 基础文字阴影
Text(
text = "Shadow Text",
fontSize = 32.sp,
fontWeight = FontWeight.Bold,
color = Color.White,
style = TextStyle(
shadow = Shadow(
color = Color.Black.copy(alpha = 0.5f),
offset = Offset(4f, 4f),
blurRadius = 8f
)
)
)
// 彩色文字阴影
Text(
text = "Colorful Shadow",
fontSize = 32.sp,
fontWeight = FontWeight.Bold,
color = Color(0xFF6200EE),
style = TextStyle(
shadow = Shadow(
color = Color(0xFF00BCD4).copy(alpha = 0.6f),
offset = Offset(6f, 6f),
blurRadius = 12f
)
)
)
// 多层文字阴影
Text(
text = "Multi Shadow",
fontSize = 32.sp,
fontWeight = FontWeight.Bold,
color = Color.White,
modifier = Modifier.drawBehind {
val textPaint = Paint().asFrameworkPaint().apply {
isAntiAlias = true
textSize = 32.sp.toPx()
typeface = Typeface.DEFAULT_BOLD
}
// 绘制多层阴影
for (i in 5 downTo 1) {
textPaint.color = android.graphics.Color.argb(
30, 0, 0, 0
)
drawContext.canvas.nativeCanvas.drawText(
"Multi Shadow",
i * 2f,
size.height / 2 + i * 2f,
textPaint
)
}
}
)
}
}
4.3 阴影动画效果
@Composable
fun AnimatedShadows() {
Column(
modifier = Modifier.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(32.dp)
) {
// 呼吸阴影动画
BreathingShadow()
// 颜色变化阴影
ColorChangingShadow()
// 偏移动画阴影
OffsetAnimatingShadow()
// 按压反馈阴影
PressableShadow()
}
}
/**
* 呼吸阴影动画
*/
@Composable
private fun BreathingShadow() {
val infiniteTransition = rememberInfiniteTransition(label = "breathing")
val elevation by infiniteTransition.animateFloat(
initialValue = 4.dp.value,
targetValue = 20.dp.value,
animationSpec = infiniteRepeatable(
animation = tween(1500, easing = FastOutSlowInEasing),
repeatMode = RepeatMode.Reverse
),
label = "elevation"
)
Box(
modifier = Modifier
.size(120.dp)
.shadow(
elevation = elevation.dp,
shape = RoundedCornerShape(16.dp),
spotColor = Color(0xFF6200EE).copy(alpha = 0.5f)
)
.background(Color.White, RoundedCornerShape(16.dp)),
contentAlignment = Alignment.Center
) {
Text("Breathing")
}
}
/**
* 颜色变化阴影
*/
@Composable
private fun ColorChangingShadow() {
val infiniteTransition = rememberInfiniteTransition(label = "color")
val hue by infiniteTransition.animateFloat(
initialValue = 0f,
targetValue = 360f,
animationSpec = infiniteRepeatable(
animation = tween(3000, easing = LinearEasing)
),
label = "hue"
)
val shadowColor = remember(hue) {
Color.hsl(hue, 0.8f, 0.5f)
}
Box(
modifier = Modifier
.size(120.dp)
.shadow(
elevation = 16.dp,
shape = CircleShape,
spotColor = shadowColor.copy(alpha = 0.6f),
ambientColor = shadowColor.copy(alpha = 0.4f)
)
.background(Color.White, CircleShape),
contentAlignment = Alignment.Center
) {
Text("Color Change")
}
}
/**
* 偏移动画阴影
*/
@Composable
private fun OffsetAnimatingShadow() {
val infiniteTransition = rememberInfiniteTransition(label = "offset")
val offsetX by infiniteTransition.animateFloat(
initialValue = -10f,
targetValue = 10f,
animationSpec = infiniteRepeatable(
animation = tween(2000, easing = FastOutSlowInEasing),
repeatMode = RepeatMode.Reverse
),
label = "offsetX"
)
val offsetY by infiniteTransition.animateFloat(
initialValue = -10f,
targetValue = 10f,
animationSpec = infiniteRepeatable(
animation = tween(2000, easing = FastOutSlowInEasing),
repeatMode = RepeatMode.Reverse
),
label = "offsetY"
)
// 使用自定义绘制实现偏移动画
Box(
modifier = Modifier
.size(120.dp)
.drawBehind {
val paint = Paint().asFrameworkPaint().apply {
color = android.graphics.Color.parseColor("#4D000000")
maskFilter = BlurMaskFilter(20f, BlurMaskFilter.Blur.NORMAL)
}
drawContext.canvas.nativeCanvas.drawRoundRect(
offsetX + 10f,
offsetY + 10f,
size.width + offsetX - 10f,
size.height + offsetY - 10f,
20f, 20f,
paint
)
}
.background(Color.White, RoundedCornerShape(16.dp)),
contentAlignment = Alignment.Center
) {
Text("Moving Shadow")
}
}
/**
* 按压反馈阴影
*/
@Composable
private fun PressableShadow() {
var isPressed by remember { mutableStateOf(false) }
val elevation by animateDpAsState(
targetValue = if (isPressed) 4.dp else 16.dp,
animationSpec = spring(
dampingRatio = Spring.DampingRatioMediumBouncy,
stiffness = Spring.StiffnessLow
),
label = "pressElevation"
)
val scale by animateFloatAsState(
targetValue = if (isPressed) 0.95f else 1f,
animationSpec = spring(
dampingRatio = Spring.DampingRatioMediumBouncy,
stiffness = Spring.StiffnessLow
),
label = "pressScale"
)
Box(
modifier = Modifier
.size(120.dp)
.graphicsLayer {
scaleX = scale
scaleY = scale
}
.shadow(
elevation = elevation,
shape = RoundedCornerShape(20.dp),
spotColor = Color(0xFF6200EE).copy(alpha = 0.5f)
)
.background(
brush = Brush.linearGradient(
colors = listOf(Color(0xFF667eea), Color(0xFF764ba2))
),
shape = RoundedCornerShape(20.dp)
)
.clickable(
interactionSource = remember { MutableInteractionSource() },
indication = null
) {
isPressed = !isPressed
},
contentAlignment = Alignment.Center
) {
Text(
text = if (isPressed) "Pressed" else "Press Me",
color = Color.White,
fontWeight = FontWeight.Bold
)
}
}
五、最佳实践与性能优化
5.1 性能考量
5.1.1 阴影渲染成本
// 性能对比说明
// ✅ 推荐:使用系统shadow modifier(GPU加速)
Box(
modifier = Modifier.shadow(elevation = 8.dp)
)
// ⚠️ 谨慎:自定义BlurMaskFilter(CPU渲染,较耗性能)
Box(
modifier = Modifier.drawBehind {
val paint = Paint().asFrameworkPaint().apply {
maskFilter = BlurMaskFilter(30f, BlurMaskFilter.Blur.NORMAL)
}
// 绘制逻辑...
}
)
// ❌ 避免:频繁创建Paint对象
Box(
modifier = Modifier.drawBehind {
repeat(100) {
val paint = Paint() // 每次绘制都创建新对象
// 绘制逻辑...
}
}
)
5.1.2 优化建议
// ✅ 缓存Paint对象
object ShadowPaints {
val defaultShadowPaint = Paint().asFrameworkPaint().apply {
maskFilter = BlurMaskFilter(20f, BlurMaskFilter.Blur.NORMAL)
}
}
// ✅ 使用drawWithCache缓存复杂阴影
@Composable
fun OptimizedShadowBox() {
Box(
modifier = Modifier
.size(100.dp)
.drawWithCache {
// 在缓存中准备阴影绘制
val shadowPath = Path().apply {
addRoundRect(
RoundRect(
rect = size.toRect(),
cornerRadius = CornerRadius(20f, 20f)
)
)
}
onDrawBehind {
// 使用缓存的路径绘制阴影
drawPath(
path = shadowPath,
color = Color.Black.copy(alpha = 0.2f)
)
}
}
.background(Color.White, RoundedCornerShape(20.dp))
)
}
5.2 常见问题与解决方案
| 问题 | 原因 | 解决方案 |
|---|---|---|
| 阴影被裁剪 | clip参数为true或父布局裁剪 | 设置clip = false,检查父布局Modifier |
| 阴影颜色不显示 | API版本不支持 | 检查Compose版本(1.2+支持彩色阴影) |
| 阴影闪烁 | 频繁重组重建shadow | 使用remember缓存shadow配置 |
| 自定义形状阴影变形 | shape路径复杂 | 简化路径,使用GenericShape |
| 性能下降 | 过多阴影或复杂模糊 | 减少阴影数量,降低elevation值 |
5.3 跨版本兼容性处理
@Composable
fun CompatibleShadowBox(
elevation: Dp,
modifier: Modifier = Modifier,
shape: Shape = RectangleShape,
ambientColor: Color = Color.Black,
spotColor: Color = Color.Black
) {
val hasColorSupport = Build.VERSION.SDK_INT >= Build.VERSION_CODES.P ||
androidx.compose.ui.platform.LocalContext.current
.packageManager
.hasSystemFeature("android.software.compose")
Box(
modifier = if (hasColorSupport) {
modifier.shadow(
elevation = elevation,
shape = shape,
ambientColor = ambientColor,
spotColor = spotColor
)
} else {
// 降级方案:使用默认黑色阴影
modifier.shadow(
elevation = elevation,
shape = shape
)
}
)
}
5.4 Material Design 3阴影规范
@Composable
fun Material3ShadowTokens() {
Column(
modifier = Modifier.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
// Level 0 - 无阴影
ShadowTokenBox(
elevation = 0.dp,
label = "Level 0"
)
// Level 1 - 组件静止状态
ShadowTokenBox(
elevation = 1.dp,
label = "Level 1"
)
// Level 2 - 卡片静止状态
ShadowTokenBox(
elevation = 3.dp,
label = "Level 2"
)
// Level 3 - 抬升状态
ShadowTokenBox(
elevation = 6.dp,
label = "Level 3"
)
// Level 4 - 浮动按钮
ShadowTokenBox(
elevation = 8.dp,
label = "Level 4 (FAB)"
)
// Level 5 - 对话框
ShadowTokenBox(
elevation = 12.dp,
label = "Level 5 (Dialog)"
)
}
}
@Composable
private fun ShadowTokenBox(elevation: Dp, label: String) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(16.dp)
) {
Surface(
modifier = Modifier.size(80.dp),
shape = RoundedCornerShape(12.dp),
shadowElevation = elevation,
color = MaterialTheme.colorScheme.surface
) {}
Text(
text = label,
style = MaterialTheme.typography.bodyMedium
)
}
}
5.5 实用工具类封装
/**
* 阴影工具类
*/
object ShadowUtils {
/**
* 创建Material风格阴影
*/
@Composable
fun Modifier.materialShadow(
elevation: Dp,
shape: Shape = RoundedCornerShape(8.dp)
): Modifier = this.shadow(
elevation = elevation,
shape = shape,
ambientColor = Color.Black.copy(alpha = 0.1f),
spotColor = Color.Black.copy(alpha = 0.2f)
)
/**
* 创建霓虹灯风格阴影
*/
@Composable
fun Modifier.neonShadow(
color: Color,
elevation: Dp = 8.dp
): Modifier = this.shadow(
elevation = elevation,
shape = RoundedCornerShape(12.dp),
ambientColor = color.copy(alpha = 0.3f),
spotColor = color.copy(alpha = 0.5f)
)
/**
* 创建柔和阴影
*/
@Composable
fun Modifier.softShadow(
elevation: Dp = 4.dp,
shape: Shape = RoundedCornerShape(16.dp)
): Modifier = this.shadow(
elevation = elevation,
shape = shape,
ambientColor = Color.Black.copy(alpha = 0.05f),
spotColor = Color.Black.copy(alpha = 0.1f)
)
/**
* 创建深度阴影(适合深色主题)
*/
@Composable
fun Modifier.deepShadow(
elevation: Dp = 16.dp,
shape: Shape = RoundedCornerShape(8.dp)
): Modifier = this.shadow(
elevation = elevation,
shape = shape,
ambientColor = Color.Black.copy(alpha = 0.4f),
spotColor = Color.Black.copy(alpha = 0.6f)
)
}
// 使用示例
@Composable
fun ShadowUtilsDemo() {
Column(
modifier = Modifier.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
// Material阴影
Box(
modifier = Modifier
.size(100.dp)
.ShadowUtils.materialShadow(elevation = 4.dp)
.background(Color.White)
)
// 霓虹阴影
Box(
modifier = Modifier
.size(100.dp)
.ShadowUtils.neonShadow(color = Color(0xFF00E5FF))
.background(Color(0xFF1A1A2E))
)
// 柔和阴影
Box(
modifier = Modifier
.size(100.dp)
.ShadowUtils.softShadow()
.background(Color.White)
)
// 深度阴影
Box(
modifier = Modifier
.size(100.dp)
.ShadowUtils.deepShadow()
.background(Color(0xFF2D2D2D))
)
}
}
总结
Jetpack Compose提供了强大而灵活的阴影系统,从简单的shadow modifier到复杂的自定义绘制,开发者可以根据需求选择合适的方式:
- 基础使用:使用
Modifier.shadow()快速添加阴影效果 - 颜色定制:利用
ambientColor和spotColor实现彩色阴影 - 自定义形状:通过
GenericShape创建任意形状的阴影 - 动画效果:配合Compose动画API实现动态阴影
- 性能优化:合理使用缓存和GPU加速,避免过度绘制
掌握这些技术,您可以在Compose应用中创建出丰富、美观且具有层次感的UI效果。