Le ombre migliorano visivamente l'interfaccia utente, indicano l'interattività agli utenti e forniscono un feedback immediato sulle azioni degli utenti. Compose offre diversi modi per incorporare le ombre nella tua app:
Modifier.shadow(): crea un'ombra basata sull'elevazione dietro un composable conforme alle linee guida di Material Design.Modifier.dropShadow(): crea un'ombra personalizzabile che appare dietro un composable, facendolo sembrare in rilievo.Modifier.innerShadow(): crea un'ombra all'interno dei bordi di un componibile, facendolo sembrare premuto sulla superficie dietro.
Modifier.shadow() è adatto per creare 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 con gradiente, ombre neumorfiche e altro ancora.
Crea ombre di base
Modifier.shadow() crea un'ombra di base seguendo le linee guida di Material Design che simula una sorgente di luce dall'alto. La profondità dell'ombra si basa su un valore elevation e l'ombra proiettata viene ritagliata in base alla forma del composable.
@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 le ombre
Utilizza il modificatore dropShadow() per disegnare un'ombra precisa dietro i tuoi contenuti, in modo che l'elemento appaia sollevato.
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 del pacchetto androidx.compose.foundation.shape, nonché le forme Material Expressive.
Per implementare un'ombra di base, aggiungi il modificatore dropShadow() alla catena componibile, specificando 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 all'elementoBoxinterno. L'ombra ha le seguenti caratteristiche:- Una forma rettangolare con angoli arrotondati (
RoundedCornerShape(20.dp)) - Un raggio di sfocatura di
10.dp, che rende i bordi morbidi e diffusi - Un valore di diffusione pari a
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 con angoli arrotondati (
- Dopo aver definito l'ombra, ilIl modificatore
background()è applicato.- La
Boxè riempita di colore bianco. - Lo sfondo viene ritagliato nella stessa forma rettangolare con angoli arrotondati dell'ombra.
- La
Risultato
Implementa le ombre interne
Per creare un effetto inverso a dropShadow(), utilizza Modifier.innerShadow(), che crea l'illusione che un elemento sia incassato o premuto nella superficie sottostante.
L'ordine è importante quando si creano ombre interne. Il modificatore innerShadow()
disegna sopra i contenuti. Per assicurarti che l'ombreggiatura sia visibile, in genere devi eseguire i seguenti passaggi:
- Disegna i contenuti in background.
- Applica il modificatore
innerShadow()per creare l'aspetto concavo.
Se l'elemento 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.Anima le ombre in base all'interazione dell'utente
Per fare in modo che le ombre rispondano alle interazioni degli utenti, puoi integrare le proprietà delle ombre con le API Animation di Compose. Ad esempio, quando un utente preme un pulsante, l'ombra può cambiare per fornire un feedback visivo istantaneo.
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 iniziale e finale dei parametri da animare alla pressione con
transition.animateColoretransition.animateFloat. - Utilizza
updateTransitione gli 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 attuali 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 nel mezzo e rallenta alla fine. - Definisce un
Boxcon una catena di funzioni di modifica che applicano tutte le proprietà animate per creare l'elemento visivo, tra cui:- .
clickable(): un modificatore che rende interattivoBox. .dropShadow(): vengono applicate prima due ombre esterne. Le loro proprietà di colore e alfa sono collegate ai valori animati (blueDropShadowe così via) e creano l'aspetto iniziale in rilievo..innerShadow(): due ombre interne vengono disegnate sopra lo sfondo. Le loro proprietà sono collegate all'altro insieme di valori animati (innerShadowColor1e così via) e creano l'aspetto rientrato.
- .
Risultato
Crea ombre con sfumatura
Le ombre non sono limitate ai colori a tinta unita. L'API Shadow accetta un Brush, che consente di creare ombre con 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 ) }
Punti chiave sul codice
dropShadow()aggiunge un'ombra dietro la casella.brush = Brush.sweepGradient(colors)colora l'ombra con un gradiente che ruota attraverso un elenco dicolorspredefiniti, creando un effetto simile a un arcobaleno.
Risultato
Puoi utilizzare un pennello come ombra per creare un gradiente dropShadow() con un'animazione "pulsante":
Combina 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.
Crea ombre neumorfiche
Le ombre neumorfiche sono caratterizzate da un aspetto morbido che emerge in modo organico dallo sfondo. Per creare ombre neumorfiche:
- Utilizza un elemento che condivide gli stessi colori dello sfondo.
- Applica due ombreggiature tenui e opposte: un'ombra chiara in un angolo e un'ombra scura nell'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) ) }
Crea 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 pari a zero e un offset distinto, come mostrato nello snippet seguente:
@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 ombre realistiche
Le ombre realistiche imitano le ombre del mondo fisico: appaiono illuminate da una fonte di luce principale, con un'ombra diretta e un'ombra più diffusa. Puoi impilare più istanze dropShadow() e innerShadow() con proprietà diverse per ricreare effetti di ombra realistici, come mostrato nello snippet seguente:
@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(). - I modificatori
innerShadow()concatenati vengono applicati per creare l'effetto bordo metallico attorno al bordo del componente.
Risultato
Lo snippet di codice precedente produce quanto segue: