Étapes clés pour améliorer l'accessibilité de Compose

Pour aider les personnes ayant des besoins d'accessibilité à utiliser votre application avec succès, concevez votre pour répondre aux principales exigences d'accessibilité.

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éfinissez une taille minimale de 48 dp pour respecter les consignes consignes d'accessibilité.

Les composants Material, tels que Checkbox, RadioButton, Switch, Slider et Surface : définissez cette taille minimale en interne, mais seulement lorsque le composant peut recevoir des actions de l'utilisateur. Par exemple, lorsqu'un Checkbox a son paramètre onCheckedChange défini sur une valeur non nulle, la case à cocher inclut avec une largeur et une hauteur d'au moins 48 dp.

@Composable
private fun CheckableCheckbox() {
    Checkbox(checked = true, onCheckedChange = {})
}

Lorsque le paramètre onCheckedChange est défini sur "null", la marge intérieure n'est pas car il n'est pas possible d'interagir directement avec le composant.

@Composable
private fun NonClickableCheckbox() {
    Checkbox(checked = true, onCheckedChange = null)
}

Figure 1 Case à cocher sans marge intérieure.

Lorsque vous implémentez des contrôles de sélection tels que Switch, RadioButton ou Checkbox, vous transférez généralement le comportement cliquable vers un conteneur parent, défini le rappel de clic sur le composable à null, et ajoutez un toggleable ou selectable au composable parent.

@Composable
private 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, elle développe la taille de la zone cible tactile en dehors des limites du composable.

L'exemple suivant contient un élément Box cliquable de très petite taille. La cible tactile est automatiquement étendue au-delà des limites du Box. à côté de Box déclenche toujours l'événement de clic.

@Composable
private fun SmallBox() {
    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 éventuel entre les zones tactiles de différents composables, utilisez une taille minimale suffisamment grande pour le composable. Dans l'exemple, il s'agirait Moyenne de l'utilisation du modificateur sizeIn pour définir la taille minimale de la zone intérieure:

@Composable
private fun LargeBox() {
    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 libellés de clic décrivent ce qui se passe lorsque l'utilisateur interagit avec composable. Les services d'accessibilité utilisent des libellés de clic pour décrire l'application les utilisateurs ayant des besoins spécifiques.

Définissez le libellé de clic en transmettant un paramètre dans le modificateur clickable:

@Composable
private 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, L'étiquette de clic dans le modificateur sémantique:

@Composable
private 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, moyen automatique pour le framework Android de comprendre l'application. s'affiche. 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 :

Bande d'icônes cliquables, avec les icônes

En se basant uniquement sur l'icône, le framework Android ne peut pas la décrire utilisateur altéré. Le framework Android a besoin d'une description textuelle supplémentaire l'icône.

Le paramètre contentDescription décrit un élément visuel. Utilisez une version localisée telle qu'elle est visible par l'utilisateur.

@Composable
private 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 voulez peut-être pas communiquer à l'utilisateur. Lorsque vous définissez le paramètre contentDescription sur null, vous indique au framework Android que cet élément n'est associé à aucune les actions ou l'état.

@Composable
private fun PostImage(post: Post, modifier: Modifier = Modifier) {
    val image = post.imageThumb ?: painterResource(R.drawable.placeholder_1_1)

    Image(
        painter = 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 transmet des informations qui l'utilisateur devra effectuer sa tâche. Si ce n'est pas le cas, mieux vaut quitter la 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. Lorsque chaque composable de bas niveau sur votre écran est concentrés de manière indépendante, les utilisateurs doivent interagir beaucoup pour se déplacer sur l'écran. Si les éléments fusionnent de manière trop agressive, les utilisateurs pourraient ne pas comprendre lesquels les éléments s'unissent

Lorsque vous appliquez un modificateur clickable à un composable, Compose fusionne automatiquement tous les éléments que contient le composable. Cela vaut également pour ListItem; les éléments d'un même élément de liste fusionnent et l'accessibilité services 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. L'accessibilité doit toujours être au rendez-vous pour les afficher comme un seul élément. Par exemple, imaginez un composable qui affiche l'avatar de l'utilisateur, son nom et d'autres informations:

Groupe d'éléments d'interface utilisateur comprenant un nom d'utilisateur. Le nom est sélectionné.

Vous pouvez autoriser Compose à fusionner ces éléments à l'aide de l'mergeDescendants dans le modificateur semantics. De cette façon, les services d'accessibilité sélectionner uniquement l'élément fusionné et toutes les propriétés sémantiques des descendants sont 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 leur contenu:

Groupe d'éléments d'interface utilisateur comprenant un nom d'utilisateur. Tous les éléments sont sélectionnés ensemble.

Ajouter des actions personnalisées

Examinez l'élément de liste suivant :

Élément de liste typique reprenant le titre de l'article, l'auteur et l'icône des favoris.

Lorsque vous utilisez un lecteur d'écran tel que TalkBack pour entendre ce qui est affiché sur le il sélectionne d'abord l'intégralité de l'élément, puis l'icône des favoris.

L'élément de liste, avec tous les éléments sélectionnés ensemble.

L'élément de liste, avec seulement l'icône de favori sélectionnée

Dans une longue liste, cela peut devenir très répétitif. Une meilleure approche consiste à Définir une action personnalisée qui permet à un utilisateur d'ajouter l'élément à ses favoris À retenir vous devez également supprimer explicitement le comportement de l'icône de favori pour s'assurer qu'il n'est pas sélectionné par le service d'accessibilité. Ce se fait avec le modificateur clearAndSetSemantics:

@Composable
private 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, Le framework Android utilise pour lire l'état dans lequel se trouve le composable. Pour Par exemple, un composable à activer/désactiver peut se trouver dans un emplacement "coché" ou une case « décochée » de l'état. Dans certains cas, vous pouvez remplacer la description de l'état par défaut étiquettes utilisées par Compose. Pour ce faire, spécifiez explicitement l'état des étiquettes de description 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 affichent 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 :

Capture d'écran d'un article de blog, avec le texte de l'article dans un conteneur à faire défiler.

Les utilisateurs ayant des besoins en termes d'accessibilité ont des difficultés à naviguer sur un tel écran. Pour vous aider la navigation, indiquer quels éléments sont des en-têtes. Dans l'exemple précédent, chaque le titre de la sous-section peut être défini comme un titre pour l'accessibilité. Un peu les services d'accessibilité, comme TalkBack, permettent aux utilisateurs de naviguer directement vers un titre.

Dans Compose, vous indiquez qu'un composable est un titre en définissant son Propriété semantics:

@Composable
private fun Subsection(text: String) {
    Text(
        text = text,
        style = MaterialTheme.typography.headlineSmall,
        modifier = Modifier.semantics { heading() }
    )
}

Gérer les composables personnalisés

Chaque fois que vous remplacez certains composants Material de votre application par des versions, vous devez garder à l'esprit les considérations d'accessibilité.

Supposons que vous remplaciez Material Checkbox par votre propre implémentation. Vous pouvez 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'implémentation du composant dans la bibliothèque Material et imiter n'importe quel 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.

Testez l'implémentation de vos composants personnalisés avec plusieurs des services d'accessibilité pour vérifier son comportement.

Ressources supplémentaires