Une composition décrit l'interface utilisateur (UI) de votre application et est générée par l'exécution des composables. La composition est une arborescence de composables qui décrit votre interface utilisateur.
À côté de la composition se trouve une arborescence parallèle, appelée arborescence sémantique. Cette arborescence décrit votre UI d'une manière alternative compréhensible pour les services d'accessibilité et pour le framework de test. Les services d'accessibilité l'utilisent pour décrire l'application aux utilisateurs ayant des besoins spécifiques. Le framework de test l'utilise pour interagir avec votre application et effectuer des assertions. L'arborescence sémantique ne contient pas d'informations sur la façon de dessiner vos composables, mais sur leur incidence sémantique.
Figure 1 : Hiérarchie classique de l'interface utilisateur et son arborescence sémantique
Si votre application inclut des composables et des modificateurs tirés des bibliothèques Foundation et Material de Compose, l'arborescence sémantique est remplie et générée automatiquement. Cependant, lorsque vous ajoutez des composables personnalisés de bas niveau, vous devez fournir la sémantique manuellement. Votre arborescence pourrait ne pas refléter correctement ou complètement la signification des éléments affichés. Dans ce cas, vous pouvez adapter l'arborescence.
Prenons l'exemple de ce composable d'agenda personnalisé :
Figure 2 : Un composable d'agenda personnalisé avec des éléments "jour" sélectionnables.
Dans cet exemple, l'intégralité de l'agenda est implémentée en tant que composable unique de bas niveau, en utilisant le composable Layout
et en dessinant directement sur Canvas
.
Sans action supplémentaire de votre part, les services d'accessibilité ne recevront pas suffisamment d'informations sur le contenu du composable et la sélection de l'utilisateur dans l'agenda. Par exemple, si un utilisateur clique sur le jour "17", le framework d'accessibilité reçoit uniquement les informations de description pour la commande d'agenda entière. Dans ce cas, le service d'accessibilité TalkBack se contente d'annoncer "Agenda" ou, un peu mieux, "Agenda d'avril", et l'utilisateur se demande quel jour a été sélectionné. Pour rendre ce composable plus accessible, vous devez ajouter manuellement des informations sémantiques.
Propriétés sémantiques
Tous les nœuds de l'arborescence d'UI ayant une incidence sémantique disposent d'un nœud parallèle dans l'arborescence sémantique. Le nœud de l'arborescence sémantique contient les propriétés qui ont la signification du composable correspondant. Par exemple, le composable Text
contient une propriété sémantique text
, car il s'agit de la signification de ce composable. Un Icon
contient une propriété contentDescription
(si elle est définie par le développeur) qui indique dans le texte la signification de Icon
. Des composables et des modificateurs construits sur la bibliothèque de base Compose définissent déjà les propriétés pertinentes pour vous. Vous pouvez également définir ou remplacer les propriétés vous-même à l'aide des modificateurs semantics
et clearAndSetSemantics
. Par exemple, vous pouvez ajouter des actions d'accessibilité personnalisées à un nœud, fournir une autre description d'état pour un élément à activer ou spécifier qu'un certain composable de texte doit être considéré comme un titre.
Pour visualiser l'arborescence sémantique, nous pouvons utiliser l'outil d'inspection de la mise en page ou la méthode printToLog()
dans nos tests. Cette action imprime l'arborescence sémantique actuelle dans Logcat.
class MyComposeTest {
@get:Rule
val composeTestRule = createComposeRule()
@Test
fun MyTest() {
// Start the app
composeTestRule.setContent {
MyTheme {
Text("Hello world!")
}
}
// Log the full semantics tree
composeTestRule.onRoot().printToLog("MY TAG")
}
}
Le résultat est le suivant :
Printing with useUnmergedTree = 'false'
Node #1 at (l=0.0, t=63.0, r=221.0, b=120.0)px
|-Node #2 at (l=0.0, t=63.0, r=221.0, b=120.0)px
Text = '[Hello world!]'
Actions = [GetTextLayoutResult]
Prenons un exemple pour voir comment les propriétés sémantiques sont utilisées pour exprimer la signification d'un composable. Prenons l'exemple d'un Switch
. Voici comment il se présente :
Figure 3 : Un interrupteur à l'état "Activé" ou "Désactivé".
Voici comment vous pouvez décrire la signification de cet élément : "Il s'agit d'un bouton bascule, qui est un élément à activer dont l'état actuel est "Activé". Vous pouvez interagir avec lui en cliquant dessus."
C'est précisément ce à quoi servent les propriétés sémantiques. Le nœud sémantique de cet élément de bouton bascule contient les propriétés suivantes, telles qu'elles sont visualisées avec l'outil d'inspection de la mise en page :
Figure 4 : Layout Inspector affichant les propriétés sémantiques d'un composable "Bouton bascule".
Le Role
indique le type d'élément que nous examinons. StateDescription
décrit comment l'état "activé" doit être référencé. Par défaut, il s'agit simplement d'une version localisée du mot "On", mais vous pouvez lui donner un nom plus spécifique (p. ex. "Activé") selon le contexte. ToggleableState
est l'état actuel du bouton bascule. La propriété OnClick
fait référence à la méthode utilisée pour interagir avec cet élément.
Pour obtenir la liste complète des propriétés sémantiques, consultez l'objet SemanticsProperties
.
Pour obtenir la liste complète des actions d'accessibilité possibles, consultez l'objet SemanticsActions
.
Le suivi des propriétés sémantiques de chaque composable dans votre application offre tout un éventail de possibilités puissantes. Voici quelques exemples :
- Talkback utilise les propriétés pour lire à voix haute ce qui est affiché à l'écran, afin de faciliter l'interaction utilisateur. Pour notre bouton bascule, vous pouvez dire : "On" (activer), "Switch" (basculer), "Double tap to toggle" (double appui pour activer/désactiver). L'utilisateur peut appuyer deux fois sur son écran pour désactiver le bouton.
-
Le framework de test utilise les propriétés pour rechercher des nœuds, interagir avec eux et effectuer des assertions. Voici un exemple de test pour notre bouton bascule :
val mySwitch = SemanticsMatcher.expectValue( SemanticsProperties.Role, Role.Switch ) composeTestRule.onNode(mySwitch) .performClick() .assertIsOff()
Arborescence sémantique fusionnée et non fusionnée
Comme mentionné précédemment, chaque composable de l'arborescence de l'UI peut avoir des propriétés sémantiques définies sur zéro ou plus. Lorsqu'aucune propriété sémantique n'est définie pour un composable, il n'est pas inclus dans l'arborescence sémantique. Ainsi, l'arborescence sémantique ne contient que les nœuds qui ont une incidence sémantique. Cependant, pour indiquer la signification correcte des informations affichées à l'écran, il est parfois utile de fusionner certaines sous-arborescences de nœuds et de les traiter comme un seul et même élément. De cette façon, nous pouvons traiter un ensemble de nœuds comme un tout, au lieu de traiter chaque nœud descendant individuellement. En règle générale, chaque nœud de cette arborescence représente un élément sélectionnable lorsque vous utilisez des services d'accessibilité.
Le bouton est un exemple de composable. Nous aimerions utiliser le bouton comme un seul élément, même s'il peut contenir plusieurs nœuds enfants :
Button(onClick = { /*TODO*/ }) {
Icon(
imageVector = Icons.Filled.Favorite,
contentDescription = null
)
Spacer(Modifier.size(ButtonDefaults.IconSpacing))
Text("Like")
}
Dans notre arborescence sémantique, les propriétés des descendants du bouton sont fusionnées et le bouton est présenté comme un nœud feuille unique dans l'arborescence :
Les composables et les modificateurs peuvent indiquer qu'ils souhaitent fusionner les propriétés sémantiques de leurs descendants en appelant Modifier.semantics
(mergeDescendants = true) {}
. Si vous définissez cette propriété sur true
, vous indiquez que les propriétés sémantiques doivent être fusionnées. Dans notre exemple Button
, le composable Button
utilise le modificateur clickable
en interne qui inclut ce modificateur semantics
. Par conséquent, les nœuds descendants du bouton sont fusionnés. Lisez la documentation sur l'accessibilité pour savoir quand modifier le comportement de fusion dans votre composable.
Plusieurs modificateurs et composables des bibliothèques Foundation et Material Compose disposent de cette propriété. Par exemple, les modificateurs clickable
et toggleable
fusionnent automatiquement leurs descendants. De plus, le composable ListItem
fusionnera ses descendants.
Inspecter l'arborescence
Lorsque nous parlons de l'arborescence sémantique, nous parlons de deux arbres différents. Il existe une arborescence sémantique fusionnée qui fusionne les nœuds descendants lorsque mergeDescendants
est défini sur true
. Il existe également une arborescence sémantique non fusionnée, qui n'applique pas la fusion, mais conserve chaque nœud intact.
Les services d'accessibilité utilisent l'arborescence non fusionnée et appliquent leurs propres algorithmes de fusion, en tenant compte de la propriété mergeDescendants
. Le framework de test utilise l'arborescence fusionnée par défaut.
Vous pouvez inspecter ces deux arborescences avec la méthode printToLog()
. Par défaut, et comme dans les exemples précédents, l'arborescence fusionnée est consignée. Pour imprimer l'arborescence non fusionnée à la place, définissez le paramètre useUnmergedTree
de l'outil de mise en correspondance onRoot()
sur true
:
composeTestRule.onRoot(useUnmergedTree = true).printToLog("MY TAG")
L'outil d'inspection de la mise en page vous permet d'afficher l'arborescence sémantique fusionnée et l'arborescence sémantique non fusionnée en sélectionnant celle que vous préférez dans la vue filtrée :
Figure 5 : Options d'affichage de l'outil d'inspection de la mise en page permettant d'afficher à la fois l'arborescence sémantique fusionnée et l'arborescence sémantique non fusionnée.
Pour chaque nœud de votre arborescence, l'outil d'inspection de mise en page affiche la sémantique fusionnée et la sémantique définie sur ce nœud dans le panneau des propriétés :
Par défaut, les outils de mise en correspondance du framework de test utilisent l'arborescence sémantique fusionnée. C'est la raison pour laquelle vous pouvez interagir avec un bouton en faisant correspondre le texte qu'il contient :
composeTestRule.onNodeWithText("Like").performClick()
Vous pouvez ignorer ce comportement en définissant le paramètre useUnmergedTree
des outils de mise en correspondance sur true
, comme nous l'avons déjà fait avec onRoot
.
Comportement de fusion
Lorsqu'un composable indique que ses descendants doivent être fusionnés, comment cette fusion se produit-elle exactement ?
Chaque propriété sémantique a une stratégie de fusion définie. Par exemple, la propriété ContentDescription
ajoute toutes les valeurs descendantes ContentDescription à une liste. Vous pouvez vérifier la stratégie de fusion d'une propriété sémantique en regardant son implémentation mergePolicy
dans SemanticsProperties.kt
.
Les propriétés peuvent choisir de toujours sélectionner la valeur parent ou enfant, de fusionner les valeurs dans une liste ou une chaîne, de ne pas autoriser la fusion du tout et de générer une exception à la place, ou toute autre stratégie de fusion personnalisée.
Notez que les descendants qui ont eux-mêmes défini mergeDescendants = true
ne sont pas inclus dans la fusion. Prenons un exemple.
Figure 6 : Élément de liste avec une image, du texte et une icône de favori.
Ici, nous avons un élément de liste cliquable. Lorsque l'utilisateur appuie sur la ligne, il accède à la page de détails de l'article et peut le lire. L'élément de liste inclut un bouton permettant de l'ajouter aux favoris. Ici, nous avons un élément cliquable imbriqué. Le bouton s'affichera donc séparément dans l'arborescence fusionnée. Le reste du contenu de la ligne est fusionné :
Figure 7. L'arborescence fusionnée contient plusieurs textes d'une liste au sein du nœud "Ligne". L'arborescence non fusionnée contient des nœuds distincts pour chaque composable Text.
Adapter l'arborescence sémantique
Comme indiqué précédemment, vous pouvez remplacer ou effacer certaines propriétés sémantiques, ou modifier le comportement de fusion de l'arborescence. C'est particulièrement utile lorsque vous créez vos propres composants personnalisés. Si vous ne définissez pas les bonnes propriétés et le bon comportement de fusion, votre application risque de ne pas être accessible et les tests pourraient produire un résultat différent. Pour en savoir plus sur certains cas d'utilisation courants où vous devez adapter l'arborescence sémantique, consultez la documentation sur l'accessibilité. Pour en savoir plus sur les tests, consultez le Guide Tests Compose.