阴影可以直观地提升界面,向用户指示互动性,并针对用户操作提供即时反馈。Compose 提供了多种将阴影融入应用的方法:
Modifier.shadow():在符合 Material Design 准则的可组合项后面创建基于海拔高度的阴影。Modifier.dropShadow():创建显示在可组合项后面的可自定义阴影,使其看起来具有海拔高度。Modifier.innerShadow():在可组合项的边框内创建阴影,使其看起来像是被压入其后面的表面。
Modifier.shadow() 适合创建基本阴影,而 dropShadow() 和 innerShadow() 修饰符则可以更精细地控制阴影的渲染。
本页面介绍了如何实现这些修饰符中的每一个,包括如何在用户互动时为阴影添加动画效果,以及如何将
innerShadow() 和 dropShadow() 修饰符链接在一起以
创建 渐变阴影、
拟态阴影等。
创建基本阴影
Modifier.shadow() 会创建遵循 Material Design
准则 的基本阴影,模拟来自上方的光源。阴影深度基于 elevation 值,并且投射的阴影会被裁剪为可组合项的形状。
@Composable fun ElevationBasedShadow() { Box( modifier = Modifier.aspectRatio(1f).fillMaxSize(), contentAlignment = Alignment.Center ) { Box( Modifier .size(100.dp, 100.dp) .shadow(10.dp, RectangleShape) .background(Color.White) ) } }
Modifier.shadow() 创建的基于海拔高度的阴影。实现阴影
使用 dropShadow() 修饰符在您的
内容后面绘制精确的阴影,使元素看起来具有海拔高度。
您可以通过其 Shadow 参数控制以下关键方面:
radius:定义模糊的柔和度和扩散度。color:定义色调的颜色。offset:沿 x 轴和 y 轴定位阴影的几何图形。spread:控制阴影几何图形的展开或收缩。
此外,shape 参数定义了阴影的整体形状。它可以
采用 androidx.compose.foundation.shape 软件包中的任何几何图形,以及
Material Expressive 形状。
如需实现基本阴影,请将 dropShadow() 修饰符添加到可组合项链中,并提供半径、颜色和扩散度。请注意,显示在阴影顶部的
purpleColor 背景是在 dropShadow() 修饰符之后绘制的:
@Composable fun SimpleDropShadowUsage() { Box(Modifier.fillMaxSize()) { Box( Modifier .width(300.dp) .height(300.dp) .dropShadow( shape = RoundedCornerShape(20.dp), shadow = Shadow( radius = 10.dp, spread = 6.dp, color = Color(0x40000000), offset = DpOffset(x = 4.dp, 4.dp) ) ) .align(Alignment.Center) .background( color = Color.White, shape = RoundedCornerShape(20.dp) ) ) { Text( "Drop Shadow", modifier = Modifier.align(Alignment.Center), fontSize = 32.sp ) } } }
代码要点
dropShadow()修饰符应用于内部Box。阴影具有以下特征:- 圆角矩形 (
RoundedCornerShape(20.dp)) - 模糊半径为
10.dp,使边缘柔和且扩散 - 扩散度为
6.dp,这会扩大阴影的大小,使其大于投射阴影的框 - alpha 为
0.5f,使阴影半透明
- 圆角矩形 (
- 定义阴影后,系统会应用 。
background()修饰符。Box填充了白色。- 背景被裁剪为与阴影相同的圆角矩形。
结果
实现内部阴影
如需创建与 dropShadow() 相反的特效,请使用
Modifier.innerShadow(),这会产生元素
凹陷或压入底层表面的错觉。
创建内部阴影时,顺序非常重要。innerShadow() 修饰符绘制在内容 顶部 。为确保阴影可见,您通常需要执行以下步骤:
- 绘制背景内容。
- 应用
innerShadow()修饰符以创建凹面外观。
如果 innerShadow() 放置在背景之前,则背景会绘制在阴影之上,从而完全隐藏阴影。
以下示例展示了在 RoundedCornerShape 上应用 innerShadow():
@Composable fun SimpleInnerShadowUsage() { Box(Modifier.fillMaxSize()) { Box( Modifier .width(300.dp) .height(200.dp) .align(Alignment.Center) // note that the background needs to be defined before defining the inner shadow .background( color = Color.White, shape = RoundedCornerShape(20.dp) ) .innerShadow( shape = RoundedCornerShape(20.dp), shadow = Shadow( radius = 10.dp, spread = 2.dp, color = Color(0x40000000), offset = DpOffset(x = 6.dp, 7.dp) ) ) ) { Text( "Inner Shadow", modifier = Modifier.align(Alignment.Center), fontSize = 32.sp ) } } }
Modifier.innerShadow()。在用户互动时为阴影添加动画效果
如需使阴影响应用户互动,您可以将阴影 属性与 Compose 的动画 API 集成。例如,当用户按下按钮时,阴影可以更改以提供即时视觉反馈。
以下代码使用阴影创建“按下”效果(表面被压入屏幕的错觉):
@Composable fun AnimatedColoredShadows() { SnippetsTheme { Box(Modifier.fillMaxSize()) { val interactionSource = remember { MutableInteractionSource() } val isPressed by interactionSource.collectIsPressedAsState() // Create transition with pressed state val transition = updateTransition( targetState = isPressed, label = "button_press_transition" ) fun <T> buttonPressAnimation() = tween<T>( durationMillis = 400, easing = EaseInOut ) // Animate all properties using the transition val shadowAlpha by transition.animateFloat( label = "shadow_alpha", transitionSpec = { buttonPressAnimation() } ) { pressed -> if (pressed) 0f else 1f } // ... val blueDropShadow by transition.animateColor( label = "shadow_color", transitionSpec = { buttonPressAnimation() } ) { pressed -> if (pressed) Color.Transparent else blueDropShadowColor } // ... Box( Modifier .clickable( interactionSource, indication = null ) { // ** ...... **// } .width(300.dp) .height(200.dp) .align(Alignment.Center) .dropShadow( shape = RoundedCornerShape(70.dp), shadow = Shadow( radius = 10.dp, spread = 0.dp, color = blueDropShadow, offset = DpOffset(x = 0.dp, -(2).dp), alpha = shadowAlpha ) ) .dropShadow( shape = RoundedCornerShape(70.dp), shadow = Shadow( radius = 10.dp, spread = 0.dp, color = darkBlueDropShadow, offset = DpOffset(x = 2.dp, 6.dp), alpha = shadowAlpha ) ) // note that the background needs to be defined before defining the inner shadow .background( color = Color(0xFFFFFFFF), shape = RoundedCornerShape(70.dp) ) .innerShadow( shape = RoundedCornerShape(70.dp), shadow = Shadow( radius = 8.dp, spread = 4.dp, color = innerShadowColor2, offset = DpOffset(x = 4.dp, 0.dp) ) ) .innerShadow( shape = RoundedCornerShape(70.dp), shadow = Shadow( radius = 20.dp, spread = 4.dp, color = innerShadowColor1, offset = DpOffset(x = 4.dp, 0.dp), alpha = innerShadowAlpha ) ) ) { Text( "Animated Shadows", // ... ) } } } }
代码要点
- 使用
transition.animateColor和transition.animateFloat声明参数的开始状态和结束状态,以便在按下时添加动画效果。 - 使用
updateTransition并为其提供所选的targetState (targetState = isPressed),以验证所有动画是否 同步。每当isPressed发生变化时,过渡对象都会自动管理所有子属性从当前值到新目标值的动画。 - 定义
buttonPressAnimation规范,该规范控制过渡的时序和缓动。它指定了一个tween(介于两者之间)和一个EaseInOut曲线,这意味着动画开始时速度很慢,中间加速,最后减速。 - 定义一个
Box,其中包含一系列修饰符函数,这些函数会应用所有动画属性来创建视觉元素,包括:- .
clickable():使Box可交互的修饰符。 .dropShadow():首先应用两个外部阴影。它们的颜色和 alpha 属性与动画值(blueDropShadow等)相关联,并创建初始凸起外观。.innerShadow():在背景上绘制两个内部阴影。 它们的属性与其他动画值集(innerShadowColor1等)相关联,并创建凹陷外观。
- .
结果
创建渐变阴影
阴影不限于纯色。阴影 API 接受 Brush,可让您创建渐变阴影。
Box( modifier = Modifier .width(240.dp) .height(200.dp) .dropShadow( shape = RoundedCornerShape(70.dp), shadow = Shadow( radius = 10.dp, spread = animatedSpread.dp, brush = Brush.sweepGradient( colors ), offset = DpOffset(x = 0.dp, y = 0.dp), alpha = animatedAlpha ) ) .clip(RoundedCornerShape(70.dp)) .background(Color(0xEDFFFFFF)), contentAlignment = Alignment.Center ) { Text( text = breathingText, color = Color.Black, style = MaterialTheme.typography.bodyLarge ) }
代码要点
dropShadow()在框后面添加阴影。brush = Brush.sweepGradient(colors)使用渐变色为阴影着色,该渐变色会 通过预定义的colors列表旋转,从而创建类似彩虹的效果。
结果
您可以使用笔刷作为阴影来创建具有“呼吸”动画的渐变 dropShadow():
组合阴影
您可以组合和分层 dropShadow() 和 innerShadow() 修饰符,以创建各种效果。以下部分将向您介绍如何使用此技术生成拟态阴影、新野兽主义阴影和逼真阴影。
创建拟态阴影
拟态阴影的特点是外观柔和,从背景中自然呈现。如需创建拟态阴影,请执行以下操作:
- 使用与背景颜色相同的元素。
- 应用两个微弱的相对阴影:一个角为浅色阴影,另一个角为深色阴影。
以下代码段分层了两个 dropShadow() 修饰符,以创建拟态效果:
@Composable fun NeumorphicRaisedButton( shape: RoundedCornerShape = RoundedCornerShape(30.dp) ) { val bgColor = Color(0xFFe0e0e0) val lightShadow = Color(0xFFFFFFFF) val darkShadow = Color(0xFFb1b1b1) val upperOffset = -10.dp val lowerOffset = 10.dp val radius = 15.dp val spread = 0.dp Box( modifier = Modifier .fillMaxSize() .background(bgColor) .wrapContentSize(Alignment.Center) .size(240.dp) .dropShadow( shape, shadow = Shadow( radius = radius, color = lightShadow, spread = spread, offset = DpOffset(upperOffset, upperOffset) ), ) .dropShadow( shape, shadow = Shadow( radius = radius, color = darkShadow, spread = spread, offset = DpOffset(lowerOffset, lowerOffset) ), ) .background(bgColor, shape) ) }
创建新野兽主义阴影
新野兽主义风格展示了高对比度、块状布局、鲜艳的色彩和粗边框。如需创建此效果,请使用模糊度为零且偏移量明显的 dropShadow(),如以下代码段所示:
@Composable fun NeoBrutalShadows() { SnippetsTheme { val dropShadowColor = Color(0xFF007AFF) val borderColor = Color(0xFFFF2D55) Box(Modifier.fillMaxSize()) { Box( Modifier .width(300.dp) .height(200.dp) .align(Alignment.Center) .dropShadow( shape = RoundedCornerShape(0.dp), shadow = Shadow( radius = 0.dp, spread = 0.dp, color = dropShadowColor, offset = DpOffset(x = 8.dp, 8.dp) ) ) .border( 8.dp, borderColor ) .background( color = Color.White, shape = RoundedCornerShape(0.dp) ) ) { Text( "Neobrutal Shadows", modifier = Modifier.align(Alignment.Center), style = MaterialTheme.typography.bodyMedium ) } } } }
创建逼真阴影
逼真阴影模仿物理世界中的阴影,看起来像是被主光源照亮,从而产生直接阴影和更弥散的阴影。您可以堆叠具有不同属性的多个 dropShadow() 和 innerShadow() 实例,以重现逼真的阴影效果,如以下代码段所示:
@Composable fun RealisticShadows() { Box(Modifier.fillMaxSize()) { val dropShadowColor1 = Color(0xB3000000) val dropShadowColor2 = Color(0x66000000) val innerShadowColor1 = Color(0xCC000000) val innerShadowColor2 = Color(0xFF050505) val innerShadowColor3 = Color(0x40FFFFFF) val innerShadowColor4 = Color(0x1A050505) Box( Modifier .width(300.dp) .height(200.dp) .align(Alignment.Center) .dropShadow( shape = RoundedCornerShape(100.dp), shadow = Shadow( radius = 40.dp, spread = 0.dp, color = dropShadowColor1, offset = DpOffset(x = 2.dp, 8.dp) ) ) .dropShadow( shape = RoundedCornerShape(100.dp), shadow = Shadow( radius = 4.dp, spread = 0.dp, color = dropShadowColor2, offset = DpOffset(x = 0.dp, 4.dp) ) ) // note that the background needs to be defined before defining the inner shadow .background( color = Color.Black, shape = RoundedCornerShape(100.dp) ) // // .innerShadow( shape = RoundedCornerShape(100.dp), shadow = Shadow( radius = 12.dp, spread = 3.dp, color = innerShadowColor1, offset = DpOffset(x = 6.dp, 6.dp) ) ) .innerShadow( shape = RoundedCornerShape(100.dp), shadow = Shadow( radius = 4.dp, spread = 1.dp, color = Color.White, offset = DpOffset(x = 5.dp, 5.dp) ) ) .innerShadow( shape = RoundedCornerShape(100.dp), shadow = Shadow( radius = 12.dp, spread = 5.dp, color = innerShadowColor2, offset = DpOffset(x = (-3).dp, (-12).dp) ) ) .innerShadow( shape = RoundedCornerShape(100.dp), shadow = Shadow( radius = 3.dp, spread = 10.dp, color = innerShadowColor3, offset = DpOffset(x = 0.dp, 0.dp) ) ) .innerShadow( shape = RoundedCornerShape(100.dp), shadow = Shadow( radius = 3.dp, spread = 9.dp, color = innerShadowColor4, offset = DpOffset(x = 1.dp, 1.dp) ) ) ) { Text( "Realistic Shadows", modifier = Modifier.align(Alignment.Center), fontSize = 24.sp, color = Color.White ) } } }
代码要点
- 应用了两个具有不同属性的链接
dropShadow()修饰符,后跟background()修饰符。 - 应用了链接
innerShadow()修饰符,以在组件边缘形成金属边框效果。
结果
前面的代码段会生成以下内容: