Le ombre elevano visivamente l'interfaccia utente, indicano l'interattività agli utenti e forniscono un feedback immediato sulle azioni dell'utente. Compose offre diversi modi per incorporare le ombre nella tua app:
Modifier.shadow(): crea un'ombra basata sull'elevazione dietro un elemento componibile conforme alle linee guida di Material Design.Modifier.dropShadow(): crea un'ombra personalizzabile che appare dietro un elemento componibile, facendolo sembrare elevato.Modifier.innerShadow(): crea un'ombra all'interno dei bordi di un elemento componibile, facendolo sembrare premuto sulla superficie sottostante.
Modifier.shadow() è adatto per la creazione di ombre di base, mentre i modificatori dropShadow() e innerShadow() offrono un controllo e una precisione più granulari sul rendering delle ombre.
Questa pagina descrive come implementare ciascuno di questi modificatori, incluso come
animare le ombre in base all'interazione dell'utente e come concatenare i modificatori
innerShadow() e dropShadow() per
creare ombre sfumate,
ombre neumorfiche, e altro ancora.
Creare ombre di base
Modifier.shadow() crea un'ombra di base che segue le linee guida
diMaterial Design che simula una sorgente luminosa dall'alto. La profondità dell'ombra si basa su un valore elevation e l'ombra proiettata viene ritagliata in base alla forma dell'elemento componibile.
@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().Implementare le ombre
Utilizza il modificatore dropShadow() per disegnare un'ombra precisa dietro i tuoi
contenuti, in modo che l'elemento sembri elevato.
Puoi controllare i seguenti aspetti chiave tramite il parametro Shadow:
radius: definisce la morbidezza e la diffusione della sfocatura.color: definisce il colore della tinta.offset: posiziona la geometria dell'ombra lungo gli assi x e y.spread: controlla l'espansione o la contrazione della geometria dell'ombra.
Inoltre, il parametro shape definisce la forma complessiva dell'ombra. Può
utilizzare qualsiasi geometria dal pacchetto androidx.compose.foundation.shape, nonché
le forme espressive di Material.
Per implementare un'ombra di base, aggiungi il modificatore dropShadow() alla catena di elementi componibili, fornendo il raggio, il colore e la diffusione. Tieni presente che lo sfondo purpleColor che appare sopra l'ombra viene disegnato dopo il modificatore 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 ) } } }
Punti chiave sul codice
- Il modificatore
dropShadow()viene applicato allaBoxinterna. L'ombra ha le seguenti caratteristiche:- Una forma rettangolare arrotondata (
RoundedCornerShape(20.dp)) - Un raggio di sfocatura di
10.dp, che rende i bordi morbidi e diffusi - Una diffusione di
6.dp, che espande le dimensioni dell'ombra e la rende più grande della casella che la proietta - Un valore alfa di
0.5f, che rende l'ombra semitrasparente
- Una forma rettangolare arrotondata (
- Dopo aver definito l'ombra, viene applicato il modificatore .
background().- La
Boxè riempita di bianco. - Lo sfondo viene ritagliato in base alla stessa forma rettangolare arrotondata dell'ombra.
- La
Risultato
Implementare le ombre interne
Per creare un effetto inverso a dropShadow(), utilizza
Modifier.innerShadow(), che crea l'illusione che un elemento sia
incassato o premuto sulla superficie sottostante.
L'ordine è importante quando si creano ombre interne. Il modificatore innerShadow() viene disegnato sopra i contenuti. Per assicurarti che l'ombra sia visibile, in genere esegui i seguenti passaggi:
- Disegna i contenuti di sfondo.
- Applica il modificatore
innerShadow()per creare l'aspetto concavo.
Se innerShadow() viene posizionato prima dello sfondo, lo sfondo viene disegnato sopra l'ombra, nascondendola completamente.
L'esempio seguente mostra un'applicazione di innerShadow() su 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() su un rettangolo con angoli arrotondati.Animare le ombre in base all'interazione dell'utente
Per fare in modo che le ombre rispondano alle interazioni dell'utente, puoi integrare le proprietà delle ombre con le API di animazione di Compose. Quando un utente preme un pulsante, ad esempio, l'ombra può cambiare per fornire un feedback visivo immediato.
Il seguente codice crea un effetto "premuto" con un'ombra (l'illusione che la superficie venga spinta verso il basso sullo schermo):
@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", // ... ) } } } }
Punti chiave sul codice
- Dichiara gli stati di inizio e fine per i parametri da animare alla pressione con
transition.animateColoretransition.animateFloat. - Utilizza
updateTransitione fornisce iltargetState (targetState = isPressed)scelto per verificare che tutte le animazioni siano sincronizzate. Ogni volta cheisPressedcambia, l'oggetto di transizione gestisce automaticamente l'animazione di tutte le proprietà secondarie dai valori correnti ai nuovi valori target. - Definisce la specifica
buttonPressAnimation, che controlla la tempistica e l'easing della transizione. Specifica untween(abbreviazione di in-between) con una durata di 400 millisecondi e una curvaEaseInOut, il che significa che l'animazione inizia lentamente, accelera al centro e rallenta alla fine. - Definisce una
Boxcon una catena di funzioni di modificatore che applicano tutte le proprietà animate per creare l'elemento visivo, tra cui:- .
clickable(): un modificatore che rende interattiva laBox. .dropShadow(): vengono applicate prima due ombre esterne. Le proprietà di colore e alfa sono collegate ai valori animati (blueDropShadowe così via) e creano l'aspetto iniziale in rilievo..innerShadow(): vengono disegnate due ombre interne sopra lo sfondo. Le relative proprietà sono collegate all'altro set di valori animati (innerShadowColor1e così via) e creano l'aspetto rientrato.
- .
Risultato
Creare ombre sfumate
Le ombre non sono limitate ai colori a tinta unita. L'API Shadow accetta un Brush, che ti consente di creare ombre sfumate.
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 ) }
Punti chiave sul codice
dropShadow()aggiunge un'ombra dietro la casella.brush = Brush.sweepGradient(colors)colora l'ombra con una sfumatura che ruota in un elenco dicolorspredefiniti, creando un effetto simile a un arcobaleno.
Risultato
Puoi utilizzare un pennello come ombra per creare un dropShadow() sfumato con un'animazione "respirante":
Combinare le ombre
Puoi combinare e sovrapporre i modificatori dropShadow() e innerShadow() per creare una varietà di effetti. Le sezioni seguenti mostrano come produrre ombre neumorfiche, neobrutaliste e realistiche con questa tecnica.
Creare ombre neumorfiche
Le ombre neumorfiche sono caratterizzate da un aspetto morbido che emerge organicamente dallo sfondo. Per creare ombre neumorfiche:
- Utilizza un elemento che condivida gli stessi colori dello sfondo.
- Applica due ombre leggere e opposte: un'ombra chiara su un angolo e un'ombra scura sull'angolo opposto.
Il seguente snippet sovrappone due modificatori dropShadow() per creare l'effetto neumorfico:
@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) ) }
Creare ombre neobrutaliste
Lo stile neobrutalista presenta layout a blocchi ad alto contrasto, colori vivaci e bordi spessi. Per creare questo effetto, utilizza un dropShadow() con sfocatura zero e un offset distinto, come mostrato nel seguente snippet:
@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 ) } } } }
Creare ombre realistiche
Le ombre realistiche imitano le ombre del mondo fisico: appaiono illuminate da una sorgente luminosa primaria, con conseguente ombra diretta e un'ombra più diffusa. Puoi impilare più istanze dropShadow() e innerShadow() con proprietà diverse per ricreare effetti ombra realistici, come mostrato nel seguente snippet:
@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 ) } } }
Punti chiave sul codice
- Vengono applicati due modificatori
dropShadow()concatenati con proprietà distinte, seguiti da un modificatorebackground(). - Vengono applicati modificatori
innerShadow()concatenati per creare l'effetto del bordo metallico attorno al bordo del componente.
Risultato
Lo snippet di codice precedente produce quanto segue: