Una composizione descrive l'UI della tua app ed è prodotta eseguendo componibili. La composizione è una struttura ad albero composta dagli elementi componibili che descrivono la tua UI.
Accanto alla composizione esiste un albero parallelo chiamato albero della semantica. Questo albero descrive la tua UI in un modo alternativo comprensibile per i servizi di accessibilità e per il framework Testing. I servizi di accessibilità utilizzano la struttura per descrivere l'app agli utenti con un'esigenza specifica. Il framework di test utilizza la struttura ad albero per interagire con l'app e fare affermazioni al riguardo. L'albero semantico non contiene le informazioni su come disegnare i componibili, ma contiene informazioni sul significato semantico degli elementi componibili.
Se la tua app è composta da elementi componibili e modificatori della libreria di base e dei materiali di Compose, l'albero della semantica viene compilato e generato automaticamente. Tuttavia, quando aggiungi elementi componibili personalizzati di basso livello, devi fornerne manualmente la semantica. Potrebbero verificarsi anche situazioni in cui la struttura ad albero non rappresenta correttamente o in modo completo il significato degli elementi sullo schermo; in tal caso puoi adattare la struttura.
Considera ad esempio questo calendario componibile personalizzato:
In questo esempio, l'intero calendario viene implementato come un unico componibile di basso livello, utilizzando l'elemento componibile Layout
e disegnandolo direttamente in Canvas
.
Se non fai altro, i servizi di accessibilità non riceveranno informazioni sufficienti sui contenuti del componibile e sulla selezione dell'utente nel calendario. Ad esempio, se un utente fa clic sul giorno che contiene 17, il framework per l'accessibilità riceve solo le informazioni di descrizione per l'intero controllo del calendario. In questo caso, il servizio di accessibilità TalkBack riporterebbe "Calendar" o, solo leggermente meglio, "Calendario di aprile" e l'utente verrebbe lasciato a chiedersi che giorno sia stato selezionato. Per rendere più accessibile questo componibile, devi aggiungere manualmente le informazioni semantiche.
Proprietà semantiche
Tutti i nodi nell'albero dell'interfaccia utente con un significato semantico hanno un nodo parallelo nell'albero semantico. Il nodo nell'albero semantico contiene le proprietà che
trasmettono il significato del componibile corrispondente. Ad esempio, l'elemento componibile Text
contiene una proprietà semantica text
, perché questo è il significato dell'elemento componibile. Un Icon
contiene una proprietà contentDescription
(se impostata dallo sviluppatore) che comunica nel testo il significato di Icon
. I componenti e modificatori creati sulla base della libreria di base di Compose già impostano le proprietà pertinenti. Se vuoi, imposta
o sostituisci le proprietà autonomamente con i modificatori semantics
e
clearAndSetSemantics
. Ad esempio, aggiungi azioni di accessibilità personalizzate a un nodo, fornisci una descrizione dello stato alternativa per un elemento attivabile oppure indica che un determinato testo componibile deve essere considerato come intestazione.
Per visualizzare l'albero della semantica, utilizza lo strumento Controllo layout oppure il metodo printToLog()
all'interno dei test. Viene visualizzato l'albero della semantica
attuale in 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") } }
L'output di questo test sarebbe:
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]
Considera come le proprietà semantiche trasmettono il significato di un componibile. Considera un
Switch
. Ecco come appare all'utente:
Per descrivere il significato di questo elemento, potresti dire: "Questo è un Switch, che è un elemento attivabile nello stato "On". Puoi fare clic per interagire con essa."
Questo è esattamente lo scopo per cui vengono utilizzate le proprietà semantiche. Il nodo della semantica di questo elemento Switch contiene le seguenti proprietà, come visualizzate con Layout Inspector:
Role
indica il tipo di elemento. L'elemento StateDescription
descrive come
fare riferimento allo stato "On". Per impostazione predefinita, si tratta di una versione localizzata della parola "On", ma può essere resa più specifica (ad esempio, "Enabled") in base al contesto. ToggleableState
indica lo stato attuale dello Switch. La
proprietà OnClick
fa riferimento al metodo utilizzato per interagire con questo elemento. Per
un elenco completo delle proprietà semantiche, consulta l'oggetto SemanticsProperties
. Per un elenco completo delle possibili azioni di accessibilità, controlla l'oggetto SemanticsActions
.
Tenere traccia delle proprietà semantiche di ogni componibile nell'app sblocca molte potenti possibilità. Alcuni esempi:
- TalkBack utilizza le proprietà per leggere ad alta voce ciò che viene mostrato sullo schermo e consente all'utente di interagire senza problemi con lo schermo. Per lo switch componibile, TalkBack potrebbe dire: "On; Switch; tocca due volte per attivare/disattivare". L'utente può toccare due volte lo schermo per disattivare l'opzione.
-
Il framework di test utilizza le proprietà per trovare i nodi, interagirvi e creare asserzioni. Un test di esempio per Switch potrebbe essere:
val mySwitch = SemanticsMatcher.expectValue( SemanticsProperties.Role, Role.Switch ) composeTestRule.onNode(mySwitch) .performClick() .assertIsOff()
Albero della semantica unito e non unito
Come accennato prima, ogni componibile nell'albero dell'interfaccia utente potrebbe avere zero o più proprietà semantiche impostate. Quando un componibile non ha proprietà semantiche impostate, non viene incluso nell'albero della semantica. In questo modo l'albero semantico contiene solo i nodi che contengono effettivamente un significato semantico. Tuttavia, a volte, per trasmettere il significato corretto di ciò che viene visualizzato sullo schermo, è anche utile unire alcuni sottoalberi di nodi e trattarli come uno solo. In questo modo puoi ragionare su un insieme di nodi nel suo complesso, invece di occuparti di ogni nodo discendente singolarmente. Come regola generale, ogni nodo in questo albero rappresenta un elemento attivabile quando si utilizzano i servizi di accessibilità.
Un esempio di questo componibile è Button
. Puoi ragionare su un pulsante come singolo elemento, anche se può contenere più nodi secondari:
Button(onClick = { /*TODO*/ }) { Icon( imageVector = Icons.Filled.Favorite, contentDescription = null ) Spacer(Modifier.size(ButtonDefaults.IconSpacing)) Text("Like") }
Nell'albero semantico, le proprietà dei discendenti del pulsante vengono unite e il pulsante viene presentato come un singolo nodo foglia nell'albero:
I componibili e i modificatori possono indicare che vogliono unire le proprietà semantiche dei loro discendenti chiamando Modifier.semantics
(mergeDescendants = true) {}
. L'impostazione di questa proprietà su true
indica che
le proprietà semantiche devono essere unite. Nell'esempio Button
, il componibile Button
utilizza internamente il modificatore clickable
che include questo modificatore semantics
. Di conseguenza, i nodi discendenti del pulsante vengono uniti.
Leggi la documentazione sull'accessibilità per scoprire di più su quando dovresti modificare il comportamento di unione nel tuo componibile.
Questa proprietà è impostata per diversi modificatori e componibili nelle librerie Foundation e Material Compose. Ad esempio, i modificatori clickable
e toggleable
uniranno automaticamente i rispettivi discendenti. Inoltre, l'elemento componibile ListItem
unisce i suoi discendenti.
Ispeziona gli alberi
In realtà, l'albero della semantica è costituito da due alberi diversi. Esiste un albero della semantica unito, che unisce i nodi discendenti quando mergeDescendants
è impostato su true
.
C'è anche un albero della semantica non unito, che non applica l'unione, ma mantiene ogni nodo intatto. I servizi di accessibilità utilizzano la struttura ad albero non unita e applicano i propri algoritmi di unione, prendendo in considerazione la proprietà mergeDescendants
. Per impostazione predefinita, il framework di test utilizza l'albero unito.
Puoi esaminare entrambi gli alberi con il metodo printToLog()
. Per impostazione predefinita, e come negli esempi precedenti, l'albero unito viene registrato. Per stampare invece l'albero non unito, imposta il parametro useUnmergedTree
del matcher onRoot()
su true
:
composeTestRule.onRoot(useUnmergedTree = true).printToLog("MY TAG")
La finestra Controllo layout ti consente di visualizzare sia l'albero della semantica unita sia quello non unito, selezionando quello preferito nel filtro della vista:
Per ogni nodo nell'albero, Controllo layout mostra sia la semantica unita sia la semantica unita impostate su tale nodo nel riquadro delle proprietà:
Per impostazione predefinita, i matcher nel framework di test utilizzano l'albero semantico unito.
Ecco perché puoi interagire con Button
facendo corrispondere il testo visualizzato al suo interno:
composeTestRule.onNodeWithText("Like").performClick()
Esegui l'override di questo comportamento impostando il parametro useUnmergedTree
dei
matcher su true
, come con il matcher onRoot
.
Comportamento di unione
Quando un componibile indica che i suoi discendenti devono essere uniti, come avviene esattamente questa unione?
Ogni proprietà semantica ha una strategia di unione definita. Ad esempio, la proprietà ContentDescription
aggiunge tutti i valori ContentDescription discendenti a un elenco. Verifica la strategia di unione di una proprietà semantics controllando la relativa
implementazione di mergePolicy
in SemanticsProperties.kt
. Le proprietà possono assumere il valore principale o secondario, unire i valori in un elenco o in una stringa, non consentire alcuna unione e generare un'eccezione o qualsiasi altra strategia di unione personalizzata.
Una nota importante è che i discendenti che hanno impostato a loro volta mergeDescendants
= true
non sono inclusi nell'unione. Vedi un esempio:
Ecco una voce di elenco cliccabile. Quando l'utente preme sulla riga, l'app va alla pagina dei dettagli dell'articolo, dove può leggerlo. All'interno dell'elemento dell'elenco è presente un pulsante per aggiungere ai preferiti l'articolo, che forma un elemento cliccabile nidificato, in modo che il pulsante venga visualizzato separatamente nell'albero unito. Il resto dei contenuti della riga viene unito:
Adattare l'albero della semantica
Come accennato prima, puoi sostituire o cancellare determinate proprietà semantiche o modificare il comportamento di unione dell'albero. Ciò è particolarmente importante quando crei i tuoi componenti personalizzati. Se non imposti le proprietà e il comportamento di unione corretti, la tua app potrebbe non essere accessibile e i test potrebbero comportarsi in modo diverso da quanto previsto. Per ulteriori informazioni su alcuni casi d'uso comuni in cui è necessario adattare l'albero della semantica, consulta la documentazione sull'accessibilità. Per ulteriori informazioni sui test, consulta la guida ai test.
Risorse aggiuntive
- Accessibilità: concetti e tecniche essenziali comuni a tutto lo sviluppo di app per Android
- Crea app accessibili: passaggi chiave che puoi intraprendere per rendere la tua app più accessibile
- Principi per migliorare l'accessibilità delle app. Principi chiave da tenere presenti quando si lavora per rendere l'app più accessibile.
- Test per l'accessibilità: Principi e strumenti di test relativi all'accessibilità di Android
Consigliato per te
- Nota: il testo del link viene visualizzato quando JavaScript è disattivato
- Accessibilità in Compose
- Material Design 2 in Compose
- Verificare il layout di Compose