Paramètres par défaut de l'API

Les API Material, Compose UI et Foundation implémentent et proposent de nombreuses pratiques accessibles par défaut. Ils contiennent une sémantique intégrée qui suit leur rôle et leur fonction spécifiques, ce qui signifie que la plupart des fonctionnalités d'accessibilité sont fournies avec peu ou pas de travail supplémentaire.

L'utilisation des API appropriées à des fins appropriées signifie généralement que les composants sont fournis avec des comportements d'accessibilité prédéfinis qui couvrent les cas d'utilisation standards. N'oubliez pas de vérifier que ces valeurs par défaut répondent à vos besoins d'accessibilité. Sinon, Compose offre également des moyens de répondre à des exigences plus spécifiques.

Connaître les sémantiques et les modèles d'accessibilité par défaut des API Compose permet de comprendre comment les utiliser en tenant compte de l'accessibilité, ainsi que de prendre en charge l'accessibilité dans des composants plus personnalisés.

Tailles minimales des 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, la case à cocher inclut une marge intérieure dont la largeur et la hauteur sont d'au moins 48 dp.

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

Case à cocher avec la marge intérieure par défaut, d'une largeur et d'une hauteur de 48 dp.
Figure 1. Case à cocher avec marge par défaut.

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
private fun NonClickableCheckbox() {
    Checkbox(checked = true, onCheckedChange = null)
}

Case à cocher sans marge intérieure.
Figure 2. Case à cocher sans marge intérieure.

Lorsque vous implémentez 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 en définissant le rappel de clic sur le composable sur null, puis en ajoutant un modificateur 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)
        }
    }
}

Case à cocher à côté du texte "Option" qui est sélectionnée et désélectionnée.
Figure 3. Case à cocher avec comportement cliquable.

Lorsque la taille d'un composable cliquable est inférieure à la taille minimale de la cible tactile, Compose augmente quand même les dimensions de cette zone. Pour ce faire, il étend la taille de la cible tactile au-delà des limites du composable.

L'exemple suivant contient une toute petite Box cliquable. La superficie de la 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 quand même 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)
        )
    }
}

Très petite zone cliquable qui s'agrandit pour devenir une cible tactile plus grande lorsque vous appuyez à côté de la zone.
Figure 4. Très petite zone cliquable qui s'étend pour devenir une cible tactile plus grande.

Pour éviter tout chevauchement potentiel entre les zones tactiles des différents composables, utilisez toujours une taille minimale assez grande pour le composable. Dans l'exemple, cela implique d'utiliser le modificateur sizeIn pour définir la taille minimale du champ intérieur:

@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)
        )
    }
}

La très petite zone de l'exemple précédent est agrandie pour créer une cible tactile plus grande.
Figure 5 : Cible tactile de forme rectangulaire plus grande.

Éléments graphiques

Lorsque vous définissez un composable Image ou Icon, il n'existe pas de moyen automatique pour que le framework Android comprenne ce que l'application affiche. Vous devez transmettre une description textuelle de l'élément graphique.

Imaginez un écran sur lequel l'utilisateur peut partager la page active avec ses amis. Cet écran contient une icône de partage cliquable :

Bandeau de quatre icônes cliquables, avec l'icône de partage en surbrillance.
Figure 6 : Ligne d'icônes cliquables, avec l'icône "Partager" sélectionnée.

S'il ne dispose que de cette seule icône, le framework Android ne peut pas 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 décrit un élément graphique. Utilisez une chaîne localisée, car 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 graphiques 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
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)
    )
}

contentDescription est principalement destiné aux éléments graphiques, tels que les images. Les composants Material, tels que Button ou Text, et les comportements exploitables, tels que clickable ou toggleable, sont fournis avec d'autres sémantiques prédéfinies qui décrivent leur comportement intrinsèque et peuvent être modifiées via d'autres API Compose.

Éléments interactifs

Les API Material et Foundation Compose créent des éléments d'interface utilisateur avec lesquels les utilisateurs peuvent interagir via les API de modification clickable et toggleable. Étant donné que les composants interactifs peuvent se composer de plusieurs éléments, clickable et toggleable fusionnent les sémantiques de leurs enfants par défaut, de sorte que le composant soit traité comme une entité logique.

Par exemple, un Button Material peut consister en une icône enfant et du texte. Au lieu de traiter les enfants comme des individus, un bouton Material fusionne les sémantiques de ses enfants par défaut, afin que les services d'accessibilité puissent les regrouper en conséquence:

Boutons avec sémantique d'enfants non fusionnés et fusionnés.
Figure 7 : Boutons avec sémantique d'enfants non fusionnés et fusionnés.

De même, l'utilisation du modificateur clickable entraîne également la fusion de la sémantique de ses descendants dans une seule entité, qui est envoyée aux services d'accessibilité avec une représentation d'action correspondante:

Row(
    // Uses `mergeDescendants = true` under the hood
    modifier = Modifier.clickable { openArticle() }
) {
    Icon(
        painter = painterResource(R.drawable.ic_logo),
        contentDescription = "Open",
    )
    Text("Accessibility in Compose")
}

Vous pouvez également définir un onClickLabel spécifique sur le composant parent cliquable pour fournir des informations supplémentaires aux services d'accessibilité et offrir une représentation plus soignée de l'action:

Row(
    modifier = Modifier
        .clickable(onClickLabel = "Open this article") {
            openArticle()
        }
) {
    Icon(
        painter = painterResource(R.drawable.ic_logo),
        contentDescription = "Open"
    )
    Text("Accessibility in Compose")
}

Prenons l'exemple de TalkBack. Ce modificateur clickable et son libellé de clic permettraient à TalkBack de fournir un indice d'action "Appuyez deux fois pour ouvrir cet article", plutôt que le retour par défaut plus générique "Appuyez deux fois pour activer".

Ces commentaires varient en fonction du type d'action. Un clic prolongé fournit une astuce TalkBack "Appuyez deux fois et maintenez pour", suivie d'un libellé:

Row(
    modifier = Modifier
        .combinedClickable(
            onLongClickLabel = "Bookmark this article",
            onLongClick = { addToBookmarks() },
            onClickLabel = "Open this article",
            onClick = { openArticle() },
        )
) {}

Dans certains cas, vous n'avez pas accès direct au modificateur clickable (par exemple, lorsqu'il est défini dans une couche imbriquée inférieure),mais vous souhaitez tout de même modifier le libellé de l'annonce par défaut. Pour ce faire, séparez la définition de clickable de la modification de l'annonce à l'aide du modificateur semantics et définissez le libellé de clic à cet endroit pour modifier la représentation de l'action:

@Composable
private fun ArticleList(openArticle: () -> Unit) {
    NestedArticleListItem(
        // Clickable is set separately, in a nested layer:
        onClickAction = openArticle,
        // Semantics are set here:
        modifier = Modifier.semantics {
            onClick(
                label = "Open this article",
                action = {
                    // Not needed here: openArticle()
                    true
                }
            )
        }
    )
}

Dans ce cas, vous n'avez pas besoin de transmettre l'action de clic deux fois, car les API Compose existantes, comme clickable ou Button, s'en chargent. En effet, la logique de fusion garantit que le libellé et l'action de modification les plus externes sont appliqués aux informations présentes.

Dans l'exemple précédent, l'action de clic openArticle() est transmise automatiquement par le NestedArticleListItem à ses sémantiques clickable et peut être laissée nulle dans la deuxième action de modification des sémantiques. Toutefois, le libellé de clic est extrait du deuxième modificateur de sémantique onClick(label = "Open this article"), car il n'était pas présent dans le premier.

Vous pouvez rencontrer des scénarios où vous vous attendez à ce que les sémantiques enfants soient fusionnées dans une sémantique parente, mais cela ne se produit pas. Pour en savoir plus, consultez la section Fusionner et effacer.

Composants personnalisés

Pour les composants personnalisés, en règle générale, examinez l'implémentation d'un composant similaire dans la bibliothèque Material ou d'autres bibliothèques Compose, et imitez ou modifiez son comportement d'accessibilité lorsque cela est approprié.

Par exemple, si vous remplacez la Checkbox Material par votre propre implémentation, l'examen de l'implémentation de la case à cocher existante vous rappellera d'ajouter le modificateur triStateToggleable, qui gère les propriétés d'accessibilité de ce composant.

Utilisez également de manière intensive les modificateurs de base, car ils incluent des éléments d'accessibilité prêts à l'emploi, ainsi que les pratiques Compose existantes abordées dans cette section.

Vous trouverez également un exemple de composant de bouton d'activation/de désactivation personnalisé dans la section Section "Effacer et définir la sémantique", ainsi que des informations plus détaillées sur l'accessibilité dans les composants personnalisés dans les Consignes relatives à l'API.