Thématisation dans Compose avec Material 3

1. Introduction

Dans cet atelier de programmation, vous apprendrez à thématiser vos applications dans Jetpack Compose à l'aide de Material Design 3. Vous découvrirez également les principaux composants des jeux de couleurs, de la typographie et des formes de Material Design 3. Cela vous permettra de thématiser votre application par des moyens personnalisés et accessibles.

Vous étudierez également les thématisations dynamiques prises en charge et les différents niveaux d'accentuation.

Points abordés

Cet atelier de programmation traite des points suivants :

  • Principales caractéristiques de la thématisation Material 3
  • Jeux de couleurs dans Material 3 et création de thèmes pour votre application
  • Prise en charge des thèmes dynamique et clair/sombre pour votre application
  • Typographie et formes pour personnaliser votre application
  • Composants Material 3 et personnalisation du style de votre application

Objectifs de l'atelier

Dans cet atelier de programmation, vous thématiserez une application de client de messagerie appelée Reply. Vous commencerez avec une application basique, utilisant le thème de référence. Vous appliquerez ensuite ce que vous avez appris pour thématiser l'application et prendre en charge les thèmes sombres.

d15db3dc75a9d00f.png

Point de départ par défaut de notre application avec le thème de référence.

Vous créerez votre thème avec un jeu de couleurs, une typographie et des formes, pour ensuite l'appliquer à la liste de diffusion et à la page d'informations de votre application. Vous ajouterez également la prise en charge des thèmes dynamiques dans l'application. À la fin de cet atelier de programmation, votre application prendra en charge les thèmes dynamiques et les palettes de couleurs.

Material 3 clair

Résultat de l'atelier de programmation sur la thématisation. Vous bénéficiez d'un thème de couleurs claires et d'un thème dynamique clair.

Matérial 3 sombre

Résultat de l'atelier de programmation sur la thématisation. Vous bénéficiez d'un thème de couleurs sombres et d'un thème dynamique sombre.

Ce dont vous avez besoin

2. Configuration

Au cours de cette étape, vous allez télécharger le code complet de l'application Reply afin de le styliser tout au long de cet atelier de programmation.

Obtenir le code

Le code de cet atelier de programmation est disponible dans le dépôt GitHub codelab-android-compose. Pour le cloner, exécutez la commande suivante :

$ git clone https://github.com/android/codelab-android-compose

Vous pouvez également télécharger deux fichiers ZIP :

Découvrir l'application exemple

Le code que vous venez de télécharger contient du code pour tous les ateliers de programmation Compose disponibles. Pour cet atelier, ouvrez le projet ThemingCodelab dans Android Studio.

Nous vous recommandons de commencer par le code de la branche "main", puis de suivre l'atelier étape par étape, à votre propre rythme. À tout moment, vous pouvez exécuter l'une ou l'autre des versions dans Android Studio en modifiant la branche Git du projet.

Explorer le code de démarrage

Le code principal contient un package UI, contenant lui-même les packages et fichiers principaux suivants que vous utiliserez :

  • MainActivity.kt : activité de point d'entrée à partir de laquelle vous démarrerez l'application Reply.
  • com.example.reply.ui.theme : ce package contient des thèmes, des éléments de typographie et des jeux de couleurs. Vous ajouterez une thématisation Material à ce package.
  • com.example.reply.ui.components : contient les composants personnalisés de l'application, comme les éléments de liste, les barres d'application, etc. Vous appliquerez des thèmes à ces composants.
  • ReplyApp.kt : il s'agit de notre principale fonction composable. C'est ici que commencera l'arborescence de l'interface utilisateur. C'est dans ce fichier que vous appliquerez les thèmes de premier niveau.

Cet atelier de programmation porte sur les fichiers du package ui.

3. Thématisation Material 3

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.

La thématisation Material 3 comprend les sous-systèmes suivants pour ajouter un thème à votre application : le jeu de couleurs, la typographie et les formes. Lorsque vous personnalisez ces valeurs, vos modifications sont automatiquement répercutées dans les composants M3 que vous utilisez pour créer votre application. Découvrons chaque sous-système et implémentons-les dans l'application exemple.

Sous-systèmes de Material Design : couleur, typographie et formes.

Les sous-systèmes de Material 3 comprennent les couleurs, la typographie et les formes.

4. Jeux de couleurs

Chaque jeu de couleurs se base sur un ensemble de cinq couleurs clés associées chacune à une palette tonale de 13 tons et utilisées par les composants Material 3.

Cinq couleurs clés de référence pour créer une thématisation M3.

Cinq couleurs clés de référence pour créer une thématisation M3.

Chaque couleur d'accentuation (primaire, secondaire et tertiaire) est ensuite déclinée en quatre couleurs compatibles de tons différents pour l'association, l'accentuation et l'expression visuelle.

Quatre tons déclinés à partir des couleurs primaires, secondaires et tertiaires de référence.

Quatre tons déclinés à partir des couleurs primaires, secondaires et tertiaires de référence.

De même, les couleurs neutres sont divisées en quatre tons compatibles pour les surfaces et l'arrière-plan. Cela permet de mettre en valeur les icônes de texte quelle que soit la surface sur laquelle est sont disposées.

Quatre tons déclinés à partir de couleurs neutres de référence.

Quatre tons déclinés à partir de couleurs neutres de référence.

Découvrez-en plus sur les jeux de couleurs et les rôles de couleurs.

Générer des jeux de couleurs

Bien que vous puissiez créer un ColorScheme personnalisé manuellement, il est souvent plus facile d'en générer une à l'aide des couleurs sources de votre marque. L'outil Material Theme Builder vous permet d'effectuer cette opération et d'éventuellement exporter le code de la thématisation Compose.

Vous pouvez choisir la couleur qui vous convient, mais pour cet atelier, vous utiliserez la couleur primaire par défaut de l'application Reply (#825500). Cliquez sur Primary (Couleur primaire) dans la section Core colors (Couleurs principales) située à gauche. Ajoutez ensuite le code dans le sélecteur de couleur.

294f73fc9d2a570e.png

Ajout du code de la couleur primaire dans Material Theme Builder.

Une fois la couleur primaire ajoutée dans Material Theme Builder, vous devriez voir le thème suivant ainsi que l'option d'exportation en haut à droite. Pour cet atelier de programmation, vous exporterez le thème dans Jetpack Compose.

Material Theme Builder avec une option d'exportation en haut à droite.

Material Theme Builder avec une option d'exportation en haut à droite.

La couleur primaire #825500 génère le thème suivant, que vous ajouterez à l'application. Material 3 propose de nombreux rôles de couleur pour exprimer avec finesse l'état, la proéminence et l'accentuation d'un composant.

Jeu de couleurs clair et sombre extrait à partir de la couleur primaire.

Jeu de couleurs clair et sombre extrait à partir de la couleur primaire.

Le fichier The Color.kt généré contient les couleurs de votre thème, ainsi que tous les rôles définis pour les couleurs des thèmes clair et sombre.

Color.kt

package com.example.reply.ui.theme
import androidx.compose.ui.graphics.Color

val md_theme_light_primary = Color(0xFF825500)
val md_theme_light_onPrimary = Color(0xFFFFFFFF)
val md_theme_light_primaryContainer = Color(0xFFFFDDB3)
val md_theme_light_onPrimaryContainer = Color(0xFF291800)
val md_theme_light_secondary = Color(0xFF6F5B40)
val md_theme_light_onSecondary = Color(0xFFFFFFFF)
val md_theme_light_secondaryContainer = Color(0xFFFBDEBC)
val md_theme_light_onSecondaryContainer = Color(0xFF271904)
val md_theme_light_tertiary = Color(0xFF51643F)
val md_theme_light_onTertiary = Color(0xFFFFFFFF)
val md_theme_light_tertiaryContainer = Color(0xFFD4EABB)
val md_theme_light_onTertiaryContainer = Color(0xFF102004)
val md_theme_light_error = Color(0xFFBA1A1A)
val md_theme_light_errorContainer = Color(0xFFFFDAD6)
val md_theme_light_onError = Color(0xFFFFFFFF)
val md_theme_light_onErrorContainer = Color(0xFF410002)
val md_theme_light_background = Color(0xFFFFFBFF)
val md_theme_light_onBackground = Color(0xFF1F1B16)
val md_theme_light_surface = Color(0xFFFFFBFF)
val md_theme_light_onSurface = Color(0xFF1F1B16)
val md_theme_light_surfaceVariant = Color(0xFFF0E0CF)
val md_theme_light_onSurfaceVariant = Color(0xFF4F4539)
val md_theme_light_outline = Color(0xFF817567)
val md_theme_light_inverseOnSurface = Color(0xFFF9EFE7)
val md_theme_light_inverseSurface = Color(0xFF34302A)
val md_theme_light_inversePrimary = Color(0xFFFFB951)
val md_theme_light_shadow = Color(0xFF000000)
val md_theme_light_surfaceTint = Color(0xFF825500)
val md_theme_light_outlineVariant = Color(0xFFD3C4B4)
val md_theme_light_scrim = Color(0xFF000000)

val md_theme_dark_primary = Color(0xFFFFB951)
val md_theme_dark_onPrimary = Color(0xFF452B00)
val md_theme_dark_primaryContainer = Color(0xFF633F00)
val md_theme_dark_onPrimaryContainer = Color(0xFFFFDDB3)
val md_theme_dark_secondary = Color(0xFFDDC2A1)
val md_theme_dark_onSecondary = Color(0xFF3E2D16)
val md_theme_dark_secondaryContainer = Color(0xFF56442A)
val md_theme_dark_onSecondaryContainer = Color(0xFFFBDEBC)
val md_theme_dark_tertiary = Color(0xFFB8CEA1)
val md_theme_dark_onTertiary = Color(0xFF243515)
val md_theme_dark_tertiaryContainer = Color(0xFF3A4C2A)
val md_theme_dark_onTertiaryContainer = Color(0xFFD4EABB)
val md_theme_dark_error = Color(0xFFFFB4AB)
val md_theme_dark_errorContainer = Color(0xFF93000A)
val md_theme_dark_onError = Color(0xFF690005)
val md_theme_dark_onErrorContainer = Color(0xFFFFDAD6)
val md_theme_dark_background = Color(0xFF1F1B16)
val md_theme_dark_onBackground = Color(0xFFEAE1D9)
val md_theme_dark_surface = Color(0xFF1F1B16)
val md_theme_dark_onSurface = Color(0xFFEAE1D9)
val md_theme_dark_surfaceVariant = Color(0xFF4F4539)
val md_theme_dark_onSurfaceVariant = Color(0xFFD3C4B4)
val md_theme_dark_outline = Color(0xFF9C8F80)
val md_theme_dark_inverseOnSurface = Color(0xFF1F1B16)
val md_theme_dark_inverseSurface = Color(0xFFEAE1D9)
val md_theme_dark_inversePrimary = Color(0xFF825500)
val md_theme_dark_shadow = Color(0xFF000000)
val md_theme_dark_surfaceTint = Color(0xFFFFB951)
val md_theme_dark_outlineVariant = Color(0xFF4F4539)
val md_theme_dark_scrim = Color(0xFF000000)

val seed = Color(0xFF825500)

Le fichier The Theme.kt généré contient une configuration pour les jeux de couleurs clairs et sombres, ainsi que le thème de l'application. Il contient également la fonction composable de thématisation principale, AppTheme().

Theme.kt

package com.example.reply.ui.theme

import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.lightColorScheme
import androidx.compose.material3.darkColorScheme
import androidx.compose.runtime.Composable

private val LightColors = lightColorScheme(
   primary = md_theme_light_primary,
   onPrimary = md_theme_light_onPrimary,
   primaryContainer = md_theme_light_primaryContainer,
   onPrimaryContainer = md_theme_light_onPrimaryContainer,
   secondary = md_theme_light_secondary,
   onSecondary = md_theme_light_onSecondary,
   secondaryContainer = md_theme_light_secondaryContainer,
   onSecondaryContainer = md_theme_light_onSecondaryContainer,
   tertiary = md_theme_light_tertiary,
   onTertiary = md_theme_light_onTertiary,
   tertiaryContainer = md_theme_light_tertiaryContainer,
   onTertiaryContainer = md_theme_light_onTertiaryContainer,
   error = md_theme_light_error,
   errorContainer = md_theme_light_errorContainer,
   onError = md_theme_light_onError,
   onErrorContainer = md_theme_light_onErrorContainer,
   background = md_theme_light_background,
   onBackground = md_theme_light_onBackground,
   surface = md_theme_light_surface,
   onSurface = md_theme_light_onSurface,
   surfaceVariant = md_theme_light_surfaceVariant,
   onSurfaceVariant = md_theme_light_onSurfaceVariant,
   outline = md_theme_light_outline,
   inverseOnSurface = md_theme_light_inverseOnSurface,
   inverseSurface = md_theme_light_inverseSurface,
   inversePrimary = md_theme_light_inversePrimary,
   surfaceTint = md_theme_light_surfaceTint,
   outlineVariant = md_theme_light_outlineVariant,
   scrim = md_theme_light_scrim,
)

private val DarkColors = darkColorScheme(
   primary = md_theme_dark_primary,
   onPrimary = md_theme_dark_onPrimary,
   primaryContainer = md_theme_dark_primaryContainer,
   onPrimaryContainer = md_theme_dark_onPrimaryContainer,
   secondary = md_theme_dark_secondary,
   onSecondary = md_theme_dark_onSecondary,
   secondaryContainer = md_theme_dark_secondaryContainer,
   onSecondaryContainer = md_theme_dark_onSecondaryContainer,
   tertiary = md_theme_dark_tertiary,
   onTertiary = md_theme_dark_onTertiary,
   tertiaryContainer = md_theme_dark_tertiaryContainer,
   onTertiaryContainer = md_theme_dark_onTertiaryContainer,
   error = md_theme_dark_error,
   errorContainer = md_theme_dark_errorContainer,
   onError = md_theme_dark_onError,
   onErrorContainer = md_theme_dark_onErrorContainer,
   background = md_theme_dark_background,
   onBackground = md_theme_dark_onBackground,
   surface = md_theme_dark_surface,
   onSurface = md_theme_dark_onSurface,
   surfaceVariant = md_theme_dark_surfaceVariant,
   onSurfaceVariant = md_theme_dark_onSurfaceVariant,
   outline = md_theme_dark_outline,
   inverseOnSurface = md_theme_dark_inverseOnSurface,
   inverseSurface = md_theme_dark_inverseSurface,
   inversePrimary = md_theme_dark_inversePrimary,
   surfaceTint = md_theme_dark_surfaceTint,
   outlineVariant = md_theme_dark_outlineVariant,
   scrim = md_theme_dark_scrim,
)

@Composable
fun AppTheme(
   useDarkTheme: Boolean = isSystemInDarkTheme(),
   content: @Composable() () -> Unit
) {
   val colors = if (!useDarkTheme) {
       LightColors
   } else {
       DarkColors
   }

   MaterialTheme(
       colorScheme = colors,
       content = content
   )
}

L'élément principal de la mise en œuvre de la thématisation dans Jetpack Compose est le composable MaterialTheme.

Encapsulez le composable MaterialTheme() dans la fonction AppTheme(), qui accepte deux paramètres :

  • useDarkTheme : ce paramètre est lié à la fonction isSystemInDarkTheme() pour observer les paramètres de thématisation du système et appliquer le thème clair ou sombre. Si vous souhaitez conserver manuellement le thème clair ou sombre de votre application, vous pouvez transmettre une valeur booléenne à useDarkTheme.
  • content : le contenu auquel le thème sera appliqué.

Theme.kt

@Composable
fun AppTheme(
   useDarkTheme: Boolean = isSystemInDarkTheme(),
   content: @Composable() () -> Unit
) {
   val colors = if (!useDarkTheme) {
       LightColors
   } else {
       DarkColors
   }

   MaterialTheme(
       colorScheme = colors,
       content = content
   )
}

Si vous essayez d'exécuter l'application maintenant, vous devriez ne constater aucune différence. Même si vous avez importé un nouveau jeu de couleurs avec de nouvelles couleurs de thématisation, vous voyez toujours le thème de référence, car vous n'avez pas appliqué le thème personnalisé à l'application Compose.

Application utilisant le thème de référence lorsqu'aucun thème personnalisé n'est appliqué.

Application utilisant le thème de référence lorsqu'aucun thème personnalisé n'est appliqué.

Pour appliquer le nouveau thème dans MainActivity.kt, encapsulez le composable principal ReplyApp avec la fonction de thématisation principale, AppTheme().

MainActivity.kt

setContent {
   val uiState by viewModel.uiState.collectAsStateWithLifecycle()

   AppTheme {
       ReplyApp(/*..*/)
   }
}

Vous modifierez également les fonctions d'aperçu pour appliquer le thème aux aperçus de l'application. Encapsulez le composable ReplyApp dans ReplyAppPreview() avec AppTheme pour appliquer la thématisation aux aperçus.

Les thèmes clair et sombre du système étant définis dans les paramètres d'aperçu, vous obtiendrez les deux aperçus.

MainActivity.kt

@Preview(
   uiMode = Configuration.UI_MODE_NIGHT_YES,
   name = "DefaultPreviewDark"
)
@Preview(
   uiMode = Configuration.UI_MODE_NIGHT_NO,
   name = "DefaultPreviewLight"
)
@Composable
fun ReplyAppPreview() {
   AppTheme {
       ReplyApp(
           replyHomeUIState = ReplyHomeUIState(
               emails = LocalEmailsDataProvider.allEmails
           )
       )
   }
}

Si vous exécutez l'application maintenant, vous devriez obtenir des aperçus de l'application avec les couleurs du thème importée plutôt que du thème de référence.

fddf7b9cc99b1fe3.png be7a661b4553167b.png

Application avec le thème de référence (à gauche).

Application avec le thème de couleurs importé (à droite).

674cec6cc12db6a0.png

Aperçus clair et sombre de l'application avec des thèmes de couleurs importés

Material 3 prend en charge les jeux de couleurs clair et sombre. Vous venez d'encapsuler l'application avec le thème importé. Les composants Material 3 utilisent les rôles de couleur par défaut.

Découvrons les rôles de couleur et leur utilisation avant de les ajouter à l'application.

Rôles de couleurs et accessibilité

Chaque rôle de couleur peut être utilisé à différents endroits en fonction de l'état, de la proéminence et de l'accentuation du composant.

1f184a05ea57aa84.png

Rôles de couleur des couleurs primaires, secondaires et tertiaires.

La couleur primaire est la couleur de base utilisée pour les composants principaux tels que les boutons visibles et les états actifs.

La couleur secondaire est utilisée pour les composants moins visibles dans l'interface utilisateur, tels que les icônes de filtre.

La couleur tertiaire est utilisée pour accentuer les contrastes. Les couleurs neutres, quant à elles, sont utilisées pour l'arrière-plan et les surfaces de l'application.

Le système de couleurs de Material fournit des valeurs tonales et des mesures standards pouvant être utilisées pour obtenir des rapports de contraste accessibles. Utilisez la couleur "on-primary" au-dessus de la couleur "primary" et "on-primary-container" au-dessus de la couleur "primary-container" afin d'assurer un contraste accessible à l'utilisateur. Il en va de même pour les autres couleurs d'accentuation et neutres.

Pour en savoir plus, consultez la section Rôles de couleurs et accessibilité.

Élévations des tons et des ombres

Material 3 représente l'élévation principalement à l'aide de superpositions de couleurs tonales. Il s'agit d'une nouvelle façon de distinguer les conteneurs et les surfaces les uns des autres : l'augmentation de l'élévation tonale utilise un ton plus prononcé, en plus des ombres.

Élévation tonale avec des ombres plus prononcées Élévation tonale au niveau 2. Les couleurs sont issues de l'emplacement de la couleur primaire.

Les superpositions d'élévation dans les thèmes sombres ont également été remplacées par des superpositions de couleurs tonales dans Material Design 3. La couleur de superposition provient de l'emplacement de la couleur primaire.

La surface M3 (le composable de sauvegarde derrière la plupart des composants M3) prend en charge l'élévation des tons et des ombres :

Surface(
   modifier = modifier,
   tonalElevation = {..}
   shadowElevation = {..}
) {
   Column(content = content)
}

Ajouter des couleurs à l'application

Si vous exécutez l'application, les couleurs exportées y apparaissent, alors que les composants prennent les couleurs par défaut. Maintenant que nous connaissons les rôles de couleurs et leur utilisation, créons un thème pour l'application avec les bons rôles de couleur.

be7a661b4553167b.png

Application avec un thème de couleur et des composants utilisant des rôles de couleur par défaut.

Couleurs de surface

Sur l'écran d'accueil, commencez par encapsuler le composable principal de l'application dans Surface(). Il s'agit de la base sur laquelle le contenu de l'application doit être placé. Ouvrez MainActivity.kt et encapsulez le composable ReplyApp() avec Surface.

Vous appliquerez également une élévation tonale de 5 dp afin de donner à la surface une couleur tonale issue de l'emplacement primaire. Vous accentuerez ainsi le contraste avec l'élément de la liste et la barre de recherche. Par défaut, l'élévation des tons et des ombres de la surface est de 0 dp.

MainActivity.kt

AppTheme {
   Surface(tonalElevation = 5.dp) {
       ReplyApp(
           replyHomeUIState = uiState,
          // other parameters
         )
   }
}

Si vous exécutez votre application maintenant, et que vous consultez les pages de liste et d'informations, la surface tonale devrait être appliquée à l'ensemble de l'application.

be7a661b4553167b.png e70d762495173610.png

Arrière-plan de l'application sans surface ni couleur tonale (à gauche).

Arrière-plan de l'application une fois la surface et la couleur tonale appliquées (à droite).

Couleurs de la barre d'application

Contrairement aux demandes de conception, l'arrière-plan de la barre de recherche personnalisée en haut de l'écran n'est pas clair. Par défaut, la surface de référence s'applique Vous pouvez ajouter un arrière-plan pour marquer la séparation.

5779fc399d8a8187.png

Barre de recherche personnalisée sans arrière-plan (à gauche).

Barre de recherche personnalisée avec un arrière-plan (à droite).

Vous allez maintenant modifier ui/components/ReplyAppBars.kt. Ce fichier contient la barre d'application. Vous allez ajouter MaterialTheme.colorScheme.background au Modifier du composable Row.

ReplyAppBars.kt

@Composable
fun ReplySearchBar(modifier: Modifier = Modifier) {
   Row(
       modifier = modifier
           .fillMaxWidth()
           .padding(16.dp)
           .background(MaterialTheme.colorScheme.background),
       verticalAlignment = Alignment.CenterVertically
   ) {
       // Search bar content
   }
}

Grâce à la couleur d'arrière-plan, une séparation nette devrait normalement apparaître entre la surface tonale et la barre d'application.

b1b374b801dadc06.png

Barre de recherche avec une couleur d'arrière-plan au-dessus de la surface tonale.

Couleurs du bouton d'action flottant

70ceac87233fe466.png

Grand bouton d'action flottant sans thématisation appliquée (à gauche).

Grand bouton d'action flottant avec une thématisation appliquée et une couleur tertiaire (à droite).

Sur l'écran d'accueil, vous pouvez améliorer l'apparence du bouton d'action flottant afin qu'il se démarque des boutons d'incitation à l'action. Pour ce faire, vous devez lui appliquer une couleur d'accentuation tertiaire.

Dans le fichier ReplyListContent.kt, modifiez la couleur du conteneur (containerColor) pour le bouton d'action flottant avec tertiaryContainer et la couleur du contenu avec onTertiaryContainer pour préserver l'accessibilité et le contraste des couleurs.

ReplyListContent.kt

ReplyInboxScreen(/*..*/) {
// Email list content
  LargeFloatingActionButton(
    containerColor = MaterialTheme.colorScheme.tertiaryContainer,
    contentColor = MaterialTheme.colorScheme.onTertiaryContainer
  ){
   /*..*/
  }
}

Exécutez l'application pour voir le thème du bouton d'action flottant. Pour cet atelier de programmation, vous utilisez un LargeFloatingActionButton.

Couleurs des cartes

La liste de diffusion affichée sur l'écran d'accueil contient des cartes. Par défaut, il s'agit d'une carte remplie. La couleur du conteneur utilisée est une variante de celle de la surface afin de distinguer nettement la carte de la surface. Compose fournit également des implémentations de ElevatedCard et OutlinedCard.

Vous pouvez mettre en évidence certains éléments importants en appliquant des tons de couleurs secondaires. Modifiez ui/components/ReplyEmailListItem.kt en modifiant la couleur du conteneur de la carte à l'aide de CardDefaults.cardColors() pour les e-mails importants :

ReplyEmailListItem.kt

Card(
   modifier =  modifier
       .padding(horizontal = 16.dp, vertical = 4.dp)
       .semantics { selected = isSelected }
       .clickable { navigateToDetail(email.id) },
   colors = CardDefaults.cardColors(
       containerColor = if (email.isImportant)
           MaterialTheme.colorScheme.secondaryContainer
       else MaterialTheme.colorScheme.surfaceVariant
   )
){
  /*..*/
}

5818200be0b01583.png 9367d40023db371d.png

Mettez en évidence l'élément de la liste en utilisant une couleur de conteneur secondaire sur la surface tonale.

Couleur des éléments de la liste détaillée

Vous avez désormais défini un thème pour votre écran d'accueil. Accédez à la page d'informations en cliquant sur l'un éléments de la liste de diffusion.

7a9ea7cf3e91e9c7.png 79b3874aeca4cd1.png

Page d'informations par défaut sans thématisation pour les éléments de liste (à gauche).

Éléments de la liste détaillée avec une thématisation d'arrière-plan appliquée (à droite).

Aucune couleur n'est appliquée à vos éléments de liste. Par conséquent, la couleur de surface tonale par défaut est utilisée. Appliquez une couleur d'arrière-plan aux éléments de liste afin de créer une séparation et ajoutez une marge intérieure pour laisser un espace autour de l'arrière-plan.

ReplyEmailThreadItem.kt

@Composable
fun ReplyEmailThreadItem(
   email: Email,
   modifier: Modifier = Modifier
) {
   Column(
       modifier = modifier
           .fillMaxWidth()
           .padding(16.dp)
           .background(MaterialTheme.colorScheme.background)
           .padding(20.dp)
    ) {
      // List item content
    }
}

Vous pouvez constater que, simplement en ajoutant un arrière-plan, vous avez une séparation nette entre la surface tonale et les éléments de liste.

Vos pages d'accueil et d'informations comportent désormais des rôles et une utilisation de couleurs adéquats. Voyons comment votre application peut exploiter les couleurs dynamiques pour proposer une expérience encore plus personnalisée et cohérente.

5. Ajouter des couleurs dynamiques dans l'application

La couleur dynamique est la partie clé de Material 3, dans laquelle un algorithme extrait des couleurs personnalisées à partir du fond d'écran d'un utilisateur pour l'appliquer à ses applications et à son UI du système.

La thématisation dynamique personnalise davantage vos applications. Elle propose également aux utilisateurs une expérience cohérente et fluide grâce au thème du système.

Les couleurs dynamiques sont disponibles sur Android 12 ou version ultérieure. Si des couleurs dynamiques sont disponibles, vous pouvez configurer un jeu de couleurs dynamique à l'aide de dynamicDarkColorScheme() ou dynamicLightColorScheme(). Sinon, utilisez un ColorScheme clair ou sombre par défaut.

Remplacez le code de la fonction AppTheme dans le fichier Theme.kt par le code suivant :

Theme.kt

@Composable
fun AppTheme(
   useDarkTheme: Boolean =  isSystemInDarkTheme(),
   content: @Composable () -> Unit
) {
   val context = LocalContext.current
   val colors = when {
       (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) -> {
           if (useDarkTheme) dynamicDarkColorScheme(context)
           else dynamicLightColorScheme(context)
       }
       useDarkTheme -> DarkColors
       else -> LightColors
   }
   
      MaterialTheme(
       colorScheme = colors,
       content = content
     )
}

fecc63b4c6034236.png

Thème dynamique extrait du fond d'écran d'Android 13.

Lorsque vous exécutez l'application, vous devriez voir la thématisation dynamique appliquée à partir du fond d'écran Android 13 par défaut.

Vous pouvez également ajouter un style dynamique à la barre d'état en fonction du jeu de couleurs utilisé pour thématiser votre application.

1095e2b2c1ffdc14.png

Application sans couleur appliquée à la barre d'état (à gauche).

Application avec une couleur appliquée à la barre d'état (à droite).

Pour modifier la couleur de la barre d'état en fonction de la couleur primaire de votre thème, ajoutez la couleur de la barre d'état après la sélection du jeu de couleurs dans le composable AppTheme :

Theme.kt

@Composable
fun AppTheme(
   useDarkTheme: Boolean =  isSystemInDarkTheme(),
   content: @Composable () -> Unit
) {
 
 // color scheme selection code

 // Add primary status bar color from chosen color scheme.
 val view = LocalView.current
 if (!view.isInEditMode) {
    SideEffect {
        val window = (view.context as Activity).window
        window.statusBarColor = colors.primary.toArgb()
        WindowCompat
            .getInsetsController(window, view)
            .isAppearanceLightStatusBars = useDarkTheme
    }
 }
   
  MaterialTheme(
    colorScheme = colors,
     content = content
   )
}

Lorsque vous exécutez l'application, la barre d'état devrait s'afficher en fonction de la couleur primaire de votre thème. Vous pouvez également essayer les thèmes dynamiques clair et sombre en passant au thème sombre du système.

69093b5bce31fd43.png

Thème dynamique clair (à gauche) et sombre (à droite) appliqué en fonction du fond d'écran par défaut d'Android 13.

Jusqu'à présent, vous avez appliqué des couleurs à votre application pour améliorer son apparence. Toutefois, la taille des différents textes de l'application est identique. Vous pouvez donc ajouter une typographie.

6. Typographie

Material Design 3 définit une échelle de types. La dénomination et le regroupement ont été simplifiés pour les affichages, les titres principaux, les titres, le corps de texte et les libellés, avec des tailles grandes, moyennes et petites pour chaque catégorie.

999a161dcd9b0ec4.png

Échelle de types Material 3.

Définir la typographie

Compose fournit la classe Typography M3, ainsi que les classes TextStyle et font-related existantes pour modéliser l'échelle de types Material 3.

Le constructeur de typographie propose des valeurs par défaut pour chaque style afin que vous puissiez omettre les paramètres que vous ne souhaitez pas personnaliser. Pour en savoir plus, consultez les styles de typographies et leurs valeurs par défaut.

Vous allez utiliser cinq styles de typographies dans votre application : headlineSmall, titleLarge, bodyLarge, bodyMedium et labelMedium. Ces styles couvrent à la fois l'écran d'accueil et l'écran d'informations.

Écran présentant l'utilisation de la typographie pour le style du titre, du libellé et du corps.

Écran présentant l'utilisation de la typographie pour le style du titre, du libellé et du corps.

Accédez ensuite au package ui/theme et ouvrez Type.kt. Ajoutez le code suivant pour appliquer votre propre implémentation de certains styles de texte au lieu des valeurs par défaut :

Type.kt

val typography = Typography(
   headlineSmall = TextStyle(
       fontWeight = FontWeight.SemiBold,
       fontSize = 24.sp,
       lineHeight = 32.sp,
       letterSpacing = 0.sp
   ),
   titleLarge = TextStyle(
       fontWeight = FontWeight.Normal,
       fontSize = 18.sp,
       lineHeight = 28.sp,
       letterSpacing = 0.sp
   ),
   bodyLarge = TextStyle(
       fontWeight = FontWeight.Normal,
       fontSize = 16.sp,
       lineHeight = 24.sp,
       letterSpacing = 0.15.sp
   ),
   bodyMedium = TextStyle(
       fontWeight = FontWeight.Medium,
       fontSize = 14.sp,
       lineHeight = 20.sp,
       letterSpacing = 0.25.sp
   ),
   labelMedium = TextStyle(
       fontWeight = FontWeight.SemiBold,
       fontSize = 12.sp,
       lineHeight = 16.sp,
       letterSpacing = 0.5.sp
   )
)

Vous avez à présent défini votre typographie. Pour l'ajouter à votre thème, transmettez-le au composable MaterialTheme() dans AppTheme :

Theme.kt

@Composable
fun AppTheme(
   useDarkTheme: Boolean = isSystemInDarkTheme(),
   content: @Composable() () -> Unit
) {
  // dynamic theming content

   MaterialTheme(
       colorScheme = colors,
       typography = typography,
       content = content
   )
}

Utiliser la typographie

Tout comme pour les couleurs, accédez au style de typographie du thème actuel à l'aide de MaterialTheme.typography. Vous disposez ainsi d'une instance qui utilise toutes les typographies définies dans Type.kt.

Text(
   text = "Hello M3 theming",
   style = MaterialTheme.typography.titleLarge
)

Text(
   text = "you are learning typography",
   style = MaterialTheme.typography.bodyMedium
)

Votre produit n'aura probablement pas besoin des 15 styles par défaut issus de l'échelle de types Material Design. Dans cet atelier de programmation, seules cinq tailles sont utilisées.

Comme vous n'avez pas appliqué de typographie aux composables Text(), l'ensemble du texte revient au style Typography.bodyLarge par défaut.

Typographie de la liste d'accueil

Appliquez ensuite la typographie à la fonction ReplyEmailListItem dans ui/components/ReplyEmailListItem.kt pour distinguer nettement les titres et les libellés :

ReplyEmailListItem.kt

Text(
   text = email.sender.firstName,
   style = MaterialTheme.typography.labelMedium
)

Text(
   text = email.createdAt,
   style = MaterialTheme.typography.labelMedium
)

Text(
   text = email.subject,
   style = MaterialTheme.typography.titleLarge,
   modifier = Modifier.padding(top = 12.dp, bottom = 8.dp),
)

Text(
   text = email.body,
   maxLines = 2,
   style = MaterialTheme.typography.bodyLarge,
   overflow = TextOverflow.Ellipsis
)

90645c0765167bb7.png 6c4af2f412c18bfb.png

Écran d'accueil sans typographie appliqué (à gauche).

Écran d'accueil avec une typographie appliquée (à droite).

Typographie de la liste détaillée

De même, vous ajouterez la typographie à l'écran d'informations en modifiant tous les composables de texte de ReplyEmailThreadItem dans ui/components/ReplyEmailThreadItem.kt :

ReplyEmailThreadItem.kt

Text(
   text = email.sender.firstName,
   style = MaterialTheme.typography.labelMedium
)

Text(
   text = stringResource(id = R.string.twenty_mins_ago),
   style = MaterialTheme.typography.labelMedium
)

Text(
   text = email.subject,
   style = MaterialTheme.typography.bodyMedium,
   modifier = Modifier.padding(top = 12.dp, bottom = 8.dp),
)

Text(
   text = email.body,
   style = MaterialTheme.typography.bodyLarge,
   color = MaterialTheme.colorScheme.onSurfaceVariant
)

543ac09e43d8761.png 3412771e95a45f36.png

Écran d'informations sans typographie appliquée (à gauche).

Écran d'informations avec une typographie appliquée (à droite).

Personnaliser la typographie

Avec Compose, il est très facile de personnaliser le style du texte ou d'utiliser une police personnalisée. Vous pouvez modifier TextStyle pour personnaliser le type et la famille de polices, l'espacement des lettres, etc.

Modifiez le style du texte dans le fichier theme/Type.kt. Tous les composants utilisant ce fichier seront également impactés.

Passez fontWeight à SemiBold et lineHeight à 32.sp pour titleLarge. Cela s'appliquera à l'objet des éléments de liste. L'objet sera ainsi accentué, ce qui créera une distinction nette.

Type.kt

...
titleLarge = TextStyle(
   fontWeight = FontWeight.SemiBold,
   fontSize = 18.sp,
   lineHeight = 32.sp,
   letterSpacing = 0.0.sp
),
...

f8d2212819eb0b61.png

Application d'une typographie personnalisée au texte de l'objet.

7. Formes

Les surfaces de Material peuvent prendre différentes formes. Les formes permettent de capter l'attention, d'identifier les composants, de communiquer un état et d'exprimer une image de marque.

Définir des formes

Compose fournit à la classe Shapes des paramètres étendus pour implémenter de nouvelles formes M3. L'échelle de formes M3, tout comme l'échelle de types, permet d'utiliser une gamme expressive de formes dans l'interface utilisateur.

L'échelle de formes propose différentes tailles :

  • Très petite
  • Petite
  • Moyenne
  • Grande
  • Très grande

Chaque forme est associée à une valeur par défaut pouvant être remplacée. Pour votre application, vous utiliserez la forme de taille moyenne afin de modifier les éléments de liste. Vous pouvez cependant déclarer d'autres formes. Créez un fichier nommé Shape.kt dans le package ui/theme et ajoutez les lignes de code pour ajouter des formes :

Shape.kt

package com.example.reply.ui.theme

import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Shapes
import androidx.compose.ui.unit.dp

val shapes = Shapes(
   extraSmall = RoundedCornerShape(4.dp),
   small = RoundedCornerShape(8.dp),
   medium = RoundedCornerShape(16.dp),
   large = RoundedCornerShape(24.dp),
   extraLarge = RoundedCornerShape(32.dp)
)

Maintenant que vous avez défini vos shapes, transmettez-les au MaterialTheme M3 comme vous l'avez fait pour les couleurs et la typographie :

Theme.kt

@Composable
fun AppTheme(
   useDarkTheme: Boolean = isSystemInDarkTheme(),
   content: @Composable() () -> Unit
) {
  // dynamic theming content

   MaterialTheme(
       colorScheme = colors,
       typography = typography,
       shapes = shapes,
       content = content
   )
}

Utiliser des formes

Comme pour la couleur et la typographie, vous pouvez appliquer des formes aux composants Material à l'aide de MaterialTheme.shape. L'instance Shapevous permettra alors d'accéder aux formes Material.

Des formes par défaut sont déjà appliquées à de nombreux composants Material, mais vous pouvez ajouter et appliquer vos propres formes aux composants via les emplacements disponibles.

Card(shape = MaterialTheme.shapes.medium) { /* card content */ }
FloatingActionButton(shape = MaterialTheme.shapes.large) { /* fab content */}

Valeurs des formes par défaut pour tous les composants Material 3.Mappage de composants Material à l'aide de différents types de formes.

Vous pouvez voir le mappage des formes pour tous les composants dans la documentation sur les formes.

Vous pouvez utiliser d'autres formes dans Compose, RectangleShape et CircleShape. La forme rectangulaire ne possède pas d'arrondi et la forme circulaire présente un contour complètement arrondi.

Vous pouvez également appliquer une forme à vos composants à l'aide de Modifiers qui prennent certaines formes, comme Modifier.clip, Modifier.background et Modifier.border.

Forme de la barre d'application

Nous souhaitons que l'arrière-plan de la barre d'application soit arrondi :

f873392abe535494.png

TopAppBar utilise un élément Row avec une couleur d'arrière-plan. Pour obtenir l'arrière-plan arrondi souhaité, définissez la forme de l'arrière-plan en transmettant CircleShape au modificateur d'arrière-plan :

ReplyAppBars.kt

@Composable
fun ReplySearchBar(modifier: Modifier = Modifier) {
   Row(
       modifier = modifier
           .fillMaxWidth()
           .padding(16.dp)
           .background(
               MaterialTheme.colorScheme.background,
               CircleShape
           ),
       verticalAlignment = Alignment.CenterVertically
   ) {
       // Search bar content
   }
}

f873392abe535494.png

Forme des éléments de la liste détaillée

L'écran d'accueil présente une carte qui utilise Shape.Medium par défaut. Toutefois, pour la page d'informations, vous avez utilisé une colonne avec une couleur d'arrière-plan. Pour donner une apparence uniforme à la liste, appliquez-lui une forme de taille moyenne.

3412771e95a45f36.png 80ee881c41a98c2a.png

Colonne des éléments de liste détaillée sans forme appliquée à la liste (à gauche) et avec une forme de taille moyenne appliquée à la liste (à droite).

ReplyEmailThreadItem.kt

@Composable
fun ReplyEmailThreadItem(
   email: Email,
   modifier: Modifier = Modifier
) {
   Column(
       modifier = modifier
           .fillMaxWidth()
           .padding(8.dp)
           .background(
               MaterialTheme.colorScheme.background,
               MaterialTheme.shapes.medium
           )
           .padding(16.dp)

   ) {
      // List item content
      
   }
}

À présent, lorsque vous exécutez votre application, les éléments de la liste présente sur l'écran d'informations apparaissent sous une forme de taille medium.

8. Mise en valeur

La mise en valeur dans l'interface utilisateur permet de mettre en avant certains contenus, par exemple lorsque vous souhaitez différencier le titre des sous-titres. La mise en valeur dans M3 utilise des spectres de couleurs et des associations de couleurs. Pour ce faire, deux possibilités s'offrent à vous :

  1. Associez les couleurs "surface", "surface-variant" et "background" avec les couleurs "on-surface" et "on-surface-variants" du système de couleurs M3 étendu.

Par exemple, vous pouvez utiliser la couleur "surface" avec la couleur "on-surface-variant" et la couleur "surface-variant" avec "on-surface" pour ajouter différents niveaux d'accentuation.

Vous pouvez également utiliser les variantes de couleur de la surface avec les couleurs d'accentuation pour réduire leur intensité par rapport aux couleurs "on-accent", tout en conservant l'accessibilité et en respectant le rapport de contraste.

Rôles de couleur de la surface, de l'arrière-plan et de la variante de la surface.

Rôles de couleur de la surface, de l'arrière-plan et de la variante de la surface.

  1. Utilisez différentes épaisseurs de police pour le texte. Comme vous l'avons vu dans la section "Typographie", vous pouvez ajouter des épaisseurs personnalisées à votre échelle de types afin d'ajouter différents niveaux d'accentuation.

Modifiez ensuite ReplyEmailListItem.kt pour accentuer la différence de mise en valeur à l'aide de la variante de la surface. Le contenu de la carte reprend la couleur par défaut en fonction de l'arrière-plan.

Utilisez onSurfaceVariant pour la couleur du composable du texte temporel et du corps du texte. L'accentuation est alors réduite par rapport à onContainerColors, qui est appliqué par défaut aux composables du texte de l'objet et du titre.

2c9b7f2bd016edb8.png 6850ff391f21e4ba.png

Texte temporel et corps du texte avec la même accentuation que l'objet et le titre (à gauche).

Texte temporel et corps du texte avec une accentuation moins importante que celle de l'objet et du titre (à droite).

ReplyEmailListItem.kt

Text(
   text = email.createdAt,
   style = MaterialTheme.typography.labelMedium,
   color = MaterialTheme.colorScheme.onSurfaceVariant
)

Text(
   text = email.body,
   maxLines = 2,
   style = MaterialTheme.typography.bodyLarge,
   color = MaterialTheme.colorScheme.onSurfaceVariant,
   overflow = TextOverflow.Ellipsis
)

Pour la carte correspond aux e-mails importants avec l'arrière-plan secondaryContainer, onSecondaryContainer est utilisé par défaut pour toutes les couleurs du texte. Pour les autres e-mails, surfaceVariant, est utilisé pour l'arrière-plan. onSurfaceVariant est donc utilisé par défaut pour le texte.

9. Félicitations

Félicitations ! Vous avez terminé cet atelier de programmation. Vous avez implémenté la thématisation Material avec Compose à l'aide de couleurs, d'une typographie, de formes et de couleurs dynamiques pour thématiser votre application et proposer une expérience personnalisée.

2d8fcabf15ac5202.png 5a4d31db0185dca6.png ce009e4ce560834d.png

Résultats finaux de la thématisation avec des couleurs dynamiques et un thème de couleurs appliqués.

Étapes suivantes

Consultez les autres ateliers de programmation du parcours Compose :

Complément d'informations

Applications exemples

  • Application exemple Reply avec une thématisation Material 3 complète
  • JetChat présentant une thématisation dynamique

Documents de référence