Les ombres améliorent 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, le faisant apparaître comme surélevé.Modifier.innerShadow(): crée une ombre à l'intérieur des bordures d'un composable, ce qui donne l'impression qu'il est enfoncé dans la surface derrière lui.
Modifier.shadow() convient pour créer des ombres de base, tandis que les modificateurs dropShadow() et innerShadow() offrent un contrôle et une précision plus précis sur l'affichage des ombres.
Cette page explique comment implémenter chacun de ces modificateurs, y compris comment animer les ombres lors de l'interaction de l'utilisateur et comment associer les modificateurs innerShadow() et dropShadow() pour créer des ombres en dégradé, des ombres neumorphiques, etc.
Créer des ombres de base
Modifier.shadow() crée une ombre de base conformément aux consignes Material Design qui simule une source de lumière au-dessus de l'élément. La profondeur de l'ombre est basée sur une valeur elevation, et l'ombre projeté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 portées
Utilisez le modificateur dropShadow() pour dessiner une ombre précise derrière votre contenu, ce qui donne l'impression que l'élément est surélevé.
Vous pouvez contrôler les aspects clés suivants via son paramètre Shadow :
radius: définit la douceur et la diffusion du 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 portée de base, ajoutez le modificateur dropShadow() à votre chaîne de composables, en indiquant le rayon, la couleur et l'étendue. 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é auBoxinterne. L'ombre présente les caractéristiques suivantes :- Une forme de rectangle arrondi (
RoundedCornerShape(20.dp)) - Un rayon de flou de
10.dp, ce qui rend les bords doux et diffus - Une propagation de
6.dp, qui augmente la taille de l'ombre et la rend plus grande que la boîte qui la projette - Une valeur alpha de
0.5f, ce qui rend l'ombre semi-transparente
- Une forme de rectangle arrondi (
- Une fois l'ombre définie, le .Le modificateur
background()est appliqué.- L'élément
Boxest rempli d'une couleur blanche. - L'arrière-plan est découpé selon la même forme de rectangle arrondi que l'ombre.
- L'élément
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 enfoncé ou pressé dans la surface sous-jacente.
L'ordre est important lors de la création d'ombres intérieures. Le modificateur innerShadow() s'affiche au-dessus du contenu. Pour vous assurer que l'ombre est visible, vous devez généralement procéder comme suit :
- Dessinez votre contenu en arrière-plan.
- Appliquez le modificateur
innerShadow()pour créer l'apparence concave.
Si l'élément innerShadow() est placé avant l'arrière-plan, l'arrière-plan est dessiné sur 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 à angles arrondisAnimer les ombres lors de l'interaction de l'utilisateur
Pour que vos ombres réagissent aux interactions des utilisateurs, vous pouvez intégrer des propriétés d'ombre aux API d'animation de Compose. Par exemple, lorsqu'un utilisateur appuie sur un bouton, l'ombre peut changer pour fournir un retour visuel instantané.
Le code suivant crée un effet "appuyé" 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 d'un 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 le timing et l'accélération de la transition. Il spécifie untween(abréviation de "in-between") d'une durée de 400 millisecondes et une courbeEaseInOut, ce qui signifie que l'animation démarre lentement, accélère au milieu et ralentit à la fin. - Définit un
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 leBoxinteractif. .dropShadow(): deux ombres portées extérieures sont appliquées en premier. Leurs propriétés de couleur et d'alpha sont associées aux valeurs animées (blueDropShadow, etc.) et créent l'apparence initiale en relief..innerShadow(): deux ombres internes 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 en dégradé
Les ombres ne sont pas limitées aux couleurs unies. L'API Shadow accepte un Brush, qui vous permet de créer des ombres en dégradé.
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 alterne entre une liste decolorsprédéfinis, créant un effet arc-en-ciel.
Résultat
Vous pouvez utiliser un pinceau comme ombre pour créer un dégradé dropShadow() avec une animation "respiration" :
Combiner les ombres
Vous pouvez combiner et superposer les modificateurs dropShadow() et innerShadow() pour créer différents effets. Les sections suivantes vous expliquent comment produire des ombres neumorphiques, néobrutalistes et réalistes avec cette technique.
Créer des ombres neumorphiques
Les ombres neumorphiques se caractérisent par un aspect doux qui émerge naturellement de l'arrière-plan. Pour créer des ombres neumorphiques, procédez comme suit :
- Utilisez un élément qui partage les mêmes couleurs que son arrière-plan.
- Appliquez deux ombres portées légères et opposées : une ombre claire dans un angle et une ombre sombre dans l'angle opposé.
L'extrait de code suivant superpose deux modificateurs dropShadow() pour créer l'effet neumorphique :
@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 se caractérise par des mises en page très contrastées et en blocs, des couleurs vives et des bordures épaisses. Pour créer cet effet, utilisez un dropShadow() sans flou et avec 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 à la fois une ombre directe et une ombre plus diffuse. Vous pouvez empiler plusieurs instances dropShadow() et innerShadow() avec différentes propriétés 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 bordure métallique autour du composant.
Résultat
L'extrait de code précédent produit le résultat suivant :