Rendre les applications adaptables et accessibles avec Jetpack Compose

1. Présentation

Dans cet atelier de programmation, vous allez apprendre à créer des applications adaptatives pour les téléphones, les tablettes et les pliables, et à préserver l'accessibilité grâce à Jetpack Compose. Vous découvrirez également les bonnes pratiques concernant l'utilisation des composants Material 3 et la gestion des thèmes.

Avant de nous lancer, il est important de comprendre ce que nous entendons par "adaptabilité et accessibilité".

Ajustement

L'UI de votre application doit s'adapter à différentes tailles d'écran, orientations et facteurs de forme. La mise en page adaptative change en fonction de l'espace disponible à l'écran. Ces modifications vont des ajustements de mise en page simples pour remplir l'espace, en choisissant des styles de navigation respectifs, et des modifications complètes de la mise en page pour utiliser l'espace supplémentaire.

Accessibilité

Les applications Android doivent être utilisables par tous, y compris par les personnes ayant des besoins en matière d'accessibilité. Les applications doivent s'adapter à différents scénarios pour offrir la meilleure expérience utilisateur possible (par exemple, contraste des couleurs, joignabilité, etc.).

Dans cet atelier de programmation, vous allez découvrir comment utiliser l'adaptabilité et l'accessibilité lorsque vous utilisez Jetpack Compose. Vous allez créer une application appelée RÉPONDRE, qui montre comment implémenter l'adaptabilité pour tous les types d'écrans. Vous verrez comment l'adaptabilité et l'accessibilité fonctionnent ensemble pour offrir aux utilisateurs une expérience optimale.

Points abordés

  • Concevoir votre application pour cibler toutes les tailles d'écran avec Jetpack Compose
  • Comment cibler votre application pour différents pliables.
  • Utiliser différents types de navigation pour améliorer la joignabilité et l'accessibilité
  • Concevoir des palettes de couleurs et une thématisation dynamique pour le Material 3 pour une expérience d'accessibilité optimale
  • Utiliser les composants Material 3 pour offrir une expérience optimale en fonction de la taille de l'écran

Prérequis

  • Android Studio Bumblebee.
  • Connaissances de Kotlin.
  • Connaissances de base de la rédaction de messages (comme l'annotation @Composable).
  • Des connaissances de base sur les mises en page "Nouveau message" (par exemple, Row et Column).
  • Une bonne connaissance des modificateurs (par exemple, Modifier.padding).

Si vous ne connaissez pas l'atelier de programmation Compose, envisagez de suivre cet atelier de programmation.

Objectifs de l'atelier

  • Une application cliente Reply avec envoi d'e-mails interactive basée sur les bonnes pratiques pour Material 3, la thématisation dynamique et la conception modulable.

Présentation de plusieurs appareils compatibles que vous allez effectuer dans cet atelier de programmation

2. Configuration

Pour télécharger l'application exemple, vous pouvez :

Télécharger le fichier ZIP

Vous pouvez également cloner le dépôt GitHub à partir de la ligne de commande à l'aide de la commande suivante:

git clone https://github.com/googlecodelabs/android-compose-codelabs.git
cd android-compose-codelabs/ReplyAdaptabilityCodelab

Vous pouvez exécuter l'un ou l'autre module dans Android Studio à tout moment en modifiant la configuration de l'exécution dans la barre d'outils.

b059413b0cf9113a.png

Ouvrir le projet dans Android Studio

  1. Dans la fenêtre "Welcome to Android Studio" (Bienvenue sur Android Studio), sélectionnez c01826594f360d94.png Open an existing Project (Ouvrir un projet existant).
  2. Sélectionnez le dossier [Download Location]/ReplyAdaptabilityCodelab (veillez à sélectionner le répertoire ReplyAdaptabilityCodlab contenant build.gradle).
  3. Une fois qu'Android Studio a importé le projet, vérifiez que vous pouvez exécuter les modules start et finished.

Explorer le code de départ

Le code de départ contient quatre packages :

  • MainActivity : activité de point d'entrée à partir de laquelle vous démarrez l'application ReplyApp. Vous allez modifier ce fichier.
  • ui : contient les thèmes, les composants et l'application ReplyApp permettant de démarrer l'interface utilisateur de rédaction. Vous allez apporter des modifications dans ce package.
  • util : contient le code d'aide du projet. Vous n'aurez pas besoin de modifier ce package.

Cet atelier de programmation est axé sur les fichiers du package reply. Le module start contient plusieurs fichiers avec lesquels vous devez vous familiariser.

Fichiers que vous modifierez dans ui package

  • MainActivity.kt : activité Android, qui constitue le point de départ du démarrage de l'application ReplyApp et la transmission des informations nécessaires (comme l'état de pliage, la taille et la mise en page).
  • ReplyApp.kt : la structure principale de l'interface de l'application se trouve dans le fichier ReplyApp.kt sur lequel vous allez travailler.
  • ReplyAppContent.kt : l'implémentation de la section "Nouveau message" et le contenu de l'appli se trouvent ici.

Commençons par MainActivity.kt : pour le module start, le code doit déjà fonctionner dans votre activité.

MainActicity.kt

override fun onCreate(savedInstanceState: Bundle?) {
   super.onCreate(savedInstanceState)

   setContent {
       ReplyTheme {
           val uiState = viewModel.uiState.collectAsState().value
           ReplyApp(uiState)
       }
   }
}

Si vous exécutez cette application sur n'importe quelle taille d'appareil, vous devriez voir une même étendue d'écran pour remplir la zone maximale sans modifier les éléments de l'interface utilisateur. Configuration initiale de ReplyApp sans aucune modification.

Essayons d'optimiser l'espace sur l'écran afin d'améliorer l'expérience utilisateur tout en restant au cœur de son accessibilité.

3. Adaptez vos applications à vos besoins

Cette section présente ce qu'est l'adaptation d'applications et les composants que Material 3 met à votre disposition pour nous faciliter la tâche.

Nous aborderons aussi les types d'écrans et les États que vous ciblerez, y compris les téléphones, les tablettes, les grandes tablettes et les pliables.

Gérer les tailles de fenêtre

Avant de passer à l'application Reply, découvrons quels types d'appareils et de tailles sont disponibles sur le marché pour permettre aux utilisateurs d'utiliser nos applications.

Nous proposons des téléphones mobiles de 10 cm à 18 cm. Nous avons également des tablettes, qui vont des tablettes plus petites aux tablettes de la taille d'un grand nombre d'ordinateurs portables.

Commençons par diviser ces différentes tailles en trois catégories en fonction de WIndowSizeClass. Les catégories ont été choisies spécifiquement pour équilibrer la simplicité de la mise en page et l'optimisation de votre application dans des cas uniques. La classe de taille des fenêtres est toujours déterminée par l'espace d'écran disponible pour l'application, qui peut ne pas correspondre à l'intégralité de l'écran physique pour réaliser plusieurs tâches en même temps ou d'autres segmentations.

Répartition de la taille de l'appareil en fonction de WindowSizeClass

WindowStateUtils**.kt**

enum class WindowSize { COMPACT, MEDIUM, EXPANDED }

fun getWindowSizeClass(windowDpSize: DpSize): WindowSize = when {
   windowDpSize.width < 0.dp -> throw IllegalArgumentException("Dp value cannot be negative")
   windowDpSize.width < 600.dp -> WindowSize.COMPACT
   windowDpSize.width < 840.dp -> WindowSize.MEDIUM
   else -> WindowSize.EXPANDED
}

WindowStateUtils.kt fournit rememberWindowSizeClass(),, ce qui nous permet d'obtenir un état "Mémorisé". Ainsi, chaque fois que la configuration change, notre arborescence de l'interface utilisateur s'affiche à nouveau en fonction de la nouvelle taille.

WindowStateUtils.kt

fun Activity.rememberWindowSizeClass(): WindowSize {
   // Get the size (in pixels) of the window
   val windowSize = rememberWindowSize()

   // Convert the window size to [Dp]
   val windowDpSize = with(LocalDensity.current) {
       windowSize.toDpSize()
   }

   // Calculate the window size class
   return getWindowSizeClass(windowDpSize)
}

Pour commencer à accepter les tailles adaptatives, il vous suffit d'ajouter rememberWindowSizeClass() au début de l'interface utilisateur de "Nouveau message" et de le transmettre à ReplyApp. Vous pouvez maintenant apporter des modifications à MainActivity.kt de sorte qu'elle ressemble à ceci.

MainActivity.kt

setContent {
   ReplyTheme(dynamicColor = false, darkTheme = false) {
       val windowSize = rememberWindowSizeClass()
       ReplyApp(windowSize, uiState)
   }
}

Ces modifications vous permettent de constater que l'application ReplyApp contient des informations sur la dernière taille de la fenêtre pour utiliser correctement l'espace.

4. Gérer les états de pliage

Vous devez également vous assurer que votre application réagit aux changements d'état de la ligne de flottaison, et pas seulement à la taille de l'écran. Il peut y avoir plusieurs états de repli, mais commencez par cibler certains cas. Ils sont déjà définis dans la classe "util".

WindowStateUtils.kt

/**
* Information about the posture of the device
*/
sealed interface DevicePosture {
   object NormalPosture : DevicePosture

   data class TableTopPosture(
       val hingePosition: Rect
   ) : DevicePosture

   data class BookPosture(
       val hingePosition: Rect
   ) : DevicePosture
}

Assurez-vous que votre interface utilisateur réagit lorsque vous passez d'une position pliée à une position non pliée. Vous devez aussi tenir compte de PostPosture et de TopTopPosture avec la position de charnière, car vous ne souhaitez pas afficher de texte ni d'autres informations utiles à la charnière.

Passons en revue la position de flottaison dans notre cycle de vie d'activité. Ajoutez ce code dans la méthode onCreate() de l'activité avant d'appeler setContent().

override fun onCreate(savedInstanceState: Bundle?) {
   super.onCreate(savedInstanceState)

    /* Flow of [DevicePosture] that emits every time there is a change in the windowLayoutInfo
    */
   val devicePostureFlow =  WindowInfoTracker.getOrCreate(this).windowLayoutInfo(this)
       .flowWithLifecycle(this.lifecycle)
       .map { layoutInfo ->
           val foldingFeature =
               layoutInfo.displayFeatures.filterIsInstance<FoldingFeature>().firstOrNull()
           when {
               isTableTopPosture(foldingFeature) ->
                   DevicePosture.TableTopPosture(foldingFeature.bounds)
               isBookPosture(foldingFeature) ->
                   DevicePosture.BookPosture(foldingFeature.bounds)
               isSeparating(foldingFeature) ->
                   DevicePosture.Separating(foldingFeature.bounds, foldingFeature.orientation)
               else -> DevicePosture.NormalPosture
           }
       }
       .stateIn(
           scope = lifecycleScope,
           started = SharingStarted.Eagerly,
           initialValue = DevicePosture.NormalPosture
       )

Vous pouvez maintenant observer le flux de position de l'appareil en tant qu'état "Nouveau message", ce qui permet à notre interface utilisateur de réagir aux changements d'état de la ligne de flottaison. Ajoutez ces modifications à setContent().

MainActivity.kt

setContent {
   ReplyTheme(dynamicColor = false, darkTheme = false) {
       val devicePosture = devicePostureFlow.collectAsState().value
       ReplyApp(windowSize, devicePosture, uiState)
   }
}

L'interface utilisateur de rédaction d'un message est désormais prête à réagir aux changements de taille de l'appareil et de repli de l'état. Vous pouvez poursuivre la conception de votre interface utilisateur pour différents États. Pour chaque modification de l'état de la ligne de flottaison, nous voulons que l'interface utilisateur réagisse comme suit.

Adaptation d'interface utilisateur pliable

5. Navigation dynamique

Dans la section précédente, vous avez fait en sorte que l'UI réagisse aux changements d'état de taille, de configuration et de pliage. Vous devez désormais comprendre comment adapter votre interaction utilisateur à des appareils lorsqu'ils traversent différents états.

Commençons par la navigation, car il s'agit de la première chose avec laquelle les utilisateurs vont interagir. Notez que les utilisateurs disposent de types d'appareils différents. Intéressons-nous à quelques éléments de navigation Material.

Navigation inférieure

La barre de navigation inférieure est idéale pour les formats compacts, car nous pouvons tenir naturellement l'appareil à l'endroit où notre pouce peut atteindre les points de contact. Vous pouvez l'utiliser lorsque votre appareil est plié ou compact.

Pour les appareils de taille moyenne ou la plupart des téléphones mobiles en mode paysage, la barre de navigation est idéale pour naviguer et être joignables facilement, car notre pouce se trouve naturellement en haut à gauche de l'appareil. Vous pouvez également afficher des informations supplémentaires dans le panneau de navigation, ainsi que dans le rail de navigation.

Le panneau de navigation permet d'afficher facilement des informations détaillées sur les onglets de navigation et est facilement accessible quand vous utilisez une tablette ou un appareil plus grand. Vous pouvez utiliser le panneau de navigation avec la barre de navigation inférieure et le panneau de navigation permanente pour les appareils très larges.

Nous allons maintenant passer d'un type de navigation à l'autre à mesure que l'état et la taille de l'appareil changent, tout en restant centrés sur l'interaction et l'accessibilité des utilisateurs.

Ajoutez la navigation dynamique à l'application. Ouvrez ReplyApp.kt et ajoutez cette information dans le composable ReplyApp.

ReplyApp.kt

/**
* This will help us select type of navigation depending on window size and
* fold state of the device.
*/
val navigationType: ReplyNavigationType

when (windowSize) {
   WindowSize.COMPACT -> {
       navigationType = ReplyNavigationType.BOTTOM_NAVIGATION
   }
   WindowSize.MEDIUM -> {
       navigationType = ReplyNavigationType.NAVIGATION_RAIL
   }
   WindowSize.EXPANDED -> {
       navigationType = if (foldingDevicePosture is DevicePosture.BookPosture) {
           ReplyNavigationType.NAVIGATION_RAIL
       } else {
           ReplyNavigationType.PERMANENT_NAVIGATION_DRAWER
       }
   }
}

Comme le panneau de navigation agit comme l'interface utilisateur du conteneur pour ReplyAppContent,, vous pouvez l'encapsuler avec un panneau de navigation permanent ou modal en fonction de notre navigationType, comme dans cet exemple. , le

ReplyApp.kt

if (navigationType == ReplyNavigationType.PERMANENT_NAVIGATION_DRAWER) {
   PermanentNavigationDrawer(drawerContent = {                    NavigationDrawerContent(selectedDestination) }) {
       ReplyAppContent(navigationType, contentType, replyHomeUIState)
   }
} else {
   ModalNavigationDrawer(
       drawerContent = {
           NavigationDrawerContent(
               selectedDestination,
               onDrawerClicked = {
                   scope.launch {
                       drawerState.close()
                   }
               }
           )
       },
       drawerState = drawerState
   ) {
       ReplyAppContent(navigationType, contentType, replyHomeUIState,
           onDrawerClicked = {
               scope.launch {
                   drawerState.open()
               }
           }
       )
   }
}

Vous disposez maintenant d'un paramètre NavigationType dynamique qui permet de modifier la navigation en cas de modification de la configuration. Ajoutons navigationType à ReplyAppContent() pour que la navigation soit dynamique.

ReplyApp.kt

@Composable
fun ReplyAppContent(
   navigationType: ReplyNavigationType,
   contentType: ReplyContentType,
   replyHomeUIState: ReplyHomeUIState,
   onDrawerClicked: () -> Unit = {}
) {
   Row(modifier = Modifier.fillMaxSize()) {
       AnimatedVisibility(visible = navigationType == ReplyNavigationType.NAVIGATION_RAIL) {
           ReplyNavigationRail(
               onDrawerClicked = onDrawerClicked
           )
       }
       Column(modifier = Modifier
           .fillMaxSize()
           .background(MaterialTheme.colorScheme.inverseOnSurface)
       ) {
           // Reply List content

           AnimatedVisibility(visible = navigationType == ReplyNavigationType.BOTTOM_NAVIGATION) {
               ReplyBottomNavigationBar()
           }
       }
   }
}

Exécuter à nouveau l'application pour essayer la navigation dynamique

Lorsque vous exécutez à nouveau l'application, vous constatez que chaque fois que la configuration de l'écran change ou que vous pliez un appareil pliant, le type de navigation correspondant à cette taille est modifié.

Affichage des modifications d'adaptabilité pour différentes tailles d'appareils.

Félicitations, vous avez appris les différents types de navigations qui peuvent s'adapter à différentes tailles et états d'écran.

Dans la section suivante, vous allez apprendre à tirer avantage de n'importe quelle zone d'écran restante au lieu d'étirer le même élément d'élément de liste en bord.

6. Utilisation de l'espace d'écran

Dans l'application, vous pouvez voir que l'écran est étiré pour remplir l'espace restant, que ce soit une petite tablette, un appareil déplié ou une grande tablette. Vous voulez faire profiter cet espace de l'écran pour afficher plus d'informations aux utilisateurs.

Comme pour navigationType, vous allez créer un contentType qui nous permettra de choisir entre un contenu de liste et d'afficher à la fois une liste et des détails détaillés sur les modifications d'état de l'écran

ReplyApp.kt

val contentType: ReplyContentType
when (windowSize) {
   WindowSize.COMPACT -> {
       contentType = ReplyContentType.LIST_ONLY
   }
   WindowSize.MEDIUM -> {
       contentType = if (foldingDevicePosture != DevicePosture.NormalPosture) {
           ReplyContentType.LIST_AND_DETAIL
       } else {
           ReplyContentType.LIST_ONLY
       }
   }
   WindowSize.EXPANDED -> {
       contentType = ReplyContentType.LIST_AND_DETAIL
   }
}

Vous pouvez maintenant transmettre ce type de contenu à ReplyAppContent,. S'il modifie la configuration, il s'adapte à votre mise en page. Vous pouvez également tenir compte de la position de la ligne de flottaison et de la position des charnières pour décider de la position de la liste et de la présentation des détails afin d'éviter d'avoir du contenu à la charnière.

ReplyApp.kt

@Composable
fun ReplyAppContent(
   navigationType: ReplyNavigationType,
   contentType: ReplyContentType,
   replyHomeUIState: ReplyHomeUIState,
   onDrawerClicked: () -> Unit = {}
) {
   Row(modifier = Modifier.fillMaxSize()) {
       Column(modifier = Modifier
           .fillMaxSize()
           .background(MaterialTheme.colorScheme.inverseOnSurface)
       ) {
           if (contentType == ReplyContentType.LIST_AND_DETAIL) {
               ReplyListAndDetailContent(
                   replyHomeUIState = replyHomeUIState,
                   modifier = Modifier.weight(1f),
               )
           } else {
               ReplyListOnlyContent(replyHomeUIState = replyHomeUIState, modifier = Modifier.weight(1f))
           }
       }
   }
}

Vue finale de ReplyApp après avoir ajouté toutes les modifications

Exécuter à nouveau l'application pour essayer l'application entièrement flexible

Exécutez de nouveau l'application et notez que, lorsque la configuration de l'écran change ou que nous déplions un appareil pliable, le contenu de navigation et d'écran change de façon dynamique en fonction de l'état de l'appareil. Avec Jetpack Compose, les modifications sont déclaratives très facilement.

Félicitations, votre application a été adaptée à tous les types d'appareils et de tailles. Essayez de lancer l'application dans des pliables, des tablettes ou d'autres appareils mobiles.

Dans les prochaines sections, vous découvrirez comment ces modifications de l'adaptabilité nous aident également à définir la structure de l'accessibilité.

7. Améliorer l'accessibilité

joignabilité

La joignabilité est la possibilité de parcourir ou d'utiliser un appareil sans nécessiter de position extrême ni de modifier la position des mains pour déclencher une interaction avec une application.

Dans l'application Reply, dans la section "Navigation dynamique", vous avez ajouté plusieurs modes de navigation à utiliser selon l'état de l'écran. Les composants Material Design, comme la barre de navigation inférieure, le rail de navigation et le panneau de navigation, permettent d'accéder facilement à la navigation en fonction de la manière dont les appareils sont fixés par différents facteurs de forme.

Démonstration de la joignabilité montrant le rail de navigation et le panneau de navigation pour différentes tailles de tablettes.

Nous avons également ajouté une liste et un facteur de forme qui permettent aux utilisateurs de passer facilement d'un fil à un autre et de faire défiler ces derniers avec les mains gauche et droite sans modifier l'emplacement.

Contraste des couleurs

L'application Reply est compatible avec les thèmes dynamiques pour Android 12 et versions ultérieures, où le jeu de couleurs est généré via la sélection de fond d'écran et d'autres paramètres de personnalisation. Les produits utilisant des couleurs dynamiques répondent aux exigences d'accessibilité, car les combinaisons algorithmiques qu'un utilisateur final peut vivre sont conçues pour respecter ces normes.

Pour en savoir plus, consultez Couleurs dynamiques.

Jeu de couleurs de Material 3 pour le mode clair et sombre

Pour cette application, nous utilisons également un jeu de couleurs Material 3 déjà conçu pour répondre aux normes d'accessibilité pour le contraste des couleurs. Le système de palettes de tons est essentiel pour que l'ensemble des couleurs soit accessible par défaut.

Démonstration de contraste des couleurs avec la thématisation du matériau 3

Combiner des couleurs basées sur la tonalité, plutôt que sur la valeur hexadécimale ou la teinte, est l'un des systèmes clés qui rendent toute sortie de couleur accessible. Vous pouvez toujours créer l'ensemble de couleurs de Material 3 en choisissant l'ensemble de couleurs principal, secondaire et tertiaire, puis utilisez l'outil de création de thèmes Material 3 pour créer des couleurs sombres et sombres. La variante générée respecte déjà les normes d'accessibilité pour le contraste des couleurs.

Sur Android 11 et versions antérieures, où le thème dynamique n'est pas disponible, nous revenons à un jeu de couleurs fixe Material 3 généré avec l'outil de création de thèmes Material.

Vous pouvez tester de nouveaux thèmes de couleur en utilisant l'outil de création de thèmes Material Design.

Vous pouvez indiquer la couleur générée directement dans le fichier ui/theme/Color.kt pour la voir fonctionner.

8. Félicitations

Félicitations, vous avez terminé cet atelier de programmation et appris à concevoir des applications accessibles et adaptables à l'aide de Jetpack Compose.

Vous avez appris à vérifier la taille et le pli de l'appareil, et à mettre à jour l'interface utilisateur, la navigation et d'autres fonctions de votre application. Vous avez également appris à exploiter le jeu de couleurs et la typographie Material 3 pour améliorer l'expérience utilisateur et l'accessibilité.

Étape suivante

Découvrez les autres ateliers de programmation dans le parcours de rédaction.

Exemples d'applications

  • Les exemples d'applications regroupent de nombreuses applications qui intègrent les bonnes pratiques expliquées dans ces ateliers de programmation.

Documents de référence