Accessibilité dans Jetpack Compose

1. Introduction

Dans cet atelier de programmation, vous apprendrez à utiliser Jetpack Compose pour améliorer l'accessibilité de votre application. Vous découvrirez, étape par étape, plusieurs cas d'utilisation courants. Nous aborderons les tailles des zones cibles tactiles, les descriptions de contenu, les libellés de clic, etc.

Les personnes malvoyantes, daltoniennes, malentendantes ou présentant des troubles de la dextérité, cognitifs ou toute autre forme de handicap utilisent des appareils Android pour effectuer des tâches quotidiennes. Lorsque vous développez des applications en gardant l'accessibilité à l'esprit, vous améliorez l'expérience utilisateur pour ces personnes, mais aussi pour celles ayant d'autres besoins.

Au cours de cet atelier de programmation, nous utiliserons TalkBack pour tester manuellement les modifications du code. TalkBack est un service d'accessibilité principalement utilisé par les déficients visuels. Assurez-vous de tester également les modifications apportées à votre code auprès d'autres services d'accessibilité, tels que Switch Access.

Le rectangle TalkBack se déplace sur l'écran d'accueil de Jetnews, tandis que le texte que TalkBack énonce est affiché en bas de l'écran

TalkBack en action dans l'application Jetnews

Points abordés

Cet atelier de programmation traite des points suivants :

  • Répondre aux besoins des utilisateurs souffrant d'un handicap en augmentant la taille des zones cibles tactiles
  • Décrire les propriétés sémantiques et expliquer comment les modifier
  • Fournir des informations aux composables pour améliorer leur accessibilité

Ce dont vous avez besoin

Objectifs de l'atelier

Dans cet atelier de programmation, nous allons améliorer l'accessibilité d'une application de lecture d'actualités. Nous commencerons avec une application qui ne dispose pas de certaines fonctionnalités d'accessibilité essentielles et nous appliquerons ce que nous avons appris pour la rendre plus accessible aux personnes ayant des besoins spécifiques.

2. Configuration

Au cours de cette étape, vous téléchargerez le code requis, qui comprend une application de lecture d'actualités simple.

Ce dont vous avez besoin

Obtenir le code

Le code de cet atelier de programmation est disponible dans le dépôt GitHub android-compose-codelabs. 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 dépôt que vous venez de télécharger contient du code pour tous les ateliers de programmation traitant de Compose. Pour cet atelier, ouvrez le projet AccessibilityCodelab 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.

Configurer TalkBack

Au cours de cet atelier de programmation, nous utiliserons TalkBack pour vérifier nos modifications. Si vous utilisez un appareil physique pour effectuer les tests, suivez ces instructions afin d'activer TalkBack. TalkBack n'est pas installé par défaut avec les émulateurs. Choisissez un émulateur qui inclut le Play Store, puis téléchargez les outils d'accessibilité Android.

3. Taille des cibles tactiles

Tous les éléments affichés sur lesquels on peut cliquer ou appuyer, ou avec lesquels il est possible d'interagir, doivent être assez grands pour permettre des interactions fiables. Assurez-vous que ces éléments font au moins 48 dp de large et de haut.

Si ces commandes sont dimensionnées de façon dynamique ou redimensionnées en fonction de la taille de leur contenu, vous pouvez utiliser le modificateur sizeIn pour définir la limite inférieure de leurs dimensions.

Certains composants Material définissent ces tailles automatiquement. Par exemple, le paramètre MinHeight du composable Button est défini sur 36 dp et utilise une marge verticale de 8 dp, ce qui correspond à la hauteur requise de 48 dp.

Lorsque nous ouvrons l'application exemple et que nous exécutons TalkBack, nous pouvons voir que la zone cible tactile de l'icône en forme de croix figurant sur les fiches d'article est très petite. Elle devrait être d'au moins 48 dp.

Voici une capture d'écran montrant l'application d'origine (à gauche) et une version améliorée (à droite).

Comparaison d'un élément de liste avec une icône en forme de croix de petite taille à gauche et une grande icône à droite.

Examinons l'implémentation et vérifions la taille de ce composable. Ouvrez PostCards.kt et recherchez le composable PostCardHistory. Comme vous pouvez le constater, l'implémentation définit une taille de 24 dp pour l'icône du menu à développer :

@Composable
fun PostCardHistory(post: Post, navigateToArticle: (String) -> Unit) {
   // ...

   Row(
       // ...
   ) {
       // ...
       CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.medium) {
           Icon(
               imageVector = Icons.Default.Close,
               contentDescription = stringResource(R.string.cd_show_fewer),
               modifier = Modifier
                   .clickable { openDialog = true }
                   .size(24.dp)
           )
       }
   }
   // ...
}

Pour augmenter la taille de la zone cible tactile de cet élément Icon, nous pouvons ajouter une marge intérieure :

@Composable
fun PostCardHistory(post: Post, navigateToArticle: (String) -> Unit) {
   // ...
   Row(
       // ...
   ) {
       // ...
       CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.medium) {
           Icon(
               imageVector = Icons.Default.Close,
               contentDescription = stringResource(R.string.cd_show_fewer),
               modifier = Modifier
                   .clickable { openDialog = true }
                   .padding(12.dp)
                   .size(24.dp)
           )
       }
   }
   // ...
}

Dans notre cas d'utilisation, il existe un moyen plus simple de s'assurer que la zone cible tactile est d'au moins 48 dp. En effet, le composant Material IconButton gère cela pour nous :

@Composable
fun PostCardHistory(post: Post, navigateToArticle: (String) -> Unit) {
   // ...
   Row(
       // ...
   ) {
       // ...
       CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.medium) {
           IconButton(onClick = { openDialog = true }) {
               Icon(
                   imageVector = Icons.Default.Close,
                   contentDescription = stringResource(R.string.cd_show_fewer)
               )
           }
       }
   }
   // ...
}

Désormais, si vous parcourez l'écran avec TalkBack, une zone cible tactile de 48 dp s'affiche. En outre, IconButton ajoute une ondulation, qui indique à l'utilisateur que cet élément est cliquable.

4. Libellés de clic

Par défaut, les éléments cliquables dans votre application ne fournissent aucune information sur les effets d'un clic sur ces éléments. Par conséquent, les services d'accessibilité tels que TalkBack utilisent une description très générique par défaut.

Pour offrir une expérience optimale aux utilisateurs ayant des besoins d'accessibilité spécifiques, nous pouvons fournir une description précise qui explique ce qui se passe lorsqu'un utilisateur clique sur cet élément.

Dans l'application Jetnews, les utilisateurs peuvent cliquer sur les différentes fiches pour lire l'article complet. Par défaut, cette action entraîne la lecture du contenu de l'élément cliquable, qui se trouve après le texte "Appuyer deux fois pour activer le contenu". Nous aimerions être plus précis et remplacer ce texte par "Appuyer deux fois pour lire l'article". Voici la version originale par rapport à la version souhaitée :

Deux enregistrements d'écran pour lesquels TalkBack est activé. Il est possible d'appuyer sur un article dans une liste verticale et sur un article dans un carrousel horizontal.

Modifier le libellé de clic d'un composable : avant (à gauche) et après (à droite)

Le modificateur clickable inclut un paramètre qui vous permet de définir directement ce libellé de clic.

Examinons de nouveau l'implémentation de PostCardHistory :

@Composable
fun PostCardHistory(
   // ...
) {
   Row(
       Modifier.clickable { navigateToArticle(post.id) }
   ) {
       // ...
   }
}

Comme vous pouvez le voir, cette implémentation utilise le modificateur clickable. Pour définir un libellé de clic, nous pouvons définir le paramètre onClickLabel :

@Composable
fun PostCardHistory(
   // ...
) {
   Row(
       Modifier.clickable(
               // R.string.action_read_article = "read article"
               onClickLabel = stringResource(R.string.action_read_article)
           ) {
               navigateToArticle(post.id)
           }
   ) {
       // ...
   }
}

TalkBack énonce désormais correctement "Appuyer deux fois pour lire l'article".

Les autres fiches d'article publiées sur l'écran d'accueil portent le même libellé générique. Examinons l'implémentation du composable PostCardPopular et mettons à jour son libellé de clic :

@Composable
fun PostCardPopular(
   // ...
) {
   Card(
       shape = MaterialTheme.shapes.medium,
       modifier = modifier.size(280.dp, 240.dp),
       onClick = { navigateToArticle(post.id) }
   ) {
       // ...
   }
}

Ce composable utilise le composable Card en interne, ce qui ne vous permet pas de définir directement le libellé de clic. À la place, vous pouvez utiliser le modificateur semantics pour définir ce libellé :

@OptIn(ExperimentalMaterialApi::class)
@Composable
fun PostCardPopular(
   post: Post,
   navigateToArticle: (String) -> Unit,
   modifier: Modifier = Modifier
) {
   val readArticleLabel = stringResource(id = R.string.action_read_article)
   Card(
       shape = MaterialTheme.shapes.medium,
       modifier = modifier
          .size(280.dp, 240.dp)
          .semantics { onClick(label = readArticleLabel, action = null) },
       onClick = { navigateToArticle(post.id) }
   ) {
       // ...
   }
}

5. Actions personnalisées

De nombreuses applications affichent une liste, où chaque élément contient une ou plusieurs actions. Sur un lecteur d'écran, il peut s'avérer fastidieux de parcourir une telle liste, car la même action serait sélectionnée encore et encore.

À la place, nous pouvons ajouter des actions d'accessibilité personnalisées à un composable. De cette façon, les actions associées au même élément de la liste peuvent être regroupées.

Dans l'application Jetnews, nous affichons une liste d'articles que l'utilisateur peut lire. Chaque élément de la liste inclut une action permettant d'indiquer que l'utilisateur souhaite voir moins d'articles sur ce sujet. Dans cette section, nous allons remplacer cette action par une action d'accessibilité personnalisée afin de faciliter la navigation dans la liste.

Sur la gauche, vous voyez la situation par défaut où chaque icône en forme de croix est sélectionnable. Sur la droite, vous voyez la solution, où l'action est incluse dans les actions personnalisées de TalkBack :

Deux enregistrements d'écran sur lesquels TalkBack est activé. L'écran de gauche montre comment l'icône en forme de croix est sélectionnable au niveau de chaque article. Appuyez deux fois pour ouvrir une boîte de dialogue. L'écran de droite ouvre un menu d'actions personnalisé en appuyant trois fois. Appuyez sur l'action "Afficher moins d'articles de ce type" pour ouvrir la même boîte de dialogue.

Ajouter une action personnalisée à un article : avant (à gauche) et après (à droite)

Ouvrez PostCards.kt et examinez l'implémentation du composable PostCardHistory. Notez les propriétés cliquables de Row et de IconButton, qui utilisent Modifier.clickable et onClick :

@Composable
fun PostCardHistory(post: Post, navigateToArticle: (String) -> Unit) {
   // ...
   Row(
       Modifier.clickable(
           onClickLabel = stringResource(R.string.action_read_article)
       ) {
           navigateToArticle(post.id)
       }
   ) {
       // ...
       CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.medium) {
           IconButton(onClick = { openDialog = true }) {
               Icon(
                   imageVector = Icons.Default.Close,
                   contentDescription = stringResource(R.string.cd_show_fewer)
               )
           }
       }
   }
   // ...
}

Par défaut, les composables Row et IconButton sont cliquables et sont donc rendus sélectionnables par TalkBack. Ce comportement se répète pour chaque élément de la liste. Vous devez donc balayer l'écran beaucoup lorsque vous parcourez la liste. Nous voulons que l'action liée à IconButton soit incluse en tant qu'action personnalisée au niveau de l'élément de la liste. Pour indiquer aux services d'accessibilité de ne pas interagir avec cet élément Icon, utilisez le modificateur clearAndSetSemantics :

@Composable
fun PostCardHistory(post: Post, navigateToArticle: (String) -> Unit) {
   // ...
   Row(
       Modifier.clickable(
           onClickLabel = stringResource(R.string.action_read_article)
       ) {
           navigateToArticle(post.id)
       }
   ) {
       // ...
       CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.medium) {
            IconButton(
                modifier = Modifier.clearAndSetSemantics { },
                onClick = { openDialog = true }
            ) {
                Icon(
                    imageVector = Icons.Default.Close,
                    contentDescription = stringResource(R.string.cd_show_fewer)
                )
            }
       }
   }
   // ...
}

Cependant, en supprimant la sémantique de IconButton, il n'est plus du tout possible d'exécuter l'action. À la place, nous pouvons ajouter l'action à l'élément de la liste en insérant une action personnalisée dans le modificateur semantics :

@Composable
fun PostCardHistory(post: Post, navigateToArticle: (String) -> Unit) {
   // ...
   val showFewerLabel = stringResource(R.string.cd_show_fewer)
   Row(
        Modifier
            .clickable(
                onClickLabel = stringResource(R.string.action_read_article)
            ) {
                navigateToArticle(post.id)
            }
            .semantics {
                customActions = listOf(
                    CustomAccessibilityAction(
                        label = showFewerLabel,
                        // action returns boolean to indicate success
                        action = { openDialog = true; true }
                    )
                )
            }
   ) {
       // ...
       CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.medium) {
            IconButton(
                modifier = Modifier.clearAndSetSemantics { },
                onClick = { openDialog = true }
            ) {
                Icon(
                    imageVector = Icons.Default.Close,
                    contentDescription = showFewerLabel
                )
            }
       }
   }
   // ...
}

Nous pouvons désormais utiliser le pop-up d'action personnalisée dans TalkBack pour appliquer l'action. Cette pratique est particulièrement pertinente lorsque le nombre d'actions dans un élément de liste augmente.

6. Description des éléments visuels

Tous les utilisateurs de votre application ne peuvent pas voir ni interpréter les éléments visuels qui s'y affichent, comme les icônes et les illustrations. De plus, les services d'accessibilité n'ont aucun moyen d'interpréter les éléments visuels en se basant uniquement sur leurs pixels. En tant que développeur, vous devez donc leur transmettre plus d'informations sur les éléments visuels de votre application.

Les composables visuels comme Image et Icon incluent un paramètre contentDescription. C'est dans ce paramètre que vous transmettez une description localisée de cet élément visuel, ou la valeur null si l'élément est purement décoratif.

Dans notre application, il manque des descriptions de contenu sur l'écran de l'article. Exécutez l'application et sélectionnez l'article du haut pour accéder à l'écran correspondant.

Deux enregistrements d'écran sur lesquels TalkBack est activé. Il est possible d'appuyer sur bouton "Retour" sur l'écran de l'article. L'écran de gauche indique "Bouton — Appuyer deux fois pour activer". L'écran de droite indique "Remonter — Appuyer deux fois pour activer".

Ajouter une description du contenu visuel : avant (à gauche) et après (à droite)

Lorsque nous ne fournissons aucune information, l'icône de navigation dans la partie supérieure gauche annonce simplement "Bouton, appuyez deux fois pour l'activer". L'utilisateur ne sait pas quelle action aura lieu lorsqu'il cliquera sur ce bouton. Ouvrez ArticleScreen.kt :

@Composable
fun ArticleScreen(
   // ...
) {
   // ...
   Scaffold(
       topBar = {
           InsetAwareTopAppBar(
               title = {
                   // ...
               },
               navigationIcon = {
                   IconButton(onClick = onBack) {
                       Icon(
                           imageVector = Icons.Filled.ArrowBack,
                           contentDescription = null
                       )
                   }
               }
           )
       }
   ) {
       // ...
   }
}

Ajoutez une description de contenu pertinente à l'icône :

@Composable
fun ArticleScreen(
   // ...
) {
   // ...
   Scaffold(
       topBar = {
           InsetAwareTopAppBar(
               title = {
                   // ...
               },
               navigationIcon = {
                   IconButton(onClick = onBack) {
                       Icon(
                           imageVector = Icons.Filled.ArrowBack,
                           contentDescription = stringResource(
                               R.string.cd_navigate_up
                           )
                       )
                   }
               }
           )
       }
   ) {
       // ...
   }
}

L'image d'en-tête est un autre élément visuel de cet article. Dans notre cas, cette image est purement décorative et ne montre rien que nous devions communiquer à l'utilisateur. Par conséquent, la description du contenu est définie sur null, et l'élément est ignoré lorsque nous utilisons un service d'accessibilité.

Le dernier élément visuel à l'écran est la photo de profil. Dans ce cas, nous utilisons un avatar générique. Il n'est donc pas nécessaire d'ajouter une description de contenu ici. Si nous utilisions la photo de profil réelle de cet auteur, nous pourrions lui demander de fournir une description de contenu adaptée.

7. Titres

Lorsqu'un écran contient beaucoup de texte, comme l'écran de notre article, il est assez difficile pour les utilisateurs malvoyants de trouver rapidement la section qu'ils recherchent. Pour ce faire, vous pouvez indiquer les parties du texte qui correspondent à des titres. Les utilisateurs peuvent alors parcourir rapidement les différents titres en balayant l'écran vers le haut ou vers le bas.

Par défaut, aucun composable n'est marqué comme titre. Dès lors, aucune navigation n'est possible. Nous aimerions que l'écran de l'article affiche la navigation par titre :

Deux enregistrements d'écran sur lesquels TalkBack est activé. Il est possible de balayer l'écran vers le bas pour parcourir les titres. Le message "Il n'y a plus aucun titre" s'affiche à gauche. L'écran de droite fait défiler les titres et lit chacun d'eux à voix haute.

Ajouter des titres : avant (à gauche) et après (à droite)

Les titres de notre article sont définis dans PostContent.kt. Ouvrons ce fichier et faisons-le défiler jusqu'au composable Paragraph :

@Composable
private fun Paragraph(paragraph: Paragraph) {
   // ...
   Box(modifier = Modifier.padding(bottom = trailingPadding)) {
       when (paragraph.type) {
           // ...
           ParagraphType.Header -> {
               Text(
                   modifier = Modifier.padding(4.dp),
                   text = annotatedString,
                   style = textStyle.merge(paragraphStyle)
               )
           }
           // ...
       }
   }
}

Ici, l'élément Header est défini comme un composable Text simple. Nous pouvons définir la propriété sémantique heading pour indiquer que ce composable est un titre.

@Composable
private fun Paragraph(paragraph: Paragraph) {
   // ...
   Box(modifier = Modifier.padding(bottom = trailingPadding)) {
       when (paragraph.type) {
           // ...
           ParagraphType.Header -> {
               Text(
                   modifier = Modifier.padding(4.dp)
                     .semantics { heading() },
                   text = annotatedString,
                   style = textStyle.merge(paragraphStyle)
               )
           }
           // ...
       }
   }
}

8. Fusion personnalisée

Comme nous l'avons vu aux étapes précédentes, les services d'accessibilité tels que TalkBack parcourent un écran élément par élément. Par défaut, chaque composable de bas niveau dans Jetpack Compose qui définit au moins une propriété sémantique est sélectionnable. Par exemple, un composable Text définit la propriété sémantique text et devient donc sélectionnable.

Toutefois, un nombre excessif d'éléments sélectionnables peut prêter à confusion, car l'utilisateur doit les parcourir un par un. À la place, vous pouvez fusionner des composables à l'aide du modificateur semantics avec sa propriété mergeDescendants.

Consultons l'écran de notre article. Le niveau de sélectionnabilité de la plupart des éléments est correct. Toutefois, les métadonnées de l'article sont actuellement lues à voix haute en tant qu'éléments distincts. Pour améliorer l'expérience utilisateur, vous pouvez les fusionner en une seule entité sélectionnable :

Deux enregistrements d'écran sur lesquels TalkBack est activé. L'écran de gauche affiche des rectangles TalkBack distincts pour les champs "Auteur" et "Métadonnées". L'écran de droite affiche un rectangle autour des deux champs et lit le contenu concaténé.

Fusionner des composables : avant (à gauche) et après (à droite)

Ouvrez PostContent.kt et vérifiez le composable PostMetadata :

@Composable
private fun PostMetadata(metadata: Metadata) {
   // ...
   Row {
       Image(
           // ...
       )
       Spacer(Modifier.width(8.dp))
       Column {
           Text(
               // ...
           )

           CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.medium) {
               Text(
                   // ..
               )
           }
       }
   }
}

Nous pouvons demander à la ligne de premier niveau de fusionner ses descendants, ce qui entraînera le comportement souhaité :

@Composable
private fun PostMetadata(metadata: Metadata) {
   // ...
   Row(Modifier.semantics(mergeDescendants = true) {}) {
       Image(
           // ...
       )
       Spacer(Modifier.width(8.dp))
       Column {
           Text(
               // ...
           )

           CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.medium) {
               Text(
                   // ..
               )
           }
       }
   }
}

9. Boutons bascules et cases à cocher

L'état des éléments à activer/désactiver, comme Switch et Checkbox, est lu à voix haute à mesure qu'ils sont sélectionnés par TalkBack. Sans contexte, il peut toutefois être difficile de comprendre ce à quoi ces éléments font référence. Pour ajouter du contexte à ce type d'élément basculant, vous pouvez agrandir la zone d'activation et de désactivation. De la sorte, l'utilisateur peut activer ou désactiver Switch ou Checkbox en appuyant sur le composable lui-même ou sur son libellé.

Un exemple est illustré dans l'écran "Centres d'intérêt". Pour y accéder, ouvrez le panneau de navigation à partir de l'écran d'accueil. L'écran "Centres d'intérêt" présente la liste des sujets auxquels un utilisateur peut s'abonner. Par défaut, les cases de cet écran sont sélectionnables en dehors de leur libellé, ce qui ne facilite pas leur compréhension. Nous préférerions que l'ensemble de la ligne (Row) soit activable :

Deux enregistrements d'écran sur lesquels TalkBack est activé, avec affichage de l'écran "Centres d'intérêt" et une liste de sujets sélectionnables. Sur l'écran de gauche, TalkBack sélectionne séparément chaque case à cocher. Sur l'écran de droite, TalkBack sélectionne la ligne entière.

Utiliser des cases à cocher : avant (à gauche) et après (à droite)

Ouvrez InterestsScreen.kt et examinez l'implémentation du composable TopicItem :

@Composable
private fun TopicItem(itemTitle: String, selected: Boolean, onToggle: () -> Unit) {
   // ...
   Row(
       modifier = Modifier
           .padding(horizontal = 16.dp, vertical = 8.dp)
   ) {
       // ...
       Checkbox(
           checked = selected,
           onCheckedChange = { onToggle() },
           modifier = Modifier.align(Alignment.CenterVertically)
       )
   }
}

Comme vous pouvez le voir ici, Checkbox possède un rappel onCheckedChange qui gère l'activation et la désactivation de l'élément. Nous pouvons agrandir ce rappel pour qu'il s'applique à toute la ligne (Row) :

@Composable
private fun TopicItem(itemTitle: String, selected: Boolean, onToggle: () -> Unit) {
   // ...
   Row(
       modifier = Modifier
           .toggleable(
               value = selected,
               onValueChange = { _ -> onToggle() },
               role = Role.Checkbox
           )
           .padding(horizontal = 16.dp, vertical = 8.dp)
   ) {
       // ...
       Checkbox(
           checked = selected,
           onCheckedChange = null,
           modifier = Modifier.align(Alignment.CenterVertically)
       )
   }
}

10. Description des états

À l'étape précédente, nous avons agrandi la zone d'activation et de désactivation d'un élément Checkbox pour qu'elle s'applique à la ligne parent (Row). Pour améliorer encore l'accessibilité de cet élément, vous pouvez ajouter une description personnalisée afin de préciser l'état du composable.

Par défaut, l'état d'une case (Checkbox) est lu comme "Coché" ou "Non coché". Nous pouvons remplacer cette description par notre propre description personnalisée :

Deux enregistrements d'écran sur lesquels TalkBack est activé ; un sujet est sélectionné sur l'écran "Centres d'intérêt". L'écran de gauche indique "Non coché", tandis que l'écran de droite indique "Non abonné".

Ajouter des descriptions d'état : avant (à gauche) et après (à droite)

Poursuivons avec le composable TopicItem que nous avons adapté à la dernière étape :

@Composable
private fun TopicItem(itemTitle: String, selected: Boolean, onToggle: () -> Unit) {
   // ...
   Row(
       modifier = Modifier
           .toggleable(
               value = selected,
               onValueChange = { _ -> onToggle() },
               role = Role.Checkbox
           )
           .padding(horizontal = 16.dp, vertical = 8.dp)
   ) {
       // ...
       Checkbox(
           checked = selected,
           onCheckedChange = null,
           modifier = Modifier.align(Alignment.CenterVertically)
       )
   }
}

Nous pouvons ajouter nos descriptions d'état personnalisées à l'aide de la propriété stateDescription dans le modificateur semantics :

@Composable
private fun TopicItem(itemTitle: String, selected: Boolean, onToggle: () -> Unit) {
   // ...
   val stateNotSubscribed = stringResource(R.string.state_not_subscribed)
   val stateSubscribed = stringResource(R.string.state_subscribed)
   Row(
       modifier = Modifier
           .semantics {
               stateDescription = if (selected) {
                   stateSubscribed
               } else {
                   stateNotSubscribed
               }
           }
           .toggleable(
               value = selected,
               onValueChange = { _ -> onToggle() },
               role = Role.Checkbox
           )
           .padding(horizontal = 16.dp, vertical = 8.dp)
   ) {
       // ...
       Checkbox(
           checked = selected,
           onCheckedChange = null,
           modifier = Modifier.align(Alignment.CenterVertically)
       )
   }
}

11. Félicitations !

Félicitations, vous avez terminé cet atelier de programmation au cours duquel vous avez découvert l'accessibilité dans Compose. Vous vous êtes familiarisé avec les zones cibles tactiles, les descriptions d'éléments visuels et les descriptions d'état. Vous avez ajouté des libellés de clic, des titres et des actions personnalisées. Vous savez comment effectuer une fusion personnalisée, et comment utiliser les boutons bascules et les cases à cocher. En appliquant ces enseignements à vos applications, vous améliorerez considérablement leur accessibilité.

Consultez les autres ateliers de programmation du parcours Compose, ainsi que d'autres exemples de code, dont celui de Jetnews.

Documentation

Pour en savoir plus et obtenir des conseils à ce sujet, consultez la documentation suivante :