Bien que Material soit notre système de conception recommandé et que Jetpack Compose comprenne une implémentation de Material, vous n'êtes pas obligé de l'utiliser. Material est entièrement basé sur des API publiques. Il est donc possible de créer votre propre système de conception de la même manière.
Plusieurs approches sont possibles :
- Extension de
MaterialTheme
avec des valeurs de thématisation supplémentaires - Remplacement d'un ou de plusieurs systèmes Material (
Colors
,Typography
ouShapes
) par des implémentations personnalisées, tout en conservant les autres - Implémentation d'un système de conception entièrement personnalisé pour remplacer
MaterialTheme
Vous pouvez également continuer à utiliser les composants Material avec une conception personnalisée du système d'exploitation. C'est possible, mais il y a des points à garder à l'esprit pour l'approche que vous avez adoptée.
Pour en savoir plus sur les constructions de niveau inférieur et les API utilisées par MaterialTheme
et les systèmes de conception personnalisés, consultez le guide Anatomie d'un thème dans Compose.
Extension du thème Material
Compose Material modélise étroitement la thématisation Material afin que les consignes de Material soient simples et sûres à suivre. Cependant, il est possible d'étendre les ensembles de couleurs, de typographie et de formes avec des valeurs supplémentaires.
L'approche la plus simple consiste à ajouter des propriétés d'extension :
// Use with MaterialTheme.colorScheme.snackbarAction val ColorScheme.snackbarAction: Color @Composable get() = if (isSystemInDarkTheme()) Red300 else Red700 // Use with MaterialTheme.typography.textFieldInput val Typography.textFieldInput: TextStyle get() = TextStyle(/* ... */) // Use with MaterialTheme.shapes.card val Shapes.card: Shape get() = RoundedCornerShape(size = 20.dp)
Cela permet d'assurer la cohérence avec les API d'utilisation de MaterialTheme
. Par exemple,
défini par Compose lui-même est
surfaceColorAtElevation
,
qui détermine la couleur de la surface à utiliser en fonction de l'élévation.
Une autre approche consiste à définir
un thème étendu qui "encapsule" MaterialTheme
et
ses valeurs.
Supposons que vous souhaitiez ajouter deux couleurs supplémentaires : caution
et onCaution
, un
utilisée pour les actions semi-dangereuses, tout en gardant
couleurs Material existantes:
@Immutable data class ExtendedColors( val caution: Color, val onCaution: Color ) val LocalExtendedColors = staticCompositionLocalOf { ExtendedColors( caution = Color.Unspecified, onCaution = Color.Unspecified ) } @Composable fun ExtendedTheme( /* ... */ content: @Composable () -> Unit ) { val extendedColors = ExtendedColors( caution = Color(0xFFFFCC02), onCaution = Color(0xFF2C2D30) ) CompositionLocalProvider(LocalExtendedColors provides extendedColors) { MaterialTheme( /* colors = ..., typography = ..., shapes = ... */ content = content ) } } // Use with eg. ExtendedTheme.colors.caution object ExtendedTheme { val colors: ExtendedColors @Composable get() = LocalExtendedColors.current }
Cela est semblable aux API d'utilisation de MaterialTheme
. Plusieurs thèmes sont également pris en charge, car vous pouvez imbriquer des ExtendedTheme
de la même manière que MaterialTheme
.
Utiliser les composants Material
Lorsque vous étendez la thématisation Material, les valeurs MaterialTheme
existantes sont conservées et les composants Material conservent des valeurs par défaut raisonnables.
Si vous souhaitez utiliser des valeurs étendues dans des composants, encapsulez-les des fonctions composables définissant directement les valeurs que vous souhaitez modifier ; et en exposant les autres en tant que paramètres du composable associé:
@Composable fun ExtendedButton( onClick: () -> Unit, modifier: Modifier = Modifier, content: @Composable RowScope.() -> Unit ) { Button( colors = ButtonDefaults.buttonColors( containerColor = ExtendedTheme.colors.caution, contentColor = ExtendedTheme.colors.onCaution /* Other colors use values from MaterialTheme */ ), onClick = onClick, modifier = modifier, content = content ) }
Vous devez ensuite remplacer les utilisations de Button
par ExtendedButton
, le cas échéant.
@Composable fun ExtendedApp() { ExtendedTheme { /*...*/ ExtendedButton(onClick = { /* ... */ }) { /* ... */ } } }
Remplacer les sous-systèmes Material
Au lieu d'étendre la thématisation Material, vous pouvez remplacer un ou plusieurs systèmes (Colors
, Typography
ou Shapes
) par une implémentation personnalisée, tout en conservant les autres.
Supposons que vous souhaitiez remplacer les systèmes de type et de forme tout en conservant la couleur système:
@Immutable data class ReplacementTypography( val body: TextStyle, val title: TextStyle ) @Immutable data class ReplacementShapes( val component: Shape, val surface: Shape ) val LocalReplacementTypography = staticCompositionLocalOf { ReplacementTypography( body = TextStyle.Default, title = TextStyle.Default ) } val LocalReplacementShapes = staticCompositionLocalOf { ReplacementShapes( component = RoundedCornerShape(ZeroCornerSize), surface = RoundedCornerShape(ZeroCornerSize) ) } @Composable fun ReplacementTheme( /* ... */ content: @Composable () -> Unit ) { val replacementTypography = ReplacementTypography( body = TextStyle(fontSize = 16.sp), title = TextStyle(fontSize = 32.sp) ) val replacementShapes = ReplacementShapes( component = RoundedCornerShape(percent = 50), surface = RoundedCornerShape(size = 40.dp) ) CompositionLocalProvider( LocalReplacementTypography provides replacementTypography, LocalReplacementShapes provides replacementShapes ) { MaterialTheme( /* colors = ... */ content = content ) } } // Use with eg. ReplacementTheme.typography.body object ReplacementTheme { val typography: ReplacementTypography @Composable get() = LocalReplacementTypography.current val shapes: ReplacementShapes @Composable get() = LocalReplacementShapes.current }
Utiliser les composants Material
Lorsqu'un ou plusieurs systèmes de MaterialTheme
ont été remplacés, l'utilisation de composants Material tels quels peut entraîner des valeurs indésirables de couleur, de type ou de forme Material.
Si vous souhaitez utiliser des valeurs de remplacement dans des composants, encapsulez-les dans vos propres fonctions modulables, en définissant directement les valeurs du système concerné et en exposant les autres en tant que paramètres du composable associé.
@Composable fun ReplacementButton( onClick: () -> Unit, modifier: Modifier = Modifier, content: @Composable RowScope.() -> Unit ) { Button( shape = ReplacementTheme.shapes.component, onClick = onClick, modifier = modifier, content = { ProvideTextStyle( value = ReplacementTheme.typography.body ) { content() } } ) }
Vous devez ensuite remplacer les utilisations de Button
par ReplacementButton
, le cas échéant.
@Composable fun ReplacementApp() { ReplacementTheme { /*...*/ ReplacementButton(onClick = { /* ... */ }) { /* ... */ } } }
Implémenter un système de conception entièrement personnalisé
Vous pouvez remplacer la thématisation Material par un système de conception entièrement personnalisé.
Notez que MaterialTheme
fournit les systèmes suivants :
Colors
,Typography
etShapes
: systèmes de thématisation MaterialTextSelectionColors
: couleurs utilisées pour la sélection de texte parText
etTextField
Ripple
etRippleTheme
: implémentation de Material deIndication
Si vous souhaitez continuer à utiliser les composants Material, vous devrez remplacer certains de ces systèmes dans vos thèmes personnalisés ou gérer les systèmes dans vos composants pour éviter tout comportement indésirable.
Toutefois, les systèmes de conception ne sont pas limités aux concepts sur lesquels se base Material. Vous pouvez modifier des systèmes existants et en introduire de nouveaux, avec de nouvelles classes et de nouveaux types, afin de rendre d'autres concepts compatibles avec les thèmes.
Dans le code suivant, nous modélisons un système de couleurs personnalisé comprenant des dégradés (List<Color>
), intégrons un système de types, ajoutons un nouveau système d'élévation et excluons les autres systèmes fournis par MaterialTheme
:
@Immutable data class CustomColors( val content: Color, val component: Color, val background: List<Color> ) @Immutable data class CustomTypography( val body: TextStyle, val title: TextStyle ) @Immutable data class CustomElevation( val default: Dp, val pressed: Dp ) val LocalCustomColors = staticCompositionLocalOf { CustomColors( content = Color.Unspecified, component = Color.Unspecified, background = emptyList() ) } val LocalCustomTypography = staticCompositionLocalOf { CustomTypography( body = TextStyle.Default, title = TextStyle.Default ) } val LocalCustomElevation = staticCompositionLocalOf { CustomElevation( default = Dp.Unspecified, pressed = Dp.Unspecified ) } @Composable fun CustomTheme( /* ... */ content: @Composable () -> Unit ) { val customColors = CustomColors( content = Color(0xFFDD0D3C), component = Color(0xFFC20029), background = listOf(Color.White, Color(0xFFF8BBD0)) ) val customTypography = CustomTypography( body = TextStyle(fontSize = 16.sp), title = TextStyle(fontSize = 32.sp) ) val customElevation = CustomElevation( default = 4.dp, pressed = 8.dp ) CompositionLocalProvider( LocalCustomColors provides customColors, LocalCustomTypography provides customTypography, LocalCustomElevation provides customElevation, content = content ) } // Use with eg. CustomTheme.elevation.small object CustomTheme { val colors: CustomColors @Composable get() = LocalCustomColors.current val typography: CustomTypography @Composable get() = LocalCustomTypography.current val elevation: CustomElevation @Composable get() = LocalCustomElevation.current }
Utiliser des composants Material
En l'absence de MaterialTheme
, l'utilisation de composants Material tels quels entraîne des valeurs indésirables de couleur, de type et de forme Material, ainsi qu'un comportement d'indication.
Si vous souhaitez utiliser des valeurs personnalisées dans des composants, encapsulez-les dans vos propres fonctions modulables, en définissant directement les valeurs pour le système concerné et en exposant les autres en tant que paramètres du composable associé.
Nous vous recommandons d'accéder aux valeurs que vous avez définies à partir de votre thème personnalisé.
Si votre thème ne fournit pas Color
, TextStyle
, Shape
ni d'autres systèmes, vous pouvez également les coder en dur.
@Composable fun CustomButton( onClick: () -> Unit, modifier: Modifier = Modifier, content: @Composable RowScope.() -> Unit ) { Button( colors = ButtonDefaults.buttonColors( containerColor = CustomTheme.colors.component, contentColor = CustomTheme.colors.content, disabledContainerColor = CustomTheme.colors.content .copy(alpha = 0.12f) .compositeOver(CustomTheme.colors.component), disabledContentColor = CustomTheme.colors.content .copy(alpha = 0.38f) ), shape = ButtonShape, elevation = ButtonDefaults.elevatedButtonElevation( defaultElevation = CustomTheme.elevation.default, pressedElevation = CustomTheme.elevation.pressed /* disabledElevation = 0.dp */ ), onClick = onClick, modifier = modifier, content = { ProvideTextStyle( value = CustomTheme.typography.body ) { content() } } ) } val ButtonShape = RoundedCornerShape(percent = 50)
Si vous avez introduit de nouveaux types de classes, tels que List<Color>
pour représenter les dégradés, il peut être préférable d'implémenter les composants en repartant de zéro plutôt que de les encapsuler. Pour obtenir un exemple, consultez JetsnackButton
dans l'exemple Jetsnack.
Recommandations personnalisées
- Remarque : Le texte du lien s'affiche lorsque JavaScript est désactivé
- Material Design 3 dans Compose
- Migrer de Material 2 vers Material 3 dans Compose
- Anatomie d'un thème dans Compose