Las sombras elevan visualmente tu IU, indican interactividad a los usuarios y proporcionan comentarios inmediatos sobre las acciones de los usuarios. Compose proporciona varias formas de incorporar sombras en tu app:
Modifier.shadow(): Crea una sombra basada en la elevación detrás de un elemento componible que cumple con los lineamientos de Material Design.Modifier.dropShadow(): Crea una sombra personalizable que aparece detrás de un elemento componible, lo que hace que parezca elevado.Modifier.innerShadow(): Crea una sombra dentro de los bordes de un elemento componible, lo que hace que parezca presionado en la superficie detrás de él.
Modifier.shadow() es adecuado para crear sombras básicas, mientras que los modificadores dropShadow() y innerShadow() ofrecen un control y una precisión más detallados sobre la renderización de sombras.
En esta página, se describe cómo implementar cada uno de estos modificadores, incluido cómo
animar sombras en la interacción del usuario y cómo encadenar los
modificadores innerShadow() y dropShadow() para
crear sombras de gradiente, sombras neumórficas y mucho más.
Crea sombras básicas
Modifier.shadow() crea una sombra básica que sigue los lineamientos de Material Design que simulan una fuente de luz cenital. La profundidad de la sombra se basa en un valor elevation, y la sombra proyectada se recorta según la forma del elemento componible.
@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().Implementa sombras paralelas
Usa el modificador dropShadow() para dibujar una sombra precisa detrás de tu contenido, lo que hace que el elemento parezca elevado.
Puedes controlar los siguientes aspectos clave a través de su parámetro Shadow:
radius: Define la suavidad y la difusión del desenfoque.color: Define el color del matiz.offset: Posiciona la geometría de la sombra a lo largo de los ejes X e Y.spread: Controla la expansión o contracción de la geometría de la sombra.
Además, el parámetro shape define la forma general de la sombra. Puede
usar cualquier geometría del paquete androidx.compose.foundation.shape, así como
las formas de Material Expressive.
Para implementar una sombra paralela básica, agrega el modificador dropShadow() a tu cadena de elementos componibles y proporciona el radio, el color y la extensión. Ten en cuenta que el fondo purpleColor que aparece sobre la sombra se dibuja después del modificador 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 ) } } }
Puntos clave sobre el código
- El modificador
dropShadow()se aplica alBoxinterno. La sombra tiene las siguientes características:- Una forma de rectángulo redondeado (
RoundedCornerShape(20.dp)) - Un radio de desenfoque de
10.dp, lo que hace que los bordes sean suaves y difusos - Una extensión de
6.dp, que expande el tamaño de la sombra y la hace más grande que la caja que la proyecta - Un valor alfa de
0.5f, lo que hace que la sombra sea semitransparente
- Una forma de rectángulo redondeado (
- Después de definir la sombra, elSe aplica el modificador
background().- El
Boxse rellena con un color blanco. - El fondo se recorta con la misma forma de rectángulo redondeado que la sombra.
- El
Resultado
Implementa sombras interiores
Para crear un efecto inverso a dropShadow(), usa Modifier.innerShadow(), que crea la ilusión de que un elemento está empotrado o presionado en la superficie subyacente.
El orden es importante cuando se crean sombras interiores. El modificador innerShadow()
se dibuja encima del contenido. Para asegurarte de que la sombra sea visible, por lo general, debes seguir estos pasos:
- Dibuja el contenido del fondo.
- Aplica el modificador
innerShadow()para crear la apariencia cóncava.
Si el innerShadow() se coloca antes del fondo, este se dibuja sobre la sombra y la oculta por completo.
En el siguiente ejemplo, se muestra una aplicación de innerShadow() en un
RoundedCornerShape:
@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() en un rectángulo de esquina redondeadaCómo animar sombras en la interacción del usuario
Para que las sombras respondan a las interacciones del usuario, puedes integrar las propiedades de sombra con las APIs de animación de Compose. Por ejemplo, cuando un usuario presiona un botón, la sombra puede cambiar para proporcionar comentarios visuales instantáneos.
El siguiente código crea un efecto de "presionado" con una sombra (la ilusión de que la superficie se empuja hacia abajo en la pantalla):
@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", // ... ) } } } }
Puntos clave sobre el código
- Declara los estados de inicio y finalización de los parámetros que se animarán cuando se presione con
transition.animateColorytransition.animateFloat. - Usa
updateTransitiony le proporciona eltargetState (targetState = isPressed)elegido para verificar que todas las animaciones estén sincronizadas. Cada vez que cambiaisPressed, el objeto de transición administra automáticamente la animación de todas las propiedades secundarias desde sus valores actuales hasta los nuevos valores de destino. - Define la especificación
buttonPressAnimation, que controla el tiempo y la aceleración de la transición. Especifica untween(abreviatura de intermedio) con una duración de 400 milisegundos y una curvaEaseInOut, lo que significa que la animación comienza lentamente, se acelera en el medio y se ralentiza al final. - Define un
Boxcon una cadena de funciones modificadoras que aplican todas las propiedades animadas para crear el elemento visual, incluidas las siguientes:- .
clickable(): Es un modificador que hace que elBoxsea interactivo. .dropShadow(): Primero, se aplican dos sombras paralelas exteriores. Sus propiedades de color y alfa están vinculadas a los valores animados (blueDropShadow, etc.) y crean la apariencia elevada inicial..innerShadow(): Se dibujan dos sombras internas sobre el fondo. Sus propiedades están vinculadas al otro conjunto de valores animados (innerShadowColor1, etcétera) y crean la apariencia con sangría.
- .
Resultado
Crea sombras con gradiente
Las sombras no se limitan a colores sólidos. La API de sombras acepta un Brush, que te permite crear sombras de gradiente.
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 ) }
Puntos clave sobre el código
dropShadow()agrega una sombra detrás del cuadro.brush = Brush.sweepGradient(colors)colorea la sombra con un gradiente que rota a través de una lista decolorspredefinidos, lo que crea un efecto similar al de un arcoíris.
Resultado
Puedes usar un pincel como sombra para crear un dropShadow() de gradiente con una animación de "respiración":
Combina sombras
Puedes combinar y superponer los modificadores dropShadow() y innerShadow() para crear una variedad de efectos. En las siguientes secciones, se muestra cómo producir sombras neumórficas, neobrutalistas y realistas con esta técnica.
Crea sombras neumórficas
Las sombras neumórficas se caracterizan por una apariencia suave que surge de forma orgánica del fondo. Para crear sombras neumórficas, haz lo siguiente:
- Usa un elemento que comparta los mismos colores que su fondo.
- Aplica dos sombras paralelas tenues y opuestas: una sombra clara en una esquina y una sombra oscura en la esquina opuesta.
En el siguiente fragmento, se superponen dos modificadores dropShadow() para crear el efecto neumórfico:
@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) ) }
Cómo crear sombras neobrutalistas
El estilo neobrutalista presenta diseños en bloques de alto contraste, colores vivos
y bordes gruesos. Para crear este efecto, usa un dropShadow() con desenfoque cero y un desplazamiento distinto, como se muestra en el siguiente fragmento:
@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 ) } } } }
Crea sombras realistas
Las sombras realistas imitan las sombras del mundo físico: aparecen iluminadas por una fuente de luz principal, lo que genera una sombra directa y una sombra más difusa. Puedes apilar varias instancias de dropShadow() y innerShadow() con diferentes propiedades para recrear efectos de sombra realistas, como se muestra en el siguiente fragmento:
@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 ) } } }
Puntos clave sobre el código
- Se aplican dos modificadores
dropShadow()encadenados con propiedades distintas, seguidos de un modificadorbackground(). - Se aplican modificadores
innerShadow()encadenados para crear el efecto de borde metálico alrededor del borde del componente.
Resultado
El fragmento de código anterior produce lo siguiente:
