As sombras elevam visualmente a interface, indicam interatividade aos usuários e fornecem feedback imediato sobre as ações deles. O Compose oferece várias maneiras de incorporar sombras ao seu app:
Modifier.shadow(): cria uma sombra com base na elevação atrás de um elemento combinável que está em conformidade com as diretrizes do Material Design.Modifier.dropShadow(): cria uma sombra personalizável que aparece atrás de um elemento combinável, fazendo com que ele pareça elevado.Modifier.innerShadow(): cria uma sombra dentro das bordas de um elemento combinável, fazendo com que ele pareça pressionado na superfície atrás dele.
Modifier.shadow() é adequado para criar sombras básicas, enquanto os modificadores dropShadow() e innerShadow() oferecem mais controle e precisão sobre a renderização de sombras.
Esta página descreve como implementar cada um desses modificadores, incluindo como
animar sombras na interação do usuário e como encadear os modificadores
innerShadow() e dropShadow() para
criar sombras gradientes,
sombras neumórficas e muito mais.
Criar sombras básicas
Modifier.shadow() cria uma sombra básica seguindo as diretrizes do Material Design
que simula uma fonte de luz suspensa. A profundidade da sombra é baseada em um valor elevation, e a sombra projetada é cortada na forma do elemento combinável.
@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().Implementar sombras projetadas
Use o modificador dropShadow() para desenhar uma sombra precisa atrás do seu
conteúdo, o que faz com que o elemento pareça elevado.
É possível controlar os seguintes aspectos principais pelo parâmetro Shadow:
radius: define a suavidade e a difusão do desfoque.color: define a cor da tonalidade.offset: posiciona a geometria da sombra ao longo dos eixos x e y.spread: controla a expansão ou contração da geometria da sombra.
Além disso, o parâmetro shape define a forma geral da sombra. Ele pode
usar qualquer geometria do pacote androidx.compose.foundation.shape, bem como
as formas expressivas do Material.
Para implementar uma sombra projetada básica, adicione o modificador dropShadow() à cadeia de elementos combináveis, fornecendo o raio, a cor e a propagação. O plano de fundo purpleColor que aparece na parte de cima da sombra é desenhado após o 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 ) } } }
Principais pontos sobre o código
- O modificador
dropShadow()é aplicado àBoxinterna. A sombra tem as seguintes características:- Uma forma de retângulo arredondado (
RoundedCornerShape(20.dp)) - Um raio de desfoque de
10.dp, tornando as bordas suaves e difusas - Uma propagação de
6.dp, que expande o tamanho da sombra e a torna maior do que a caixa que a projeta - Um alfa de
0.5f, tornando a sombra semitransparente
- Uma forma de retângulo arredondado (
- Depois que a sombra é definida, o modificador .
background()é aplicado.- A
Boxé preenchida com uma cor branca. - O plano de fundo é cortado na mesma forma de retângulo arredondado da sombra.
- A
Resultado
Implementar sombras internas
Para criar um efeito inverso a dropShadow(), use
Modifier.innerShadow(), que cria a ilusão de que um elemento está
rebaixado ou pressionado na superfície subjacente.
A ordem é importante ao criar sombras internas. O modificador innerShadow() é desenhado acima do conteúdo. Para garantir que a sombra esteja visível, normalmente você realiza as seguintes etapas:
- Desenhe o conteúdo do plano de fundo.
- Aplique o modificador
innerShadow()para criar a aparência côncava.
Se o innerShadow() for colocado antes do plano de fundo, ele será desenhado sobre a sombra, ocultando-a completamente.
O exemplo a seguir mostra uma aplicação de innerShadow() em um 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() em um retângulo de canto arredondado.Animar sombras na interação do usuário
Para que as sombras respondam às interações do usuário, é possível integrar propriedades de sombra a APIs de animação do Compose. Quando um usuário pressiona um botão, por exemplo, a sombra pode mudar para fornecer feedback visual instantâneo.
O código a seguir cria um efeito "pressionado" com uma sombra (a ilusão de que a plataforma está sendo empurrada para baixo na tela):
@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", // ... ) } } } }
Principais pontos sobre o código
- Declara os estados inicial e final dos parâmetros a serem animados ao pressionar com
transition.animateColoretransition.animateFloat. - Usa
updateTransitione fornece otargetState (targetState = isPressed)escolhido para verificar se todas as animações estão sincronizadas. Sempre queisPressedmuda, o objeto de transição gerencia automaticamente a animação de todas as propriedades filhas dos valores atuais para os novos valores de destino. - Define a especificação
buttonPressAnimation, que controla o tempo e a facilidade da transição. Ela especifica umtween(abreviação de "in-between") com uma duração de 400 milissegundos e uma curvaEaseInOut, o que significa que a animação começa lenta, acelera no meio e desacelera no final. - Define uma
Boxcom uma cadeia de funções modificadoras que aplicam todas as propriedades animadas para criar o elemento visual, incluindo o seguinte:- .
clickable(): um modificador que torna aBoxinterativa. .dropShadow(): duas sombras projetadas externas são aplicadas primeiro. As propriedades de cor e alfa estão vinculadas aos valores animados (blueDropShadowetc.) e criam a aparência elevada inicial..innerShadow(): duas sombras internas são desenhadas na parte de cima do plano de fundo. As propriedades delas estão vinculadas ao outro conjunto de valores animados (innerShadowColor1etc.) e criam a aparência recuada.
- .
Resultado
Criar sombras gradientes
As sombras não estão limitadas a cores sólidas. A API Shadow aceita um Brush, que permite criar sombras gradientes.
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 ) }
Principais pontos sobre o código
dropShadow()adiciona uma sombra atrás da caixa.brush = Brush.sweepGradient(colors)colore a sombra com um gradiente que gira por uma lista decolorspredefinidas, criando um efeito semelhante a um arco-íris.
Resultado
É possível usar um pincel como uma sombra para criar um dropShadow() gradiente com uma animação "respiratória":
Combinar sombras
É possível combinar e sobrepor os modificadores dropShadow() e innerShadow() para criar uma variedade de efeitos. As seções a seguir mostram como produzir sombras neumórficas, neobrutalistas e realistas com essa técnica.
Criar sombras neumórficas
As sombras neumórficas são caracterizadas por uma aparência suave que emerge organicamente do plano de fundo. Para criar sombras neumórficas, faça o seguinte:
- Use um elemento que compartilhe as mesmas cores do plano de fundo.
- Aplique duas sombras projetadas fracas e opostas: uma sombra clara em um canto e uma sombra escura no canto oposto.
O snippet a seguir sobrepõe dois modificadores dropShadow() para criar o efeito 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) ) }
Criar sombras neobrutalistas
O estilo neobrutalista apresenta layouts de blocos de alto contraste, cores vivas e bordas grossas. Para criar esse efeito, use um dropShadow() com desfoque zero e um deslocamento distinto, conforme mostrado no snippet a seguir:
@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 ) } } } }
Criar sombras realistas
As sombras realistas imitam sombras no mundo físico. Elas parecem iluminadas por uma fonte de luz primária, resultando em uma sombra direta e uma sombra mais difusa. É possível empilhar várias instâncias dropShadow() e innerShadow() com propriedades diferentes para recriar efeitos de sombra realistas, conforme mostrado no snippet a seguir:
@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 ) } } }
Principais pontos sobre o código
- Dois modificadores
dropShadow()encadeados com propriedades distintas são aplicados, seguidos por um modificadorbackground(). - Os modificadores
innerShadow()encadeados são aplicados para criar o efeito de aro metálico ao redor da borda do componente.
Resultado
O snippet de código anterior produz o seguinte: