Contrôler l'ordre de balayage

Par défaut, le comportement du lecteur d'écran d'accessibilité dans une application Compose est implémenté dans l'ordre de lecture attendu, généralement de gauche à droite, puis de haut en bas. Toutefois, dans certains types de mises en page d'application, l'algorithme ne peut pas déterminer l'ordre de lecture réel sans indications supplémentaires. Dans les applications basées sur les vues, vous pouvez résoudre ces problèmes à l'aide des propriétés traversalBefore et traversalAfter. À partir de Compose 1.5, Compose fournit une API tout aussi flexible, mais avec un nouveau modèle conceptuel.

isTraversalGroup et traversalIndex sont des propriétés sémantiques qui vous permettent de contrôler l'accessibilité et l'ordre de sélection de TalkBack dans les cas où l'algorithme de tri par défaut n'est pas approprié. isTraversalGroup identifie les groupes sémantiquement importants, tandis que traversalIndex ajuste l'ordre des éléments individuels au sein de ces groupes. Vous pouvez utiliser isTraversalGroup seul ou avec traversalIndex pour une personnalisation plus avancée.

Utilisez isTraversalGroup et traversalIndex dans votre application pour contrôler l'ordre de balayage du lecteur d'écran.

Regrouper des éléments avec isTraversalGroup

isTraversalGroup est une propriété booléenne qui définit si un nœud sémantique est un groupe de balayage. Ce type de nœud a pour fonction de servir de limite ou de frontière pour organiser les enfants du nœud.

Si vous définissez isTraversalGroup = true sur un nœud, tous les enfants de ce nœud sont visités avant de passer à d'autres éléments. Vous pouvez définir isTraversalGroup sur les nœuds sélectionnables non compatibles avec les lecteurs d'écran, tels que les colonnes, les lignes ou les cases.

L'exemple suivant utilise isTraversalGroup. Il émet quatre éléments de texte. Les deux éléments de gauche appartiennent à un seul élément CardBox, tandis que les deux éléments de droite appartiennent à un autre élément CardBox:

// CardBox() function takes in top and bottom sample text.
@Composable
fun CardBox(
    topSampleText: String,
    bottomSampleText: String,
    modifier: Modifier = Modifier
) {
    Box(modifier) {
        Column {
            Text(topSampleText)
            Text(bottomSampleText)
        }
    }
}

@Composable
fun TraversalGroupDemo() {
    val topSampleText1 = "This sentence is in "
    val bottomSampleText1 = "the left column."
    val topSampleText2 = "This sentence is "
    val bottomSampleText2 = "on the right."
    Row {
        CardBox(
            topSampleText1,
            bottomSampleText1
        )
        CardBox(
            topSampleText2,
            bottomSampleText2
        )
    }
}

Le code produit un résultat semblable à celui-ci:

Mise en page avec deux colonnes de texte, la colonne de gauche indiquant "This sentence is in the left column" (Cette phrase est dans la colonne de gauche) et la colonne de droite indiquant "This sentence is on the right" (Cette phrase est à droite).
Figure 1. Mise en page en deux phrases (une dans la colonne de gauche et l'autre dans la colonne de droite)

Comme aucune sémantique n'a été définie, le comportement par défaut du lecteur d'écran consiste à balayer les éléments de gauche à droite et de haut en bas. En raison de cette valeur par défaut, TalkBack lit les fragments de phrases dans le mauvais ordre:

"Cette phrase est en" → "Cette phrase est" → "Colonne de gauche". → « à droite ».

Pour classer correctement les fragments, modifiez l'extrait d'origine en définissant isTraversalGroup sur true:

@Composable
fun TraversalGroupDemo2() {
    val topSampleText1 = "This sentence is in "
    val bottomSampleText1 = "the left column."
    val topSampleText2 = "This sentence is"
    val bottomSampleText2 = "on the right."
    Row {
        CardBox(
//      1,
            topSampleText1,
            bottomSampleText1,
            Modifier.semantics { isTraversalGroup = true }
        )
        CardBox(
//      2,
            topSampleText2,
            bottomSampleText2,
            Modifier.semantics { isTraversalGroup = true }
        )
    }
}

Étant donné que isTraversalGroup est défini spécifiquement sur chaque CardBox, les limites des CardBox s'appliquent lors du tri de leurs éléments. Dans ce cas, l'élément CardBox gauche est lu en premier, suivi de l'élément CardBox droit.

TalkBack lit maintenant les fragments de phrases dans le bon ordre:

"Cette phrase est dans" → "colonne de gauche". → "Cette phrase est" → "à droite".

Personnaliser davantage l'ordre de balayage

traversalIndex est une propriété flottante qui vous permet de personnaliser l'ordre de balayage pour TalkBack. Si le regroupement d'éléments ne suffit pas pour que TalkBack fonctionne correctement, utilisez traversalIndex conjointement avec isTraversalGroup pour personnaliser davantage l'ordre des lecteurs d'écran.

La propriété traversalIndex présente les caractéristiques suivantes:

  • Les éléments avec des valeurs traversalIndex plus faibles sont prioritaires.
  • Elles peuvent être positives ou négatives.
  • La valeur par défaut est 0f.
  • N'affecte que les nœuds sélectionnables du lecteur d'écran, tels que les éléments à l'écran tels que le texte ou les boutons. Par exemple, définir uniquement traversalIndex sur une colonne n'aurait aucun effet, sauf si isTraversalGroup est également défini sur la colonne.

L'exemple suivant montre comment utiliser traversalIndex et isTraversalGroup ensemble.

Exemple: balayage de l'affichage heure et statistiques

L'affichage heure et statistiques est un scénario courant dans lequel l'ordre de balayage standard ne fonctionne pas. L'exemple de cette section est un sélecteur d'heure, dans lequel un utilisateur peut parcourir les chiffres d'un affichage heure et statistiques et sélectionner des chiffres pour les tranches horaires et des minutes.

Affichage heure et statistiques avec un sélecteur de l'heure au-dessus.
Figure 2 : Image d'un affichage heure et statistiques.

L'extrait simplifié suivant contient un CircularLayout dans lequel 12 chiffres sont dessinés, en commençant par 12 et en se déplaçant dans le sens des aiguilles d'une montre autour du cercle:

@Composable
fun ClockFaceDemo() {
    CircularLayout {
        repeat(12) { hour ->
            ClockText(hour)
        }
    }
}

@Composable
private fun ClockText(value: Int) {
    Box(modifier = Modifier) {
        Text((if (value == 0) 12 else value).toString())
    }
}

Comme l'affichage heure et statistiques n'est pas lu de manière logique avec l'ordre de gauche à droite et de haut en bas par défaut, TalkBack lit les chiffres dans le désordre. Pour résoudre ce problème, utilisez la valeur du compteur d'incrémentation, comme indiqué dans l'extrait de code suivant:

@Composable
fun ClockFaceDemo() {
    CircularLayout(Modifier.semantics { isTraversalGroup = true }) {
        repeat(12) { hour ->
            ClockText(hour)
        }
    }
}

@Composable
private fun ClockText(value: Int) {
    Box(modifier = Modifier.semantics { this.traversalIndex = value.toFloat() }) {
        Text((if (value == 0) 12 else value).toString())
    }
}

Pour définir correctement l'ordre des balayages, commencez par définir CircularLayout comme groupe de balayages et définissez isTraversalGroup = true. Ensuite, lorsque le texte d'horloge est dessiné dans la mise en page, définissez son traversalIndex correspondant sur la valeur du compteur.

Comme la valeur du compteur augmente continuellement, la traversalIndex de chaque valeur d'horloge augmente à mesure que des nombres sont ajoutés à l'écran. La valeur d'horloge 0 a une valeur traversalIndex de 0, et la valeur d'horloge 1 a une traversalIndex de 1. Dans ce cas, l'ordre dans lequel TalkBack les lit est défini. Les chiffres contenus dans CircularLayout sont maintenant lus dans l'ordre attendu.

Étant donné que les traversalIndexes définis ne se rapportent qu'aux autres index du même groupe, le reste de l'ordre de l'écran a été conservé. En d'autres termes, les modifications sémantiques indiquées dans l'extrait de code précédent ne modifient que l'ordre dans l'affichage heure et statistiques pour lequel isTraversalGroup = true est défini.

Notez que si la sémantique CircularLayout's n'est pas définie sur isTraversalGroup = true, les modifications de traversalIndex s'appliquent toujours. Cependant, sans CircularLayout pour les lier, les douze chiffres de l'affichage heure et statistiques sont lus en dernier, une fois que tous les autres éléments de l'écran ont été consultés. Cela est dû au fait que tous les autres éléments ont une traversalIndex par défaut de 0f et que les éléments de texte d'horloge sont lus après tous les autres éléments 0f.

Exemple: Personnaliser l'ordre de balayage pour le bouton d'action flottant

Dans cet exemple, traversalIndex et isTraversalGroup contrôlent l'ordre de navigation d'un bouton d'action flottant Material Design. Cet exemple se base sur la mise en page suivante:

Mise en page avec une barre d'application supérieure, un exemple de texte, un bouton d'action flottant et une barre d'application inférieure.
Figure 3. Mise en page avec une barre d'application supérieure, un exemple de texte, un bouton d'action flottant et une barre d'application inférieure.

Par défaut, la mise en page de cet exemple présente l'ordre TalkBack suivant:

Barre d'application supérieure → Exemples de texte de 0 à 6 → bouton d'action flottant (FAB) → Barre d'application inférieure

Vous voudrez peut-être que le lecteur d'écran se concentre d'abord sur le bouton d'action flottant. Pour définir un traversalIndex sur un élément Material tel qu'un bouton d'action flottant:

@Composable
fun FloatingBox() {
    Box(modifier = Modifier.semantics { isTraversalGroup = true; traversalIndex = -1f }) {
        FloatingActionButton(onClick = {}) {
            Icon(imageVector = Icons.Default.Add, contentDescription = "fab icon")
        }
    }
}

Dans cet extrait, la création d'une zone avec isTraversalGroup définie sur true et traversalIndex sur la même zone (-1f est inférieure à la valeur par défaut de 0f) signifie que la boîte flottante est placée avant tous les autres éléments à l'écran.

Vous pouvez ensuite placer la boîte flottante et d'autres éléments dans un échafaudage, qui implémente une mise en page Material Design:

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun ColumnWithFABFirstDemo() {
    Scaffold(
        topBar = { TopAppBar(title = { Text("Top App Bar") }) },
        floatingActionButtonPosition = FabPosition.End,
        floatingActionButton = { FloatingBox() },
        content = { padding -> ContentColumn(padding = padding) },
        bottomBar = { BottomAppBar { Text("Bottom App Bar") } }
    )
}

TalkBack interagit avec les éléments dans l'ordre suivant:

Bouton d'action flottant → Barre d'application supérieure → Exemples de texte 0 à 6 → Barre d'application inférieure

Ressources supplémentaires