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 un système de conception personnalisé. Cela est possible, mais vous devez tenir compte de certains points selon 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 de la thématisation 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.colors.snackbarAction val Colors.snackbarAction: Color get() = if (isLight) 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
. primarySurface
est un exemple défini par Compose, qui joue le rôle de proxy entre primary
et surface
en fonction de Colors.isLight
.
Une autre approche consiste à définir un thème étendu qui "encapsule" MaterialTheme
et ses valeurs.
Supposons que vous souhaitiez ajouter deux autres couleurs (tertiary
et onTertiary
) tout en conservant les couleurs de Material existantes :
@Immutable data class ExtendedColors( val tertiary: Color, val onTertiary: Color ) val LocalExtendedColors = staticCompositionLocalOf { ExtendedColors( tertiary = Color.Unspecified, onTertiary = Color.Unspecified ) } @Composable fun ExtendedTheme( /* ... */ content: @Composable () -> Unit ) { val extendedColors = ExtendedColors( tertiary = Color(0xFFA8EFF0), onTertiary = Color(0xFF002021) ) CompositionLocalProvider(LocalExtendedColors provides extendedColors) { MaterialTheme( /* colors = ..., typography = ..., shapes = ... */ content = content ) } } // Use with eg. ExtendedTheme.colors.tertiary 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
.
Utilisation des 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 dans vos propres fonctions modulables, en 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.tertiary, contentColor = ExtendedTheme.colors.onTertiary /* 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 = { /* ... */ }) { /* ... */ } } }
Remplacement des 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 le système de couleurs :
@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 }
Utilisation des 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émentation d'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 MaterialContentAlpha
: niveaux d'opacité pour mettre l'accent surText
etIcon
TextSelectionColors
: 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 }
Utilisation 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 = ContentAlpha.disabled) ), 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 voir un exemple, consultez
JetsnackButton
de l'échantillon Jetsnack.
Recommandations personnalisées
- Remarque : Le texte du lien s'affiche lorsque JavaScript est désactivé
- Material Design 2 dans Compose
- Migrer de Material 2 vers Material 3 dans Compose
- Anatomie d'un thème dans Compose