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 app:
Modifier.shadow(): cria uma sombra baseada em elevação atrás de um elemento combinável que segue 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 contra a superfície atrás dele.
Modifier.shadow() é adequado para criar sombras básicas, enquanto os modificadores
dropShadow() e innerShadow() oferecem controle e precisão mais refinados
sobre a renderização de sombras.
Nesta página, descrevemos 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 de gradiente, 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 superior. A profundidade
da sombra é baseada em um valor elevation, e a sombra projetada é cortada de acordo com a
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
conteúdo, o que faz com que o elemento pareça elevado.
É possível controlar os seguintes aspectos principais usando o 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, além
das formas expressivas do Material Design.
Para implementar uma sombra projetada básica, adicione o modificador dropShadow() à sua
cadeia de combináveis, fornecendo o raio, a cor e a propagação. O plano de fundo
purpleColor que aparece sobre a 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 ) } } }
Pontos principais sobre o código
- O modificador
dropShadow()é aplicado aoBoxinterno. A sombra tem as seguintes características:- Uma forma de retângulo arredondado (
RoundedCornerShape(20.dp)) - Um raio de desfoque de
10.dp, que suaviza e difunde as bordas. - Uma propagação de
6.dp, que expande o tamanho da sombra e a torna maior 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, oO modificador
background()é aplicado.- A
Boxé preenchida com uma cor branca. - O plano de fundo é cortado na mesma forma de retângulo arredondado que a 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 abaixo.
A ordem é importante ao criar sombras internas. O modificador innerShadow()
desenha na parte de cima do conteúdo. Para garantir que a sombra fique visível, geralmente
você segue estas etapas:
- Desenhe o conteúdo em segundo plano.
- Aplique o modificador
innerShadow()para criar a aparência côncava.
Se o innerShadow() for colocado antes do plano de fundo, o plano de fundo 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, integre as propriedades de sombra às 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 de "pressionado" com uma sombra (a ilusão de que a superfície 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", // ... ) } } } }
Pontos principais sobre o código
- Declara os estados inicial e final para os parâmetros 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 easing da transição. Ele especifica umtween(abreviação de "in-between", ou seja, "intermediário") com uma duração de 400 milissegundos e uma curvaEaseInOut, o que significa que a animação começa devagar, acelera no meio e desacelera no final. - Define um
Boxcom uma cadeia de funções modificadoras que aplicam todas as propriedades animadas para criar o elemento visual, incluindo:- .
clickable(): um modificador que torna oBoxinterativo. .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 sobre o plano de fundo. As propriedades deles estão vinculadas ao outro conjunto de valores animados (innerShadowColor1etc.) e criam a aparência recuada.
- .
Resultado
Criar sombras em gradiente
As sombras não se limitam a cores sólidas. A API de sombra aceita um Brush, que
permite criar 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 ) }
Pontos principais 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 decolorspredefinidos, criando um efeito de arco-íris.
Resultado
É possível usar um pincel como sombra para criar um gradiente dropShadow() com uma
animação de "respiração":
Combinar sombras
É possível combinar e sobrepor os modificadores dropShadow() e innerShadow() para
criar vários 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 surge organicamente do plano de fundo. Para criar sombras neumórficas, faça o seguinte:
- Use um elemento que tenha as mesmas cores do plano de fundo.
- Aplique duas sombras projetadas leves 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 em 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 abaixo:
@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 as sombras do mundo físico: elas parecem iluminadas por uma
fonte de luz principal, resultando em uma sombra direta e outra mais
difusa. É possível empilhar várias instâncias de dropShadow() e innerShadow() com
propriedades diferentes para recriar efeitos de sombra realistas, como 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 ) } } }
Pontos principais 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 borda metálica ao redor do componente.
Resultado
O snippet de código anterior produz o seguinte: