Semantica

Oltre alle informazioni principali che un composable contiene, come una stringa di testo di un composable Text, può essere utile avere altre informazioni complementari sui componenti dell'interfaccia utente.

Le informazioni sul significato e sul ruolo di un componente in Compose si chiamano semantica e sono un modo per fornire contesto aggiuntivo sui componenti a servizi come accessibilità, compilazione automatica e test. Ad esempio, un'icona della fotocamera potrebbe essere visivamente solo un'immagine, ma il significato semantico potrebbe essere "Scatta una foto".

Combinando la semantica appropriata con le API Compose appropriate, fornisci ai servizi di accessibilità quante più informazioni possibili sul tuo componente, che poi decide come rappresentarlo all'utente.

Le API Material e Compose UI e Foundation sono dotate di una semantica integrata che segue il loro ruolo e la loro funzione specifici, ma puoi anche modificare questa semantica per le API esistenti o impostarne di nuove per i componenti personalizzati, in base ai tuoi requisiti specifici.

Proprietà semantiche

Le proprietà semantiche trasmettono il significato del composable corrispondente. Ad esempio, il composable Text contiene una proprietà semantica text, perché è il significato del composable. Un Icon contiene una proprietà contentDescription (se impostata dallo sviluppatore) che indica in testo il significato dell'icona.

Considera in che modo le proprietà di semantica trasmettono il significato di un composable. Prendi in considerazione un Switch. Ecco come appare all'utente:

Figura 1. Un Switch negli stati "On" e "Off".

Per descrivere il significato di questo elemento, puoi dire quanto segue: "Questo è un pulsante di attivazione/disattivazione, ovvero un elemento attivabile/disattivabile nel suo stato "On". Puoi fare clic su di essa per interagire."

È esattamente a questo scopo che vengono utilizzate le proprietà di semantica. Il nodo semantico di questo elemento Switch contiene le seguenti proprietà, visualizzate con l'ispettore di layout:

Layout Inspector che mostra le proprietà Semantics di un componibile Switch
Figura 2. Layout Inspector che mostra le proprietà di semantica di un Switchcomponibile
.

Role indica il tipo di elemento. StateDescription descrive come deve essere fatto riferimento allo stato "On". Per impostazione predefinita, si tratta di una versione localizzata della parola "On", ma può essere resa più specifica (ad esempio "Attivata") in base al contesto. ToggleableState indica lo stato attuale dello switch. La proprietà OnClick fa riferimento al metodo utilizzato per interagire con questo elemento.

Monitorare le proprietà semantiche di ogni composable nella tua app sblocca moltissime funzionalità efficaci:

  • I servizi di accessibilità utilizzano le proprietà per rappresentare l'interfaccia utente visualizzata sullo schermo e consentire agli utenti di interagire con essa. Per il componente composable Pulsante, TalkBack potrebbe annunciare: "On; Pulsante; 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, interagire con essi e fare affermazioni:
    val mySwitch = SemanticsMatcher.expectValue(
        SemanticsProperties.Role, Role.Switch
    )
    composeTestRule.onNode(mySwitch)
        .performClick()
        .assertIsOff()

I composabili e i modificatori basati sulla libreria di base di Compose impostano già per impostazione predefinita le proprietà pertinenti. Se vuoi, puoi modificare manualmente queste proprietà per migliorare il supporto dell'accessibilità per casi d'uso specifici o modificare la strategia di unione o eliminazione dei composabili.

Per segnalare ai servizi di accessibilità il tipo specifico di contenuti del componente, puoi applicare una serie di semantiche diverse. Queste aggiunte supporteranno le informazioni semantiche principali esistenti e aiuteranno i servizi di accessibilità a perfezionare il modo in cui il componente viene rappresentato, annunciato o con cui viene interagito.

Per un elenco completo delle proprietà di semantica, consulta l'oggetto SemanticsProperties. Per un elenco completo delle possibili azioni di accessibilità, consulta l'oggetto SemanticsActions.

Intestazioni

Le app spesso contengono schermate con contenuti ricchi di testo, come articoli lunghi o pagine di notizie, che in genere sono suddivise in diverse sezioni con intestazioni:

Un post del blog con il testo dell'articolo in un contenitore scorrevole.
Figura 3. Un post del blog con il testo dell'articolo in un contenitore scorrevole.

Gli utenti con esigenze di accessibilità possono avere difficoltà a navigare facilmente in una schermata di questo tipo. Per migliorare l'esperienza di navigazione, alcuni servizi di accessibilità consentono di navigare più facilmente tra sezioni o intestazioni. Per attivare questa opzione, indica che il componente è un heading definendone la proprietà semantica:

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

Avvisi e popup

Se il componente è un avviso o un popup, ad esempio Snackbar, ti consigliamo di indicare ai servizi di accessibilità che è possibile comunicare agli utenti una nuova struttura o aggiornamenti dei contenuti.

I componenti simili agli avvisi possono essere contrassegnati con la proprietà di semantica liveRegion. In questo modo, i servizi di accessibilità possono notificare automaticamente all'utente le modifiche a questo componente o ai suoi componenti secondari:

PopupAlert(
    message = "You have a new message",
    modifier = Modifier.semantics {
        liveRegion = LiveRegionMode.Polite
    }
)

Devi utilizzare liveRegionMode.Polite nella maggior parte dei casi in cui l'attenzione degli utenti deve essere attirata solo brevemente su avvisi o contenuti importanti in mutazione sullo schermo.

Ti consigliamo di utilizzare liveRegion.Assertive con parsimonia per evitare feedback che potrebbero causare interruzioni. Deve essere utilizzato in situazioni in cui è fondamentale informare gli utenti di contenuti urgenti:

PopupAlert(
    message = "Emergency alert incoming",
    modifier = Modifier.semantics {
        liveRegion = LiveRegionMode.Assertive
    }
)

Le regioni in tempo reale non devono essere utilizzate per i contenuti che si aggiornano di frequente, come i timer di conto alla rovescia, per evitare di sovraccaricare gli utenti con feedback costanti.

Componenti simili a finestre

I componenti personalizzati simili a finestre, come ModalBottomSheet, richiedono indicatori aggiuntivi per distinguerli dai contenuti circostanti. A questo scopo, puoi utilizzare la semantica paneTitle, in modo che eventuali modifiche pertinenti a finestre o riquadri possano essere rappresentate in modo appropriato dai servizi di accessibilità, insieme alle relative informazioni semantiche principali:

ShareSheet(
    message = "Choose how to share this photo",
    modifier = Modifier
        .fillMaxWidth()
        .align(Alignment.TopCenter)
        .semantics { paneTitle = "New bottom sheet" }
)

Come riferimento, consulta come Material 3 utilizza paneTitle per i suoi componenti.

Componenti di errore

Per altri tipi di contenuti, come i componenti simili a errori, ti consigliamo di approfondire le informazioni semantiche principali per gli utenti con esigenze di accessibilità. Quando definisci gli stati di errore, puoi comunicare ai servizi di accessibilità la semantica di error e fornire messaggi di errore espansi.

In questo esempio, TalkBack legge le informazioni del testo dell'errore principale, seguite da messaggi aggiuntivi espansi:

Error(
    errorText = "Fields cannot be empty",
    modifier = Modifier
        .semantics {
            error("Please add both email and password")
        }
)

Componenti di monitoraggio dell'avanzamento

Per i componenti personalizzati che monitorano l'avanzamento, ti consigliamo di informare gli utenti delle modifiche apportate, incluso il valore corrente dell'avanzamento, l'intervallo e la dimensione del passo. Puoi farlo con la semantica progressBarRangeInfo: in questo modo, i servizi di accessibilità sono a conoscenza delle modifiche dell'avanzamento e possono aggiornare gli utenti di conseguenza. Inoltre, le diverse tecnologie per la disabilità potrebbero avere modi unici per suggerire l'aumento e la diminuzione della progressione.

ProgressInfoBar(
    modifier = Modifier
        .semantics {
            progressBarRangeInfo =
                ProgressBarRangeInfo(
                    current = progress,
                    range = 0F..1F
                )
        }
)

Informazioni su elenchi e articoli

In elenchi e griglie personalizzate con molti elementi, potrebbe essere utile per i servizi di accessibilità ricevere anche informazioni più dettagliate, come il numero totale di elementi e indici.

Utilizzando le semantiche collectionInfo e collectionItemInfo rispettivamente per l'elenco e gli elementi, in questo lungo elenco, i servizi di accessibilità possono informare gli utenti dell'indice dell'elemento in cui si trovano rispetto alla raccolta totale, oltre alle informazioni semantiche testuali:

MilkyWayList(
    modifier = Modifier
        .semantics {
            collectionInfo = CollectionInfo(
                rowCount = milkyWay.count(),
                columnCount = 1
            )
        }
) {
    milkyWay.forEachIndexed { index, text ->
        Text(
            text = text,
            modifier = Modifier.semantics {
                collectionItemInfo =
                    CollectionItemInfo(index, 0, 0, 0)
            }
        )
    }
}

Descrizione dello stato

Un componibile può definire un stateDescription per la semantica che il framework Android utilizza per leggere lo stato del componibile. Ad esempio, un composable attivabile/disattivabile può trovarsi in uno stato "selezionato" o "deselezionato". In alcuni casi, potresti voler sostituire le etichette di descrizione dello stato predefinite utilizzate da Compose. Puoi farlo specificando esplicitamente le etichette di descrizione dello stato prima di definire un composable come attivabile/disattivabile:

@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() }
            )
    ) {
        /* ... */
    }
}

Azioni personalizzate

Le azioni personalizzate possono essere utilizzate per gesti del touchscreen più complessi, come scorrimento per ignorare o trascinamento, poiché possono essere difficili da usare per gli utenti con disabilità motorie o altre disabilità.

Per rendere più accessibile il gesto di scorrimento per chiudere, puoi collegarlo a un'azione personalizzata, passando l'azione di chiusura e l'etichetta:

SwipeToDismissBox(
    modifier = Modifier.semantics {
        // Represents the swipe to dismiss for accessibility
        customActions = listOf(
            CustomAccessibilityAction(
                label = "Remove article from list",
                action = {
                    removeArticle()
                    true
                }
            )
        )
    },
    state = rememberSwipeToDismissBoxState(),
    backgroundContent = {}
) {
    ArticleListItem()
}

Un servizio di accessibilità come TalkBack evidenzia il componente e suggerisce che sono disponibili altre azioni nel relativo menu, rappresentando l'azione di scorrimento per chiudere:

Visualizzazione del menu di azioni di TalkBack
Figura 4. Visualizzazione del menu di azioni di TalkBack.

Un altro caso d'uso per le azioni personalizzate sono gli elenchi lunghi con elementi che hanno più azioni disponibili, poiché potrebbe essere noioso per gli utenti eseguire l'iterazione di ogni azione per ogni singolo elemento:

=Visualizzazione della navigazione di Switch Access sullo schermo
Figura 5. Visualizzazione della navigazione di Switch Access sullo schermo.

Per migliorare l'esperienza di navigazione, che è particolarmente utile per le tecnologie per la disabilità basate sulle interazioni come Switch Access o Voice Access, puoi utilizzare azioni personalizzate sul contenitore per spostare le azioni dal singolo traversale in un menu di azioni separato:

ArticleListItemRow(
    modifier = Modifier
        .semantics {
            customActions = listOf(
                CustomAccessibilityAction(
                    label = "Open article",
                    action = {
                        openArticle()
                        true
                    }
                ),
                CustomAccessibilityAction(
                    label = "Add to bookmarks",
                    action = {
                        addToBookmarks()
                        true
                    }
                ),
            )
        }
) {
    Article(
        modifier = Modifier.clearAndSetSemantics { },
        onClick = openArticle,
    )
    BookmarkButton(
        modifier = Modifier.clearAndSetSemantics { },
        onClick = addToBookmarks,
    )
}

In questi casi, assicurati di cancellare manualmente la semantica dei componenti secondari originali con il modificatore clearAndSetSemantics, poiché li stai spostando nelle azioni personalizzate.

Utilizzando Switch Access come esempio, il relativo menu si apre quando selezioni il contenitore e elenca le azioni nidificate disponibili:

Evidenziazione di Switch Access dell'elemento dell'elenco di articoli
Figura 6. Evidenzia l'elemento dell'elenco di articoli di Switch Access.
Visualizzazione del menu di azioni di Switch Access.
Figura 7. Visualizzazione del menu di azioni di Switch Access.

Albero della semantica

Una composizione descrive l'interfaccia utente dell'app e viene prodotta dall'esecuzione di composable. La composizione è una struttura ad albero costituita dai composabili che descrivono l'interfaccia utente.

Accanto alla composizione esiste un albero parallelo, chiamato albero semantico. Questa struttura ad albero descrive l'interfaccia utente in un modo alternativo, comprensibile per i servizi di accessibilità e per il framework di test. I servizi di accessibilità utilizzano la struttura ad albero per descrivere l'app agli utenti con un bisogno specifico. Il framework di test utilizza la struttura ad albero per interagire con la tua app e fare affermazioni in merito. L'albero della semantica non contiene informazioni su come disegnare i composabili, ma contiene informazioni sul significato semantico dei composabili.

Una gerarchia dell'interfaccia utente tipica e la relativa struttura ad albero della semantica
Figura 8. Una gerarchia dell'interfaccia utente tipica e la relativa struttura ad albero della semantica.

Se la tua app è composta da composabili e modificatori della base di Compose e della libreria di Material, l'albero della semantica viene compilato e generato automaticamente. Tuttavia, quando aggiungi composabili di basso livello personalizzati, devi fornire manualmente la relativa semantica. Potrebbero anche esserci situazioni in cui l'albero non rappresenta correttamente o completamente il significato degli elementi sullo schermo. In questo caso, puoi adattarlo.

Prendi ad esempio questo composable del calendario personalizzato:

Un calendario personalizzato componibile con elementi di giorno selezionabili
Figura 9. Un calendario personalizzato componibile con elementi di giorno selezionabili.

In questo esempio, l'intero calendario è implementato come un singolo composable di basso livello, utilizzando il composable Layout e disegnando direttamente in Canvas. Se non esegui altre operazioni, i servizi di accessibilità non riceveranno informazioni sufficienti sui contenuti del composable e sulla selezione dell'utente all'interno del calendario. Ad esempio, se un utente fa clic sul giorno contenente il 17, il framework di accessibilità riceve solo le informazioni sulla descrizione dell'intero controllo del calendario. In questo caso, il servizio di accessibilità TalkBack annuncierebbe "Calendario" o, solo leggermente meglio, "Calendario di aprile" e l'utente non saprebbe quale giorno è stato selezionato. Per rendere questo composable più accessibile, dovrai aggiungere manualmente le informazioni semantiche.

Albero unito e non unito

Come accennato in precedenza, ogni composable nella struttura ad albero dell'interfaccia utente potrebbe avere zero o più proprietà semantiche impostate. Quando un composable non ha proprietà di semantica impostate, non è incluso nell'albero della semantica. In questo modo, l'albero della semantica contiene solo i nodi che contengono effettivamente un significato semantico. Tuttavia, a volte per trasmettere il significato corretto di ciò che viene mostrato sullo schermo, è utile anche unire determinati sotto alberi di nodi e trattarli come uno solo. In questo modo, puoi ragionare su un insieme di nodi nel loro complesso, anziché gestire singolarmente ogni nodo discendente. Come regola generale, ogni nodo di questa struttura rappresenta un elemento che può essere attivato quando si utilizzano i servizi di accessibilità.

Un esempio di questo tipo di composable è Button. Puoi considerare un pulsante come un 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 della semantica, le proprietà dei discendenti del pulsante vengono unite e il pulsante viene visualizzato come un singolo nodo foglia nell'albero:

Rappresentazione della semantica di una singola pagina unita
Figura 10. Rappresentazione semantica di una singola foglia unita.

I composabili e i modificatori possono indicare che vogliono unire le proprietà di semantica dei loro discendenti chiamando Modifier.semantics (mergeDescendants = true) {}. L'impostazione di questa proprietà su true indica che le proprietà di semantica devono essere unite. Nell'esempio Button, il composable Button utilizza internamente il modificatore clickable che include questo modificatore semantics. Pertanto, i nodi discendenti del pulsante vengono uniti. Leggi la documentazione sull'accessibilità per scoprire di più su quando devi modificare il comportamento di unione nel tuo composable.

Diversi modificatori e composabili nelle librerie Foundation e Material Compose hanno questa proprietà impostata. Ad esempio, i modificatori clickable e toggleable uniscono automaticamente i relativi discendenti. Inoltre, il componibile ListItem riunirà i suoi discendenti.

Controlla l'albero

L'albero della semantica è in realtà costituito da due alberi diversi. Esiste un albero di semantica unito, che unisce i nodi discendenti quando mergeDescendants è impostato su true. Esiste anche un albero della semantica non unito, che non applica l'unione, ma mantiene invariati tutti i nodi. I servizi di accessibilità utilizzano l'albero non unito e applicano i propri algoritmi di unione, tenendo conto della proprietà mergeDescendants. Il framework di test utilizza l'albero unito per impostazione predefinita.

Puoi ispezionare entrambi gli alberi con il metodo printToLog(). Per impostazione predefinita, come negli esempi precedenti, la registrazione del albero unito viene eseguita. Per stampare invece l'albero non unito, imposta il parametro useUnmergedTree dell'operatore di corrispondenza onRoot() su true:

composeTestRule.onRoot(useUnmergedTree = true).printToLog("MY TAG")

L'ispettore layout ti consente di visualizzare sia l'albero della semantica unito sia quello non unito, selezionando quello preferito nel filtro della visualizzazione:

Opzioni di visualizzazione dell'ispettore layout, che consentono la visualizzazione dell'albero della semantica unito e non unito
Figura 11. Opzioni di visualizzazione dell'ispettore layout, che consentono la visualizzazione dell'albero della semantica unito e non unito.

Per ogni nodo dell'albero, l'ispettore del layout mostra sia le semantiche unite sia le semantiche impostate su quel nodo nel riquadro delle proprietà:

Proprietà di semantica unite e impostate
Figura 12. Le proprietà di semantica sono state unite e impostate.

Per impostazione predefinita, i corrispondenti nel framework di test utilizzano l'albero della semantica unito. Ecco perché puoi interagire con un Button abbinando il testo visualizzato al suo interno:

composeTestRule.onNodeWithText("Like").performClick()

Per eseguire l'override di questo comportamento, imposta il parametro useUnmergedTree dei matcher su true, come per il matcher onRoot.

Adatta l'albero

Come accennato in precedenza, puoi ignorare o cancellare determinate proprietà di semantica o modificare il comportamento di unione dell'albero. Questo è 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 scoprire di più sui test, consulta la guida ai test.