Les ombres surélèvent visuellement votre UI, indiquent l'interactivité aux utilisateurs et fournissent un retour immédiat sur leurs actions. Compose propose plusieurs façons d'intégrer des ombres dans votre application :
Modifier.shadow(): crée une ombre basée sur l'élévation derrière un composable conforme aux consignes Material Design.Modifier.dropShadow(): crée une ombre personnalisable qui apparaît derrière un composable, ce qui le fait apparaître comme surélevé.Modifier.innerShadow(): crée une ombre à l'intérieur des bordures d'un composable, ce qui le fait apparaître comme enfoncé dans la surface derrière lui.
Modifier.shadow() est adapté à la création d'ombres de base, tandis que les modificateurs dropShadow() et innerShadow() offrent un contrôle plus précis sur le rendu des ombres.
Cette page explique comment implémenter chacun de ces modificateurs, y compris comment
animer les ombres lors d'une interaction utilisateur et comment enchaîner les modificateurs
innerShadow() et dropShadow() pour
créer des ombres dégradées, des
ombres néomorphiques, etc.
Créer des ombres de base
Modifier.shadow() crée une ombre de base conforme aux consignes Material Design
qui simule une source de lumière provenant du dessus. La profondeur de l'ombre est basée sur une valeur elevation, et l'ombre portée est découpée selon la forme du 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().Implémenter des ombres projetées
Utilisez le dropShadow() modificateur pour dessiner une ombre précise derrière votre
contenu, ce qui fait apparaître l'élément comme surélevé.
Vous pouvez contrôler les aspects clés suivants via son paramètre Shadow :
radius: définit la douceur et la diffusion de votre flou.color: définit la couleur de la teinte.offset: positionne la géométrie de l'ombre le long des axes x et y.spread: contrôle l'expansion ou la contraction de la géométrie de l'ombre.
En outre, le paramètre shape définit la forme globale de l'ombre. Il peut
utiliser n'importe quelle géométrie du package androidx.compose.foundation.shape, ainsi
que les formes Material Expressive.
Pour implémenter une ombre projetée de base, ajoutez le modificateur dropShadow() à votre chaîne composable, en fournissant le rayon, la couleur et la diffusion. Notez que l'arrière-plan purpleColor qui apparaît au-dessus de l'ombre est dessiné après le modificateur 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 ) } } }
Points clés concernant le code
- Le modificateur
dropShadow()est appliqué à laBoxintérieure. L'ombre présente les caractéristiques suivantes :- Forme de rectangle à coins arrondis (
RoundedCornerShape(20.dp)) - Rayon de flou de
10.dp, ce qui rend les bords doux et diffus - Diffusion de
6.dp, ce qui augmente la taille de l'ombre et la rend plus grande que la zone qui la projette - Alpha de
0.5f, ce qui rend l'ombre semi-transparente
- Forme de rectangle à coins arrondis (
- Une fois l'ombre définie, le .modificateur
background()est appliqué.- La
Boxest remplie d'une couleur blanche. - L'arrière-plan est découpé selon la même forme de rectangle à coins arrondis que l'ombre.
- La
Résultat
Implémenter des ombres intérieures
Pour créer un effet inverse à dropShadow(), utilisez
Modifier.innerShadow(), qui donne l'illusion qu'un élément est
en retrait ou enfoncé dans la surface sous-jacente.
L'ordre est important lors de la création d'ombres intérieures. Le modificateur innerShadow() dessine au-dessus du contenu. Pour vous assurer que l'ombre est visible, vous devez généralement procéder comme suit :
- Dessinez le contenu de votre arrière-plan.
- Appliquez le modificateur
innerShadow()pour créer l'apparence concave.
Si innerShadow() est placé avant l'arrière-plan, celui-ci est dessiné au-dessus de l'ombre, ce qui la masque complètement.
L'exemple suivant montre une application de innerShadow() sur 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() sur un rectangle à coins arrondis.Animer les ombres lors d'une interaction utilisateur
Pour que vos ombres répondent aux interactions utilisateur, vous pouvez intégrer des propriétés d'ombre aux API d'animation de Compose. Lorsqu'un utilisateur appuie sur un bouton, par exemple, l'ombre peut changer pour fournir un retour visuel instantané.
Le code suivant crée un effet "enfoncé" avec une ombre (l'illusion que la surface est enfoncée dans l'écran) :
@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", // ... ) } } } }
Points clés concernant le code
- Déclare les états de début et de fin des paramètres à animer lors de l'appui avec
transition.animateColorettransition.animateFloat. - Utilise
updateTransitionet lui fournit letargetState (targetState = isPressed)choisi pour vérifier que toutes les animations sont synchronisées. Chaque fois queisPressedchange, l'objet de transition gère automatiquement l'animation de toutes les propriétés enfants, de leurs valeurs actuelles aux nouvelles valeurs cibles. - Définit la spécification
buttonPressAnimation, qui contrôle la synchronisation et le lissage de vitesse de la transition. Elle spécifie untween(abréviation de "in-between") d'une durée de 400 millisecondes et une courbeEaseInOut, ce qui signifie que l'animation commence lentement, s'accélère au milieu et ralentit à la fin. - Définit une
Boxavec une chaîne de fonctions de modificateur qui appliquent toutes les propriétés animées pour créer l'élément visuel, y compris les éléments suivants :- .
clickable(): modificateur qui rend laBoxinteractive. .dropShadow(): deux ombres projetées externes sont appliquées en premier. Leurs propriétés de couleur et alpha sont liées aux valeurs animées (blueDropShadow, etc.) et créent l'apparence surélevée initiale..innerShadow(): deux ombres intérieures sont dessinées au-dessus de l'arrière-plan. Leurs propriétés sont liées à l'autre ensemble de valeurs animées (innerShadowColor1, etc.) et créent l'apparence en retrait.
- .
Résultat
Créer des ombres dégradées
Les ombres ne se limitent pas aux couleurs unies. L'API Shadow accepte un Brush, ce qui vous permet de créer des ombres dégradées.
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 ) }
Points clés concernant le code
dropShadow()ajoute une ombre derrière la zone.brush = Brush.sweepGradient(colors)colore l'ombre avec un dégradé qui pivote dans une liste decolorsprédéfinies, créant ainsi un effet arc-en-ciel.
Résultat
Vous pouvez utiliser un pinceau comme ombre pour créer un dropShadow() dégradé avec une animation "respiratoire" :
Combiner des ombres
Vous pouvez combiner et superposer les modificateurs dropShadow() et innerShadow() pour créer différents effets. Les sections suivantes vous montrent comment produire des ombres néomorphiques, néobrutalistes et réalistes avec cette technique.
Créer des ombres néomorphiques
Les ombres néomorphiques se caractérisent par une apparence douce qui émerge de manière organique de l'arrière-plan. Pour créer des ombres néomorphiques, procédez comme suit :
- Utilisez un élément qui partage les mêmes couleurs que son arrière-plan.
- Appliquez deux ombres projetées opposées et légères : une ombre claire dans un coin et une ombre foncée dans le coin opposé.
L'extrait suivant superpose deux modificateurs dropShadow() pour créer l'effet néomorphique :
@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) ) }
Créer des ombres néobrutalistes
Le style néobrutaliste présente des mises en page très contrastées et volumineuses, des couleurs vives et des bordures épaisses. Pour créer cet effet, utilisez un dropShadow() avec un flou nul et un décalage distinct, comme illustré dans l'extrait suivant :
@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 ) } } } }
Créer des ombres réalistes
Les ombres réalistes imitent les ombres du monde physique : elles semblent éclairées par une source de lumière principale, ce qui donne lieu à une ombre directe et à une ombre plus diffuse. Vous pouvez empiler plusieurs instances dropShadow() et innerShadow() avec des propriétés différentes pour recréer des effets d'ombre réalistes, comme illustré dans l'extrait suivant :
@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 ) } } }
Points clés concernant le code
- Deux modificateurs
dropShadow()enchaînés avec des propriétés distinctes sont appliqués, suivis d'un modificateurbackground(). - Les modificateurs
innerShadow()enchaînés sont appliqués pour créer l'effet de bord métallique autour du bord du composant.
Résultat
L'extrait de code précédent produit le résultat suivant :