1. Introduction
Dans cet atelier de programmation, vous allez apprendre à utiliser les API de thématisation Jetpack Compose pour personnaliser le style de votre application. Nous verrons comment personnaliser les couleurs, les formes et la typographie pour qu'elles soient utilisées de manière cohérente dans l'ensemble de l'application, avec la prise en charge de plusieurs thèmes tels que le thème clair et le thème sombre.
Points abordés
Cet atelier de programmation traite des points suivants :
- Présentation de Material Design et de sa personnalisation pour votre marque
- Comment Compose met en œuvre le système Material Design
- Comment définir et utiliser les couleurs, la typographie et les formes dans votre application
- Comment appliquer un style aux composants
- Comment gérer les thèmes clair et sombre
Objectifs de l'atelier
Dans cet atelier de programmation, nous allons personnaliser le style d'une application de lecture d'actualités. Nous commencerons avec une application sans style, puis appliquerons ce que nous allons apprendre pour personnaliser l'application et gérer les thèmes sombres.
Avant : application sans style | Après : application stylisée | Après : thème sombre |
Conditions préalables
- Connaître la syntaxe du langage Kotlin, y compris les lambdas
- Connaître les bases de Compose
- Avoir des connaissances de base sur les mises en page Compose (par exemple
Row
,Column
etModifier
)
2. Configuration
Dans cette étape, vous téléchargerez le code que vous utiliserez pendant l'atelier. Il comprend une application de lecture d'actualités simple que nous allons styliser.
Ce dont vous avez besoin
Télécharger le code
Si git est installé, vous pouvez simplement exécuter la commande ci-dessous. Pour vérifier si git est installé, saisissez git --version
dans le terminal ou la ligne de commande, et vérifiez qu'il s'exécute correctement.
git clone https://github.com/googlecodelabs/android-compose-codelabs.git cd android-compose-codelabs/ThemingCodelabM2
Si vous n'avez pas git, cliquez sur le bouton ci-dessous pour télécharger l'ensemble du code de cet atelier de programmation :
Ouvrez le projet dans Android Studio, sélectionnez Fichier > Importer le projet, puis accédez au répertoire ThemingCodelabM2
.
Le projet contient trois packages principaux :
com.codelab.theming.data
Ce package contient des classes de modèle et des exemples de données. Vous ne devriez pas avoir besoin de modifier ce package pendant cet atelier de programmation.com.codelab.theming.ui.start
Ce package est le point de départ de cet atelier. Vous devez apporter toutes les modifications demandées dans cet atelier de programmation dans ce package.com.codelab.theming.ui.finish
Ce package correspond à l'état final de l'atelier de programmation, pour information.
Créer et exécuter l'application
L'application possède deux configurations d'exécution qui reflètent les états initial et final de l'atelier de programmation. Pour déployer le code sur votre appareil ou votre émulateur, sélectionnez une des deux configurations et appuyez sur le bouton d'exécution.
L'application comprend également des aperçus de mise en page Compose. Lorsque vous accédez à Home.kt
dans le package start
ou finish
et que vous ouvrez l'affichage Projet, plusieurs aperçus permettent d'effectuer des itérations rapides sur le code de l'interface utilisateur :
3. Thématisation Material
Jetpack Compose propose la mise en œuvre de Material Design, un système de conception complet permettant de créer des interfaces numériques. Les composants Material Design (boutons, cartes, boutons bascules, etc.) sont basés sur la thématisation Material, un moyen systématique de personnaliser Material Design pour mieux refléter la marque de votre produit. Un thème Material inclut les attributs couleur, typographie et forme. Si vous personnalisez ces attributs, ils seront automatiquement répercutés dans les composants que vous utilisez pour créer votre application.
Il est utile de comprendre la thématisation Material pour savoir comment thématiser vos applications Jetpack Compose. Voici une brève description des concepts. Si vous connaissez déjà bien la thématisation Material, vous pouvez passer à l'étape suivante.
Couleur
Material Design définit un certain nombre de couleurs nommées sémantiquement que vous pouvez utiliser dans votre application :
La couleur Primary correspond à la couleur principale de votre marque, tandis que la couleur Secondary sert à fournir des accents. Vous pouvez indiquer des variantes plus sombres/plus claires pour les zones contrastées. Les couleurs d'arrière-plan et de surface sont utilisées pour les conteneurs contenant des composants qui résident en théorie sur une "surface" de votre application. Material définit également les couleurs "on", c'est-à-dire les couleurs à utiliser pour le contenu situé au-dessus de l'une des couleurs nommées. Par exemple, le texte d'un conteneur coloré en "Surface" doit être coloré en "OnSurface". Les composants Material sont configurés de manière à utiliser ces couleurs de thème. Par exemple, la couleur du bouton d'action flottant est définie par défaut sur secondary
, la couleur du paramètre Fiches sur surface
, etc.
Il est possible de fournir d'autres palettes de couleurs, comme un thème clair et un thème sombre, en définissant des couleurs nommées :
Nous vous conseillons également de définir une petite palette de couleurs et de l'utiliser de façon cohérente dans l'ensemble de l'application. L'outil de sélection de couleur de Material peut vous aider à choisir les couleurs et à créer une palette de couleurs. Il s'assure même de l'accessibilité des combinaisons.
Typographie
De même, Material Design définit un certain nombre de styles de type nommés sémantiquement :
Vous ne pouvez pas modifier les styles de type selon le thème, mais l'utilisation d'une échelle de type favorise la cohérence dans votre application. Le fait de fournir vos propres polices et d'autres personnalisations de type sera reflété dans les composants Material que vous utilisez dans votre application. Par exemple, les barres d'application utilisent le style h6
par défaut, les boutons utilisent button
. Le générateur d'échelles de type Material peut vous aider à créer votre échelle de type.
Forme
Material Design prend en charge l'utilisation systématique de formes pour refléter votre marque. Il définit trois catégories : composants petits, moyens et grands. Chacune d'elles peut définir une forme à utiliser, en personnalisant le style d'angle (coupé ou arrondi) et la taille.
La personnalisation de votre thème pour les formes sera reflétée sur de nombreux composants. Par exemple, les boutons et les champs de texte utilisent par défaut le thème pour les formes de petite taille, les fiches et les boîtes de dialogue le thème pour les formes de taille moyenne et les feuilles de calcul le thème pour les formes de grande taille. Pour consulter la liste complète des composants avec les thèmes de forme qui leur sont associés, cliquez ici. L'outil de personnalisation des formes Material peut vous aider à générer un thème pour les formes.
Référence
Material Design est défini par défaut sur un thème de référence, à savoir le jeu de couleurs violettes, l'échelle de type Roboto et les formes légèrement arrondies comme sur les images ci-dessus. Si vous ne spécifiez ou ne personnalisez pas votre thème, les composants utilisent le thème de référence.
4. Définir votre thème
MaterialTheme
L'élément principal de la mise en œuvre de la thématisation dans Jetpack Compose est le composable MaterialTheme
. Le fait de placer ce composable dans votre hiérarchie Compose vous permet de définir la personnalisation de la couleur, du type et de la forme de tous les composants qu'il contient. Voici comment ce composable est défini dans la bibliothèque :
@Composable
fun MaterialTheme(
colors: Colors,
typography: Typography,
shapes: Shapes,
content: @Composable () -> Unit
) { ...
Vous pouvez ensuite récupérer les paramètres transmis dans ce composable à l'aide de MaterialTheme
object
, qui expose les propriétés de colors
, typography
et shapes
. Nous aborderons ces points plus en détail plus bas.
Ouvrez Home.kt
et localisez la fonction composable Home
, qui est le point d'entrée principal de l'application. Notez que même si nous déclarons un MaterialTheme
, nous ne spécifions aucun paramètre. Nous utilisons donc par défaut le "style de référence" :
@Composable
fun Home() {
...
MaterialTheme {
Scaffold(...
Créons des paramètres de couleur, de type et de forme pour appliquer un thème pour notre application.
Créer un thème
Pour centraliser votre style, nous vous recommandons de créer votre propre composable qui encapsule et configure un MaterialTheme
. Vous définissez ainsi les personnalisations de votre thème à un seul et même endroit et pouvez les réutiliser facilement sur de nombreux écrans ou dans des @Preview
. Vous pouvez créer plusieurs composables de thème si besoin, par exemple si vous souhaitez utiliser différents styles pour différentes sections de votre application.
Dans le package com.codelab.theming.ui.start.theme
, créez un fichier appelé Theme.kt
. Ajoutez une fonction composable appelée JetnewsTheme
qui accepte d'autres composables comme contenu et encapsule un MaterialTheme
:
@Composable
fun JetnewsTheme(content: @Composable () -> Unit) {
MaterialTheme(content = content)
}
Revenez maintenant à Home.kt
et remplacez MaterialTheme
par notre JetnewsTheme
(et importez-le) :
- MaterialTheme {
+ JetnewsTheme {
...
Vous ne remarquerez aucune modification immédiate dans la @Preview
de cet écran. Mettez à jour PostItemPreview
et FeaturedPostPreview
pour encapsuler leur contenu avec notre nouveau composable JetnewsTheme
, afin que les aperçus utilisent notre nouveau thème :
@Preview("Featured Post")
@Composable
private fun FeaturedPostPreview() {
val post = remember { PostRepo.getFeaturedPost() }
+ JetnewsTheme {
FeaturedPost(post = post)
+ }
}
Couleurs
Voici la palette de couleurs que nous souhaitons intégrer dans notre application (pour l'instant, nous allons utiliser une palette de couleurs claires, nous reviendrons au thème sombre bientôt) :
Les couleurs de Compose sont définies à l'aide de la classe Color
. Plusieurs constructeurs vous permettent de spécifier la couleur en tant que ULong
ou par canal de couleur distinct.
Créez un fichier Color.kt
dans votre package theme
. Ajoutez les couleurs suivantes en tant que propriétés publiques de premier niveau dans ce fichier :
val Red700 = Color(0xffdd0d3c)
val Red800 = Color(0xffd00036)
val Red900 = Color(0xffc20029)
Maintenant que nous avons défini les couleurs de nos applications, rassemblons-les dans un objet Colors
, requis par MaterialTheme
, en attribuant des couleurs spécifiques aux couleurs nommées de Material. Revenez à Theme.kt
et ajoutez les éléments suivants :
private val LightColors = lightColors(
primary = Red700,
primaryVariant = Red900,
onPrimary = Color.White,
secondary = Red700,
secondaryVariant = Red900,
onSecondary = Color.White,
error = Red800
)
Ici, nous utilisons la fonction lightColors
pour créer nos Colors
. Cela fournit des valeurs par défaut logiques, et il n'est donc pas nécessaire de spécifier toutes les couleurs qui composent une palette de couleurs Material. Par exemple, notez que nous n'avons pas spécifié de couleur de background
, ni la plupart des couleurs "on". Nous utiliserons les valeurs par défaut.
Utilisons maintenant ces couleurs dans l'application. Mettez à jour votre composable JetnewsTheme
pour utiliser nos Colors
:
@Composable
fun JetnewsTheme(content: @Composable () -> Unit) {
MaterialTheme(
+ colors = LightColors,
content = content
)
}
Ouvrez Home.kt
et actualisez l'aperçu. Notez le nouveau jeu de couleurs reflété dans les composants tels que la TopAppBar
.
Typographie
Voici l'échelle de type que nous souhaitons intégrer dans notre application :
Dans Compose, nous pouvons définir des objets TextStyle
, afin de définir les informations nécessaires pour appliquer un style à certains textes. Exemple de ses attributs :
data class TextStyle(
val color: Color = Color.Unset,
val fontSize: TextUnit = TextUnit.Inherit,
val fontWeight: FontWeight? = null,
val fontStyle: FontStyle? = null,
val fontFamily: FontFamily? = null,
val letterSpacing: TextUnit = TextUnit.Inherit,
val background: Color = Color.Unset,
val textAlign: TextAlign? = null,
val textDirection: TextDirection? = null,
val lineHeight: TextUnit = TextUnit.Inherit,
...
)
L'échelle de type souhaitée utilise Montserrat pour les titres et Domine pour le corps du texte. Les fichiers de police pertinents sont déjà ajoutés au dossier res/fonts
de votre projet.
Créez un fichier Typography.kt
dans le package theme
. Commençons par définir les FontFamily
(qui combinent les différentes épaisseurs de chaque Font
) :
private val Montserrat = FontFamily(
Font(R.font.montserrat_regular),
Font(R.font.montserrat_medium, FontWeight.W500),
Font(R.font.montserrat_semibold, FontWeight.W600)
)
private val Domine = FontFamily(
Font(R.font.domine_regular),
Font(R.font.domine_bold, FontWeight.Bold)
)
Créez maintenant un objet Typography
accepté par un MaterialTheme
, en spécifiant des TextStyle
pour chaque style sémantique de l'échelle :
val JetnewsTypography = Typography(
h4 = TextStyle(
fontFamily = Montserrat,
fontWeight = FontWeight.W600,
fontSize = 30.sp
),
h5 = TextStyle(
fontFamily = Montserrat,
fontWeight = FontWeight.W600,
fontSize = 24.sp
),
h6 = TextStyle(
fontFamily = Montserrat,
fontWeight = FontWeight.W600,
fontSize = 20.sp
),
subtitle1 = TextStyle(
fontFamily = Montserrat,
fontWeight = FontWeight.W600,
fontSize = 16.sp
),
subtitle2 = TextStyle(
fontFamily = Montserrat,
fontWeight = FontWeight.W500,
fontSize = 14.sp
),
body1 = TextStyle(
fontFamily = Domine,
fontWeight = FontWeight.Normal,
fontSize = 16.sp
),
body2 = TextStyle(
fontFamily = Montserrat,
fontSize = 14.sp
),
button = TextStyle(
fontFamily = Montserrat,
fontWeight = FontWeight.W500,
fontSize = 14.sp
),
caption = TextStyle(
fontFamily = Montserrat,
fontWeight = FontWeight.Normal,
fontSize = 12.sp
),
overline = TextStyle(
fontFamily = Montserrat,
fontWeight = FontWeight.W500,
fontSize = 12.sp
)
)
Ouvrez Theme.kt
et mettez à jour le composable JetnewsTheme
pour utiliser notre nouvelle Typography
:
@Composable
fun JetnewsTheme(content: @Composable () -> Unit) {
MaterialTheme(
colors = LightColors,
+ typography = JetnewsTypography,
content = content
)
}
Ouvrez Home.kt
et actualisez l'aperçu pour afficher la nouvelle typographie.
Formes
Nous souhaitons utiliser des formes pour caractériser notre marque dans l'application. Nous voulons appliquer une forme aux angles coupés à plusieurs éléments :
Compose propose les classes RoundedCornerShape
et CutCornerShape
, qui vous permettent de définir votre thème pour les formes.
Créez un fichier Shape.kt
dans le package theme
et ajoutez les éléments suivants :
val JetnewsShapes = Shapes(
small = CutCornerShape(topStart = 8.dp),
medium = CutCornerShape(topStart = 24.dp),
large = RoundedCornerShape(8.dp)
)
Ouvrez Theme.kt
et mettez à jour le composable JetnewsTheme
pour pouvoir utiliser ces Shapes
:
@Composable
fun JetnewsTheme(content: @Composable () -> Unit) {
MaterialTheme(
colors = LightColors,
typography = JetnewsTypography,
+ shapes = JetnewsShapes,
content = content
)
}
Ouvrez Home.kt
et actualisez l'aperçu pour voir à quel point la Card
, qui affiche l'article mis en avant, reflète le nouveau thème de forme appliqué.
Thème sombre
La compatibilité de votre application avec un thème sombre permet non seulement une intégration améliorée sur les appareils des utilisateurs (qui disposent d'un thème sombre global à partir d'Android 10), mais également une réduction de la consommation d'énergie et une réponse aux besoins en accessibilité. Material propose des conseils de conception pour créer un thème sombre. Voici une autre palette de couleurs que nous aimerions ajouter pour le thème sombre :
Ouvrez Color.kt
et ajoutez les couleurs suivantes :
val Red200 = Color(0xfff297a2)
val Red300 = Color(0xffea6d7e)
Ouvrez Theme.kt
et ajoutez :
private val DarkColors = darkColors(
primary = Red300,
primaryVariant = Red700,
onPrimary = Color.Black,
secondary = Red300,
onSecondary = Color.Black,
error = Red200
)
À présent, mettez à jour JetnewsTheme
:
@Composable
fun JetnewsTheme(
+ darkTheme: Boolean = isSystemInDarkTheme(),
content: @Composable () -> Unit
) {
MaterialTheme(
+ colors = if (darkTheme) DarkColors else LightColors,
typography = JetnewsTypography,
shapes = JetnewsShapes,
content = content
)
}
Nous avons ajouté ici un nouveau paramètre pour indiquer s'il faut utiliser un thème sombre. Nous avons défini par défaut qu'il doit interroger le paramètre global de l'appareil. Cela nous offre une bonne valeur par défaut, et il est toujours facile de la remplacer si vous voulez qu'un écran en particulier soit toujours sombre, ou ne le soit jamais, ou si vous souhaitez créer une @Preview
à thème sombre.
Ouvrez Home.kt
et créez un aperçu pour le composable FeaturedPost
qui s'affiche en mode sombre :
@Preview("Featured Post • Dark")
@Composable
private fun FeaturedPostDarkPreview() {
val post = remember { PostRepo.getFeaturedPost() }
JetnewsTheme(darkTheme = true) {
FeaturedPost(post = post)
}
}
Actualisez le volet d'aperçu pour afficher l'aperçu avec le thème sombre.
5. Utiliser des couleurs
À la dernière étape, nous avons vu comment créer votre propre thème afin de définir les couleurs, le type et les formes de votre application. Tous les composants Material utilisent ces personnalisations prêtes à l'emploi. Par exemple, le composable FloatingActionButton
utilise par défaut la couleur secondary
du thème, mais vous pouvez en définir une autre en spécifiant une valeur différente pour ce paramètre :
@Composable
fun FloatingActionButton(
backgroundColor: Color = MaterialTheme.colors.secondary,
...
) {
Vous ne souhaitez pas toujours utiliser les paramètres par défaut. Cette section vous explique comment utiliser les couleurs dans votre application.
Couleurs brutes
Comme nous l'avons vu précédemment, Compose propose une classe Color
. Vous pouvez les créer localement, les conserver dans un object
, etc. :
Surface(color = Color.LightGray) {
Text(
text = "Hard coded colors don't respond to theme changes :(",
textColor = Color(0xffff00ff)
)
}
Color
utilise un certain nombre de méthodes utiles, telles que copy
, pour créer une couleur avec différentes valeurs alpha, rouges, vertes et bleues.
Couleurs du thème
Une approche plus flexible consiste à récupérer les couleurs de votre thème :
Surface(color = MaterialTheme.colors.primary)
Ici, nous utilisons le MaterialTheme
object
dont la propriété colors
renvoie l'ensemble Colors
dans le composable MaterialTheme
. Cela signifie que nous pouvons prendre en charge différents styles en proposant différents ensembles de couleurs pour notre thème. Nous n'avons pas besoin de modifier le code de l'application. Par exemple, notre AppBar
utilise la couleur primary
et l'arrière-plan de l'écran est surface
; la modification des couleurs de thème est reflétée dans les composables suivants :
Étant donné que chaque couleur de notre thème est une instance Color
, nous pouvons également facilement déduire les couleurs à l'aide de la méthode copy
:
val derivedColor = MaterialTheme.colors.onSurface.copy(alpha = 0.1f)
Ici, nous créons une copie de la couleur onSurface
, mais avec une opacité de 10 %. Cette approche garantit que les couleurs fonctionnent sous différents thèmes, ce qui peut ne pas être le cas lorsque des couleurs statiques sont codées en dur.
Couleurs de la surface et du contenu
De nombreux composants acceptent une paire de couleurs et des "couleurs de contenu" :
Surface(
color: Color = MaterialTheme.colors.surface,
contentColor: Color = contentColorFor(color),
...
TopAppBar(
backgroundColor: Color = MaterialTheme.colors.primarySurface,
contentColor: Color = contentColorFor(backgroundColor),
...
Cela vous permet non seulement de définir la couleur d'un composable, mais aussi d'utiliser une couleur par défaut pour le "contenu", c'est-à-dire les composables qu'il contient. De nombreux composables utilisent cette couleur de contenu par défaut, par exemple, une couleur Text
ou une teinte Icon
. La méthode contentColorFor
récupère la couleur "on" appropriée pour toutes les couleurs du thème. Par exemple, si vous définissez un arrière-plan primary
, la valeur onPrimary
est définie comme couleur du contenu. Si vous choisissez une couleur d'arrière-plan différente de celle du thème, vous devez fournir une couleur de contenu logique.
Surface(color = MaterialTheme.colors.primary) {
Text(...) // default text color is 'onPrimary'
}
Surface(color = MaterialTheme.colors.error) {
Icon(...) // default tint is 'onError'
}
Vous pouvez utiliser le LocalContentColor
CompositionLocal
pour récupérer la couleur qui contraste avec l'arrière-plan actuel :
BottomNavigationItem(
unselectedContentColor = LocalContentColor.current ...
Lorsque vous définissez la couleur des éléments, utilisez de préférence une Surface
, car elle définit une valeur CompositionLocal
de couleur de contenu appropriée. Méfiez-vous des appels Modifier.background
directs qui ne définissent pas de couleur de contenu appropriée.
-Row(Modifier.background(MaterialTheme.colors.primary)) {
+Surface(color = MaterialTheme.colors.primary) {
+ Row(
...
Actuellement, nos composants Header
ont toujours un arrière-plan Color.LightGray
. Le thème clair semble correct, mais les couleurs sont très contrastées par rapport à l'arrière-plan du thème sombre. Ils ne spécifient pas la couleur du texte en question. Par conséquent, héritez la couleur de contenu actuelle qui peut ne pas contraster avec l'arrière-plan :
Résolvons à présent ce problème. Dans le composable Header
dans Home.kt
, supprimez le modificateur background
spécifiant la couleur codée en dur. À la place, encapsulez le Text
dans une Surface
avec une couleur dérivée du thème et spécifiez que le contenu doit être coloré en primary
:
+ Surface(
+ color = MaterialTheme.colors.onSurface.copy(alpha = 0.1f),
+ contentColor = MaterialTheme.colors.primary,
+ modifier = modifier
+ ) {
Text(
text = text,
modifier = Modifier
.fillMaxWidth()
- .background(Color.LightGray)
.padding(horizontal = 16.dp, vertical = 8.dp)
)
+ }
Valeurs alpha du contenu
Bien souvent, nous voulons mettre en avant ou au contraire mettre en retrait des contenus pour communiquer sur leur importance et fournir une hiérarchie visuelle. Material Design recommande d'utiliser différents niveaux d'opacité pour représenter ces différents niveaux d'importance.
Jetpack Compose met cela en œuvre via LocalContentAlpha
. Vous pouvez spécifier une valeur alpha du contenu pour une hiérarchie en fournissant une valeur pour ce CompositionLocal
. Les composables enfants peuvent utiliser cette valeur. Par exemple, Text
et Icon
utilisent par défaut la combinaison de LocalContentColor
ajustée pour utiliser LocalContentAlpha
. Material spécifie des valeurs alpha standards (high
, medium
, disabled
), qui sont modélisées par l'objet ContentAlpha
. Notez que MaterialTheme
utilise ContentAlpha.high
par défaut pour LocalContentAlpha
.
// By default, both Icon & Text use the combination of LocalContentColor &
// LocalContentAlpha. De-emphasize content by setting a different content alpha
CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.medium) {
Text(...)
}
CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.disabled) {
Icon(...)
Text(...)
}
Il est ainsi facile de représenter l'importance des composants de manière cohérente.
Nous utiliserons les valeurs alpha de contenu pour clarifier la hiérarchie des informations dans l'article sélectionné. Dans Home.kt
, dans le composable PostMetadata
, mettez en valeur les métadonnées medium
:
+ CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.medium) {
Text(
text = text,
modifier = modifier
)
+ }
Thème sombre
Comme nous l'avons vu, pour gérer des thèmes sombres dans Compose, il vous suffit de définir différents ensembles de couleurs et d'interroger les couleurs via le thème. Voici quelques exceptions :
Vous pouvez vérifier si votre exécution est dans un thème clair :
val isLightTheme = MaterialTheme.colors.isLight
Cette valeur est définie par les fonctions du générateur lightColors/blackColors.
Dans les thèmes Material et sombres, les surfaces dont les élévations sont plus élevées reçoivent des superpositions d'élévation (leur arrière-plan est éclairci). Ce comportement est automatique lorsque vous utilisez une palette de couleurs sombres :
Surface(
elevation = 2.dp,
color = MaterialTheme.colors.surface, // color will be adjusted for elevation
...
Nous pouvons observer ce comportement automatique dans notre application, à la fois dans les composants TopAppBar
et Card
que nous utilisons. Les élévations 4 dp et 1 dp sont utilisées par défaut afin d'éclaircir automatiquement l'arrière-plan dans le thème sombre et mieux communiquer cette élévation :
Material Design suggère d'éviter les zones étendues de couleurs vives dans le thème sombre. Il est courant de colorer un conteneur avec une couleur primary
dans le thème clair et une couleur surface
dans les thèmes sombres. de nombreux composants utilisent cette stratégie par défaut, par exemple les barres d'application et la navigation inférieure. Pour faciliter l'implémentation, Colors
propose une couleur primarySurface
qui a exactement ce comportement et que ces composants utilisent par défaut.
Notre application définit actuellement la couleur de la barre d'application sur primary
. Nous pouvons suivre ce conseil en le remplaçant par primarySurface
ou simplement en supprimant ce paramètre par défaut. Dans le composable AppBar
, modifiez le paramètre backgroundColor
de TopAppBar
:
@Composable
private fun AppBar() {
TopAppBar(
...
- backgroundColor = MaterialTheme.colors.primary
+ backgroundColor = MaterialTheme.colors.primarySurface
)
}
6. Utiliser du texte
Lorsque nous utilisons du texte, nous utilisons le composable Text
pour afficher le texte, TextField
et OutlinedTextField
pour saisir du texte et TextStyle
pour appliquer un style unique à notre texte. Les AnnotatedString
permettent d'appliquer plusieurs styles à du texte.
Comme nous l'avons vu avec les couleurs, les composants Material qui affichent du texte récupèrent les personnalisations de typographie de notre thème :
Button(...) {
Text("This text will use MaterialTheme.typography.button style by default")
}
C'est un peu plus complexe que d'utiliser les paramètres par défaut, comme nous l'avons vu avec les couleurs. En effet, les composants n'affichent pas directement le texte, mais offrent des API d'emplacement qui vous permettent de transmettre un composable Text
. Comment les composants définissent-ils un style de typographie pour un thème ? En arrière-plan, ils utilisent le composable ProvideTextStyle
(qui utilise lui-même un CompositionLocal
) pour définir un TextStyle
"actuel". Le composable Text
interroge par défaut ce style "actuel" si vous ne fournissez aucun paramètre textStyle
concret.
Par exemple, à partir des classes Button
et Text
de Compose :
@Composable
fun Button(
// many other parameters
content: @Composable RowScope.() -> Unit
) {
...
ProvideTextStyle(MaterialTheme.typography.button) { //set the "current" text style
...
content()
}
}
@Composable
fun Text(
// many, many parameters
style: TextStyle = LocalTextStyle.current // get the value set by ProvideTextStyle
) { ...
Styles de texte du thème
Comme avec les couleurs, il est préférable de récupérer les TextStyle
depuis le thème actuel, ce qui vous encourage à utiliser un petit ensemble de styles cohérents et les rend plus faciles à gérer. MaterialTheme.typography
récupère l'instance Typography
définie dans votre composable MaterialTheme
, ce qui vous permet d'utiliser les styles que vous avez définis :
Text(
style = MaterialTheme.typography.subtitle2
)
Si vous devez personnaliser un TextStyle
, vous pouvez le copy
et remplacer les propriétés (il s'agit simplement d'une data class
), ou bien le composable Text
accepte un certain nombre de paramètres de style qui seront superposés à n'importe quel TextStyle
:
Text(
text = "Hello World",
style = MaterialTheme.typography.body1.copy(
background = MaterialTheme.colors.secondary
)
)
Text(
text = "Hello World",
style = MaterialTheme.typography.subtitle2,
fontSize = 22.sp // explicit size overrides the size in the style
)
Dans notre application, de nombreux emplacements appliquent automatiquement le TextStyle
du thème. Par exemple, TopAppBar
définit le style de son title
avec h6
, et ListItem
définit le style du texte principal et secondaire avec subtitle1
et body2
, respectivement.
Nous allons appliquer les styles typographiques du thème au reste de notre application. Définissez subtitle2
pour Header
, et h6
pour le titre et body2
pour l'auteur et les métadonnées du texte de FeaturedPost
:
@Composable
fun Header(...) {
...
Text(
text = text,
+ style = MaterialTheme.typography.subtitle2
Plusieurs styles
Si vous devez appliquer plusieurs styles à un texte, vous pouvez utiliser la classe AnnotatedString
pour appliquer le balisage, en ajoutant des SpanStyle
à une plage de texte. Vous pouvez les ajouter de façon dynamique ou utiliser la syntaxe DSL pour créer du contenu :
val text = buildAnnotatedString {
append("This is some unstyled text\n")
withStyle(SpanStyle(color = Color.Red)) {
append("Red text\n")
}
withStyle(SpanStyle(fontSize = 24.sp)) {
append("Large text")
}
}
Appliquez un style aux balises décrivant chaque article de notre application. Actuellement, elles utilisent le même style de texte que les autres métadonnées. Nous utiliserons le style de texte overline
et une couleur d'arrière-plan pour les différencier. Dans le composable PostMetadata
:
+ val tagStyle = MaterialTheme.typography.overline.toSpanStyle().copy(
+ background = MaterialTheme.colors.primary.copy(alpha = 0.1f)
+ )
post.tags.forEachIndexed { index, tag ->
...
+ withStyle(tagStyle) {
append(" ${tag.toUpperCase()} ")
+ }
}
7. Utiliser des formes
Tout comme pour la couleur et la typographie, la définition de votre thème pour les formes est reflétée dans les composants Material. Par exemple, les Button
vont récupérer la forme définie pour les composants de petite taille :
@Composable
fun Button( ...
shape: Shape = MaterialTheme.shapes.small
) {
Comme pour les couleurs, les composants Material utilisent des paramètres par défaut pour vérifier facilement la catégorie de forme utilisée par un composant ou pour fournir une alternative. Pour voir le mappage complet des composants aux catégories de forme, consultez la documentation.
Notez que certains composants utilisent des formes de thème modifiées en fonction de leur contexte. Par exemple, TextField
utilise par défaut le thème pour les formes de petite taille, mais applique une taille zéro angle aux angles inférieurs :
@Composable
fun FilledTextField(
// other parameters
shape: Shape = MaterialTheme.shapes.small.copy(
bottomStart = ZeroCornerSize, // overrides small theme style
bottomEnd = ZeroCornerSize // overrides small theme style
)
) {
Formes de thème
Bien sûr, vous pouvez utiliser des formes lorsque vous créez vos propres composants en utilisant des composables ou des Modifier
qui acceptent les formes, par exemple Surface
, Modifier.clip
, Modifier.background
, Modifier.border
, etc.
@Composable
fun UserProfile(
...
shape: Shape = MaterialTheme.shapes.medium
) {
Surface(shape = shape) {
...
}
}
Ajoutons la thématisation des formes à l'image affichée dans PostItem
. Nous allons lui appliquer la forme small
du thème avec un clip
Modifier
pour couper l'angle supérieur gauche :
@Composable
fun PostItem(...) {
...
Image(
painter = painterResource(post.imageThumbId),
+ modifier = Modifier.clip(shape = MaterialTheme.shapes.small)
)
8. "Styles" des composants
Compose ne permet pas explicitement d'extraire le style d'un composant tel qu'un style Android View ou CSS. Étant donné que tous les composants Compose ont été créés en langage Kotlin, d'autres moyens permettent d'atteindre le même objectif. Au lieu de cela, créez votre propre bibliothèque de composants personnalisés et utilisez-les dans votre application.
Nous avons déjà fait cela dans notre application :
@Composable
fun Header(
text: String,
modifier: Modifier = Modifier
) {
Surface(
color = MaterialTheme.colors.onSurface.copy(alpha = 0.1f),
contentColor = MaterialTheme.colors.primary,
modifier = modifier.semantics { heading() }
) {
Text(
text = text,
style = MaterialTheme.typography.subtitle2,
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp, vertical = 8.dp)
)
}
}
Le composable Header
est essentiellement un Text
stylisé que nous pouvons utiliser dans l'application.
Nous avons constaté que tous les composants sont basés sur des blocs de construction de niveau inférieur. Vous pouvez donc utiliser ces mêmes blocs pour personnaliser les composants de Material Design. Par exemple, nous avons remarqué que Button
utilise le composable ProvideTextStyle
pour définir un style de texte par défaut pour le contenu qui lui est transmis. Vous pouvez utiliser exactement le même mécanisme pour définir votre propre style de texte :
@Composable
fun LoginButton(
onClick: () -> Unit,
modifier: Modifier = Modifier,
content: @Composable RowScope.() -> Unit
) {
Button(
colors = ButtonConstants.defaultButtonColors(
backgroundColor = MaterialTheme.colors.secondary
),
onClick = onClick,
modifier = modifier
) {
ProvideTextStyle(...) { // set our own text style
content()
}
}
}
Dans cet exemple, nous créons notre propre "style" pour LoginButton
en encapsulant la classe Button
standard et spécifions certaines propriétés comme unbackgroundColor
et un style de texte différents.
Il n'existe pas non plus de concept de style par défaut, qui permet de personnaliser l'apparence par défaut d'un type de composant. Là encore, vous pouvez atteindre cet objectif en créant votre propre composant qui encapsule et personnalise un composant de bibliothèque. Par exemple, imaginons que vous souhaitiez personnaliser la forme de tous les Button
de votre application, sans modifier le thème des formes de petite taille, ce qui affecterait d'autres composants (autres que Button
). Pour ce faire, créez votre propre composable et utilisez-le dans toute l'application :
@Composable
fun AcmeButton(
// expose Button params consumers should be able to change
) {
val acmeButtonShape: Shape = ...
Button(
shape = acmeButtonShape,
// other params
)
}
9. Félicitations
Félicitations, vous avez terminé cet atelier de programmation et défini le style d'une application Jetpack Compose !
Vous avez mis en œuvre un thème Material pour personnaliser la couleur, la typographie et les formes utilisées dans l'application afin de refléter votre marque et renforcer la cohérence. Vous avez également géré les thèmes clair et sombre.
Et maintenant ?
Consultez les autres ateliers de programmation du parcours Compose :
- Principes de base de Compose
- Mises en page avec Compose
- Gestion de l'état dans Compose
- Compose pour les applications existantes
Complément d'informations
Exemples d'applications
- Owl présentant plusieurs thèmes
- Jetcaster présentant une thématisation dynamique
- Jetsnack présentant la mise en œuvre d'un système de conception personnalisé