Modificare l'ordine di attraversamento

L'ordine di attraversamento è l'ordine in cui i servizi di accessibilità esplorano gli elementi dell'interfaccia utente. In un'app Scrivi, gli elementi sono disposti nell'ordine di lettura previsto, solitamente da sinistra verso destra e dall'alto verso il basso. Tuttavia, in alcuni scenari, Scrivi potrebbe richiedere suggerimenti aggiuntivi per determinare l'ordine di lettura corretto.

isTraversalGroup e traversalIndex sono proprietà semantiche che consentono di influenzare l'ordine di attraversamento per i servizi di accessibilità in scenari in cui l'algoritmo di ordinamento predefinito di Compose non è sufficiente. isTraversalGroup identifica i gruppi semanticamente importanti che richiedono la personalizzazione, mentre traversalIndex regola l'ordine dei singoli elementi all'interno di questi gruppi. Puoi utilizzare isTraversalGroup da solo per indicare che tutti gli elementi di un gruppo devono essere selezionati insieme oppure con traversalIndex per ulteriori personalizzazioni.

Utilizza isTraversalGroup e traversalIndex nella app per controllare l'ordine di esplorazione dello screen reader.

Raggruppare gli elementi per la navigazione

isTraversalGroup è una proprietà booleana che definisce se un nodo semantico è un gruppo di esplorazione. Questo tipo di nodo ha la funzione di delimitare o bordare l'organizzazione dei nodi secondari.

L'impostazione isTraversalGroup = true su un nodo indica che tutti i nodi secondari di quel nodo vengono visitati prima di passare ad altri elementi. Puoi impostare isTraversalGroup su nodi non attivabili tramite lo screen reader, ad esempio colonne, righe o caselle.

L'esempio seguente utilizza isTraversalGroup. Emette quattro elementi di testo. I due elementi a sinistra appartengono a un elemento CardBox, mentre i due elementi a destra appartengono a un altro elemento 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
        )
    }
}

Il codice produce un output simile al seguente:

Layout con due colonne di testo, con la colonna sinistra che riporta "Questa
  frase è nella colonna sinistra" e la colonna destra "Questa frase è a destra".
Figura 1. Un layout con due frasi (una nella colonna di sinistra e una nella colonna di destra).

Poiché non è stata impostata alcuna semantica, il comportamento predefinito dello screen reader è esaminare gli elementi da sinistra a destra e dall'alto verso il basso. A causa di questa impostazione predefinita, TalkBack legge i frammenti di frase nell'ordine sbagliato:

"Questa frase si trova nella" → "Questa frase si trova nella" → "colonna a sinistra". → "a destra."

Per ordinare correttamente i frammenti, modifica lo snippet originale impostandoisTraversalGroup su 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 }
        )
    }
}

Poiché isTraversalGroup è impostato specificamente su ogni CardBox, i limiti di CardBox vengono applicati durante l'ordinamento dei relativi elementi. In questo caso, viene letto prima il CardBox a sinistra, seguito dal CardBox a destra.

Ora TalkBack legge i frammenti di frase nell'ordine corretto:

"Questa frase si trova nella" → "colonna di sinistra". → "Questa frase è" → "a destra."

Personalizzare l'ordine di attraversamento

traversalIndex è una proprietà float che consente di personalizzare l'ordine di esplorazione di TalkBack. Se raggruppare gli elementi non è sufficiente per il corretto funzionamento di TalkBack, utilizza traversalIndex insieme a isTraversalGroup per personalizzare ulteriormente l'ordine dello screen reader.

La proprietà traversalIndex ha le seguenti caratteristiche:

  • Gli elementi con valori traversalIndex più bassi hanno una priorità maggiore.
  • Può essere positivo o negativo.
  • Il valore predefinito è 0f.
  • Affinché l'indice di attraversamento influisca sul comportamento di attraversamento, deve essere impostato su un componente che sarà selezionabile e acquisibile dai servizi di accessibilità, ad esempio elementi sullo schermo come testo o pulsanti.
    • L'impostazione di solo traversalIndex, ad esempio, su un Column non avrebbe alcun effetto, a meno che nella colonna non sia impostato anche isTraversalGroup.

L'esempio seguente mostra come utilizzare traversalIndex e isTraversalGroup insieme.

Un quadrante è uno scenario comune in cui l'ordinamento di attraversamento standard non funziona. L'esempio in questa sezione è un selettore dell'ora, in cui un utente può scorrere i numeri su un quadrante e selezionare le cifre per le ore e i minuti.

Un quadrante orologio con un selettore dell'ora sopra.
Figura 2. Un'immagine di un quadrante.

Nel seguente snippet semplificato è presente un CircularLayout in cui sono disegnati 12 numeri, a partire da 12 e procedendo in senso orario attorno al cerchio:

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

Poiché il quadrante non viene letto in modo logico con l'ordinamento predefinito da sinistra a destra e dall'alto verso il basso, TalkBack legge i numeri fuori sequenza. Per risolvere il problema, utilizza il valore del contatore in aumento, come mostrato nello snippet seguente:

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

Per impostare correttamente l'ordine di attraversamento, crea innanzitutto CircularLayout come gruppo di attraversamento e imposta isTraversalGroup = true. Poi, man mano che ogni testo dell'orologio viene visualizzato nel layout, imposta il relativo traversalIndex sul valore del contatore.

Poiché il valore del contatore aumenta continuamente, il valore traversalIndex di ogni valore dell'orologio è maggiore man mano che i numeri vengono aggiunti allo schermo: il valore dell'orologio 0 ha un valore traversalIndex pari a 0 e il valore dell'orologio 1 ha un valore traversalIndex pari a 1. In questo modo, viene impostato l'ordine in cui TalkBack le legge. Ora i numeri all'interno di CircularLayout vengono letti nell'ordine previsto.

Poiché i valori traversalIndexes impostati si riferiscono solo ad altri indici all'interno dello stesso raggruppamento, il resto dell'ordine dello schermo è stato mantenuto. In altre parole, le modifiche semantiche mostrate nello snippet di codice precedente modificano solo l'ordine all'interno del quadrante con isTraversalGroup = true impostato.

Tieni presente che, senza impostare la semantica CircularLayout's su isTraversalGroup = true, le modifiche traversalIndex vengono comunque applicate. Tuttavia, senza il carattere CircularLayout per legarli, le dodici cifre del quadrante vengono lette per ultime, dopo che sono stati visitati tutti gli altri elementi sullo schermo. Questo accade perché tutti gli altri elementi hanno un valore traversalIndex predefinito di 0f e gli elementi di testo dell'orologio vengono letti dopo tutti gli altri elementi 0f.

Considerazioni relative alle API

Tieni presente quanto segue quando utilizzi le API di attraversamento:

  • isTraversalGroup = true deve essere impostato sull'elemento principale contenente gli elementi raggruppati.
  • traversalIndex deve essere impostato su un componente secondario che contiene la semantica e verrà selezionato dai servizi di accessibilità.
  • Assicurati che tutti gli elementi che stai esaminando siano allo stesso livello zIndex, in quanto ciò influisce anche sulla semantica e sull'ordine di attraversamento.
  • Assicurati che non vengano unite semantiche non necessarie, in quanto ciò potrebbe influire sui componenti a cui vengono applicati gli indici di attraversamento.