Les applications écrites dans Compose doivent prendre en charge l'accessibilité pour les utilisateurs ayant des besoins particuliers. Les services d'accessibilité permettent de transformer ce qui est affiché à l'écran en un format plus adapté pour un utilisateur ayant un besoin spécifique. Pour prendre en charge les services d'accessibilité, les applications utilisent des API dans le framework Android afin d'afficher des informations sémantiques concernant leurs éléments d'interface utilisateur. Le framework Android transmet ensuite ces informations sémantiques aux services d'accessibilité. Chaque service d'accessibilité peut choisir la meilleure description de l'application pour l'utilisateur. Android fournit plusieurs services d'accessibilité, y compris Talkback et Switch Access.
Sémantique
Compose utilise des propriétés sémantiques pour transmettre des informations aux services d'accessibilité. Les propriétés sémantiques fournissent des informations sur les éléments d'interface utilisateur qui sont présentés à l'utilisateur. La plupart des composables intégrés tels que Text
et Button
remplissent ces propriétés sémantiques avec des informations déduites du composable et de ses enfants. Certains modificateurs comme toggleable
et clickable
définissent également certaines propriétés sémantiques. Cependant, le framework a parfois besoin d'informations supplémentaires pour comprendre comment décrire à l'utilisateur un élément d'interface utilisateur.
Ce document décrit différentes situations dans lesquelles vous devez ajouter explicitement des informations supplémentaires à un composable afin qu'il puisse être correctement décrit dans le framework Android. Il explique également comment remplacer complètement les informations sémantiques pour un composable donné. Il requiert des connaissances de base concernant l'accessibilité sous Android.
Cas d'utilisation courants
Afin d'aider les personnes ayant des besoins d'accessibilité à utiliser votre application sans problème, nous vous recommandons de suivre les bonnes pratiques décrites sur cette page.
Définissez une taille minimale pour les zones cibles tactiles
Tout élément à l'écran sur lequel un utilisateur peut cliquer ou appuyer, ou avec lequel il peut interagir, doit être assez grand pour permettre une interaction fiable. Lorsque vous dimensionnez ces éléments, assurez-vous de définir la taille minimale sur 48 dp de manière à respecter les consignes d'accessibilité de Material Design.
Pour les composants Material, comme Checkbox
, RadioButton
, Switch
, Slider
et Surface
, définissez cette taille minimale en interne, mais seulement lorsque l'utilisateur peut agir dessus. Par exemple, lorsque le paramètre onCheckedChange
d'une Checkbox
a une valeur non nulle, il inclut une marge intérieure dont la largeur et la hauteur sont d'au moins 48 dp.
@Composable
fun CheckableCheckbox() {
Checkbox(checked = true, onCheckedChange = {})
}
Lorsque le paramètre onCheckedChange
est défini sur nul, la marge intérieure n'est pas incluse, car il est impossible d'interagir directement avec le composant.
@Composable
fun NonClickableCheckbox() {
Checkbox(checked = true, onCheckedChange = null)
}
Lorsque vous intégrez des commandes de sélection telles que Switch
, RadioButton
ou Checkbox
, vous élevez généralement le comportement cliquable au rang d'un conteneur parent, définissez le rappel de clic sur le composable sur null
, puis ajoutez un modificateur toggleable
ou selectable
au composable parent.
@Composable
fun CheckableRow() {
MaterialTheme {
var checked by remember { mutableStateOf(false) }
Row(
Modifier
.toggleable(
value = checked,
role = Role.Checkbox,
onValueChange = { checked = !checked }
)
.padding(16.dp)
.fillMaxWidth()
) {
Text("Option", Modifier.weight(1f))
Checkbox(checked = checked, onCheckedChange = null)
}
}
}
Lorsque la taille d'un composable cliquable est inférieure à la taille minimale de la zone cible tactile, Compose augmente quand même les dimensions de cette zone. Pour ce faire, il étend la taille de la zone cible tactile au-delà des limites du composable.
Dans l'exemple suivant, nous créons une toute petite Box
cliquable. La superficie de la zone cible tactile est automatiquement étendue au-delà des limites de cette Box
. Par conséquent le fait d'appuyer à côté de Box
déclenche donc quand même l'événement de clic.
@Composable
fun DefaultPreview() {
var clicked by remember { mutableStateOf(false) }
Box(
Modifier
.size(100.dp)
.background(if (clicked) Color.DarkGray else Color.LightGray)
) {
Box(
Modifier
.align(Alignment.Center)
.clickable { clicked = !clicked }
.background(Color.Black)
.size(1.dp)
)
}
}
Pour éviter tout chevauchement potentiel entre les zones tactiles des différents composables, vous devez toujours tâcher d'utiliser une taille minimale assez grande pour le composable. Dans notre exemple, cela implique d'utiliser le modificateur sizeIn
pour définir la taille minimale du champ intérieur :
@Composable
fun DefaultPreview() {
var clicked by remember { mutableStateOf(false) }
Box(
Modifier
.size(100.dp)
.background(if (clicked) Color.DarkGray else Color.LightGray)
) {
Box(
Modifier
.align(Alignment.Center)
.clickable { clicked = !clicked }
.background(Color.Black)
.sizeIn(minWidth = 48.dp, minHeight = 48.dp)
)
}
}
Ajouter des étiquettes de clic
Vous pouvez utiliser une étiquette de clic pour ajouter une signification sémantique au comportement de clic d'un composable. Les étiquettes de clic décrivent ce qui se passe lorsque l'utilisateur interagit avec le composable. Les services d'accessibilité utilisent des étiquettes de clic pour décrire l'application aux utilisateurs ayant des besoins spécifiques.
Définissez l'étiquette de clic en transmettant un paramètre dans le modificateur clickable
:
@Composable
fun ArticleListItem(openArticle: () -> Unit) {
Row(
Modifier.clickable(
// R.string.action_read_article = "read article"
onClickLabel = stringResource(R.string.action_read_article),
onClick = openArticle
)
) {
// ..
}
}
Si vous n'avez pas accès au modificateur cliquable, vous pouvez également définir l'étiquette de clic dans le modificateur sémantique :
@Composable
fun LowLevelClickLabel(openArticle: () -> Boolean) {
// R.string.action_read_article = "read article"
val readArticleLabel = stringResource(R.string.action_read_article)
Canvas(
Modifier.semantics {
onClick(label = readArticleLabel, action = openArticle)
}
) {
// ..
}
}
Décrire les éléments visuels
Lorsque vous définissez un composable Image
ou Icon
, il n'existe pas de moyen automatique pour que le framework Android comprenne ce qui est affiché. Vous devez transmettre une description textuelle de l'élément visuel.
Imaginez un écran sur lequel l'utilisateur peut partager la page active avec ses amis. Cet écran contient une icône de partage cliquable :
S'il ne dispose que de cette seule icône, le framework Android ne peut pas trouver comment la décrire à un utilisateur malvoyant. Le framework Android a besoin d'une description textuelle supplémentaire de l'icône.
Le paramètre contentDescription
permet de décrire un élément visuel. Vous devez utiliser une chaîne localisée, car celle-ci sera communiquée à l'utilisateur.
@Composable
fun ShareButton(onClick: () -> Unit) {
IconButton(onClick = onClick) {
Icon(
imageVector = Icons.Filled.Share,
contentDescription = stringResource(R.string.label_share)
)
}
}
Certains éléments visuels sont purement décoratifs, et vous ne souhaitez peut-être pas les communiquer à l'utilisateur. Lorsque vous définissez le paramètre contentDescription
sur null
, vous indiquez au framework Android que cet élément n'est associé à aucune action ni aucun état.
@Composable
fun PostImage(post: Post, modifier: Modifier = Modifier) {
val image = post.imageThumb ?: imageResource(R.drawable.placeholder_1_1)
Image(
bitmap = image,
// Specify that this image has no semantic meaning
contentDescription = null,
modifier = modifier
.size(40.dp, 40.dp)
.clip(MaterialTheme.shapes.small)
)
}
C'est à vous de décider si un élément visuel spécifique a besoin d'une contentDescription
. Demandez-vous si l'élément donne des informations dont l'utilisateur aura besoin pour effectuer sa tâche. Si ce n'est pas le cas, mieux vaut ne pas fournir de description.
Fusionner des éléments
Les services d'accessibilité tels que TalkBack et Switch Access permettent aux utilisateurs de déplacer le curseur sur les différents éléments à l'écran. Il est important que les éléments soient sélectionnés avec la précision appropriée. Si chaque composable de bas niveau à l'écran est sélectionné indépendamment, l'utilisateur devra beaucoup interagir pour se déplacer dans l'écran. Si les éléments sont fusionnés de manière excessive, les utilisateurs pourraient ne pas comprendre quels éléments vont ensemble.
Lorsque vous appliquez un modificateur clickable
à un composable, Compose fusionne automatiquement tous les éléments qu'il contient. Cela s'applique également à ListItem
. Les éléments d'un élément de liste sont fusionnés et les services d'accessibilité les voient comme un seul élément.
Il se peut qu'un ensemble de composables constitue un groupe logique, mais que ce groupe ne soit pas cliquable ou ne fasse pas partie d'un élément de liste. Vous souhaitez quand même que les services d'accessibilité les considèrent comme un seul élément. Par exemple, imaginez un composable qui affiche l'avatar d'un utilisateur, son nom et d'autres informations supplémentaires :
Vous pouvez demander à Compose de fusionner ces éléments à l'aide du paramètre mergeDescendants
dans le modificateur semantics
. De cette façon, les services d'accessibilité ne sélectionneront que l'élément fusionné, et toutes les propriétés sémantiques des descendants seront fusionnées.
@Composable
private fun PostMetadata(metadata: Metadata) {
// Merge elements below for accessibility purposes
Row(modifier = Modifier.semantics(mergeDescendants = true) {}) {
Image(
imageVector = Icons.Filled.AccountCircle,
contentDescription = null // decorative
)
Column {
Text(metadata.author.name)
Text("${metadata.date} • ${metadata.readTimeMinutes} min read")
}
}
}
Les services d'accessibilité se concentrent désormais sur l'ensemble du conteneur, en fusionnant leur contenu :
Ajouter des actions personnalisées
Examinez l'élément de liste suivant :
Lorsque vous utilisez un lecteur d'écran comme Talkback pour écouter ce qui s'affiche à l'écran, il sélectionne d'abord l'élément entier, puis l'icône des favoris.
Dans une longue liste, cela peut devenir très répétitif. Il est préférable de définir une action personnalisée permettant à un utilisateur d'ajouter l'élément à ses favoris. N'oubliez pas que vous devez aussi supprimer explicitement le comportement de l'icône de favori elle-même pour vous assurer qu'elle ne sera pas sélectionnée par le service d'accessibilité.
Pour ce faire, utilisez le modificateur clearAndSetSemantics
:
@Composable
fun PostCardSimple(
/* ... */
isFavorite: Boolean,
onToggleFavorite: () -> Boolean
) {
val actionLabel = stringResource(
if (isFavorite) R.string.unfavorite else R.string.favorite
)
Row(modifier = Modifier
.clickable(onClick = { /* ... */ })
.semantics {
// Set any explicit semantic properties
customActions = listOf(
CustomAccessibilityAction(actionLabel, onToggleFavorite)
)
}
) {
/* ... */
BookmarkButton(
isBookmarked = isFavorite,
onClick = onToggleFavorite,
// Clear any semantics properties set on this node
modifier = Modifier.clearAndSetSemantics { }
)
}
}
Décrire l'état d'un élément
Un composable peut définir une stateDescription
pour la sémantique utilisée par le framework Android afin de lire l'état dans lequel se trouve le composable. Par exemple, un composable activable peut être à l'état "coché" ou "décoché". Dans certains cas, vous pouvez remplacer les étiquettes de description d'état par défaut utilisées par Compose. Pour ce faire, spécifiez explicitement les étiquettes de description d'état avant de définir un composable comme activable :
@Composable
private fun TopicItem(itemTitle: String, selected: Boolean, onToggle: () -> Unit) {
val stateSubscribed = stringResource(R.string.subscribed)
val stateNotSubscribed = stringResource(R.string.not_subscribed)
Row(
modifier = Modifier
.semantics {
// Set any explicit semantic properties
stateDescription = if(selected) stateSubscribed else stateNotSubscribed
}
.toggleable(
value = selected,
onValueChange = { onToggle() }
)
) {
/* ... */
}
}
Définir des titres
Les applications présentent parfois de nombreux contenus sur un seul écran, dans un conteneur à faire défiler. Par exemple, un écran peut afficher le contenu complet d'un article que l'utilisateur lit :
Les utilisateurs ayant des besoins spécifiques en termes d'accessibilité auront du mal à naviguer sur ce type d'écran. Pour faciliter la navigation, vous pouvez préciser quels sont les titres. Dans l'exemple ci-dessus, chaque titre de sous-section peut être défini comme un titre d'accessibilité. Certains services d'accessibilité, tels que TalkBack, permettent aux utilisateurs de naviguer directement d'un titre à l'autre.
Dans Compose, vous indiquez qu'un composable est un titre en définissant sa propriété sémantique :
@Composable
private fun Subsection(text: String) {
Text(
text = text,
style = MaterialTheme.typography.h5,
modifier = Modifier.semantics { heading() }
)
}
Créer des composables de bas niveau personnalisés
Un cas d'utilisation plus avancé implique le remplacement de certains composants Material de votre application par des versions personnalisées. Dans ce scénario, il est essentiel de garder à l'esprit les éléments d'accessibilité. Supposons que vous remplaciez la Checkbox
Material par votre propre intégration. Il serait très facile d'oublier d'ajouter le modificateur triStateToggleable
, qui gère les propriétés d'accessibilité de ce composant.
En règle générale, examinez l'intégration du composant dans la bibliothèque Material et imitez tout comportement d'accessibilité que vous pouvez trouver. Utilisez également de manière intensive les modificateurs de base plutôt que les modificateurs de niveau de l'interface utilisateur, car ils incluent des éléments d'accessibilité prêts à l'emploi. Assurez-vous de tester l'intégration de votre composant personnalisé avec plusieurs services d'accessibilité pour vérifier son comportement.
En savoir plus
Pour en savoir plus sur la prise en charge de l'accessibilité dans votre code Compose, suivez l'atelier de programmation Accessibilité dans Jetpack Compose.