Modificar a ordem de apresentação

A ordem de apresentação é a ordem em que os serviços de acessibilidade navegam pelos elementos da interface. Em um app Compose, os elementos são organizados na ordem de leitura esperada, que geralmente é da esquerda para a direita e de cima para baixo. No entanto, há alguns casos em que o Compose pode precisar de dicas adicionais para determinar a ordem correta de leitura.

isTraversalGroup e traversalIndex são propriedades semânticas que permitem influenciar a ordem de travessia para serviços de acessibilidade em cenários em que o algoritmo de classificação padrão do Compose não é suficiente. isTraversalGroup identifica grupos semanticamente importantes que precisam de personalização, enquanto traversalIndex ajusta a ordem de elementos individuais nesses grupos. É possível usar isTraversalGroup sozinho para indicar que todos os elementos de um grupo precisam ser selecionados juntos ou com traversalIndex para mais personalização.

Use isTraversalGroup e traversalIndex no app para controlar a ordem de passagem do leitor de tela.

Agrupar elementos para a travessia

isTraversalGroup é uma propriedade booleana que define se um nó de semântica é um grupo de travessia. Esse tipo de nó tem a função de servir como limite ou borda na organização dos filhos do nó.

Definir isTraversalGroup = true em um nó significa que todos os filhos desse nó são visitados antes de passar para outros elementos. É possível definir isTraversalGroup em nós com foco que não são leitores de tela, como colunas, linhas ou caixas.

O exemplo a seguir usa isTraversalGroup. Ele emite quatro elementos de texto. Os dois elementos à esquerda pertencem a um elemento CardBox, enquanto os dois elementos à direita pertencem a outro 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
        )
    }
}

O código produz uma saída semelhante a esta:

Layout com duas colunas de texto, com a coluna esquerda lendo "Esta
  frase está na coluna esquerda" e a coluna direita lendo "Esta frase está à direita".
Figura 1. Um layout com duas frases (uma na coluna esquerda e outra na coluna direita).

Como nenhuma semântica foi definida, o comportamento padrão do leitor de tela é percorrer os elementos da esquerda para a direita e de cima para baixo. Por causa desse padrão, o TalkBack lê os fragmentos da frase na ordem errada:

"Esta frase está em" → "Esta frase está" → "na coluna esquerda." → "à direita".

Para ordenar os fragmentos corretamente, modifique o snippet original para definir isTraversalGroup como 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 }
        )
    }
}

Como isTraversalGroup é definido especificamente em cada CardBox, os limites de CardBox são aplicados ao classificar os elementos. Nesse caso, o CardBox esquerdo é lido primeiro, seguido pelo CardBox direito.

Agora, o TalkBack lê os fragmentos da frase na ordem correta:

"Esta frase está na" → "coluna esquerda." → "Esta frase está" → "na direita."

Personalizar a ordem de apresentação

traversalIndex é uma propriedade float que permite personalizar a ordem de navegação do TalkBack. Se agrupar elementos não for suficiente para que o TalkBack funcione corretamente, use traversalIndex em conjunto com isTraversalGroup para personalizar ainda mais a ordem do leitor de tela.

A propriedade traversalIndex tem as seguintes características:

  • Os elementos com valores de traversalIndex mais baixos são priorizados primeiro.
  • Pode ser positivo ou negativo.
  • O valor padrão é 0f.
  • Para que o índice de translação influencie o comportamento de translação, ele precisa ser definido em um componente que será selecionável e focalizável por serviços de acessibilidade, como elementos na tela, como texto ou botões.
    • Definir apenas traversalIndex em, por exemplo, um Column não teria nenhum efeito, a menos que a coluna também tenha isTraversalGroup definido.

O exemplo a seguir mostra como usar traversalIndex e isTraversalGroup juntos.

Um mostrador de relógio é um cenário comum em que a ordenação de travessia padrão não funciona. O exemplo desta seção é um seletor de horário, em que o usuário pode percorrer os números em um mostrador do relógio e selecionar dígitos para os intervalos de hora e minuto.

Um mostrador de relógio com um seletor de hora acima dele.
Figura 2. Uma imagem de um mostrador de relógio.

No snippet simplificado a seguir, há uma CircularLayout em que 12 números são desenhados, começando com 12 e se movendo no sentido horário ao redor do círculo:

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

Como o mostrador de relógio não é lido de forma lógica com a ordem padrão da esquerda para a direita e de cima para baixo, o TalkBack lê os números fora de ordem. Para corrigir isso, use o valor de incremento do contador, conforme mostrado no snippet a seguir:

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

Para definir corretamente a ordem de travessia, primeiro torne o CircularLayout um grupo de travessia e defina isTraversalGroup = true. Em seguida, conforme cada texto do relógio é desenhado no layout, defina o traversalIndex correspondente como o valor do contador.

Como o valor do contador aumenta continuamente, o traversalIndex de cada valor do relógio é maior à medida que os números são adicionados à tela. O valor do relógio 0 tem um traversalIndex de 0, e o valor do relógio 1 tem um traversalIndex de 1. Dessa forma, a ordem em que o TalkBack os lê é definida. Agora, os números dentro do CircularLayout são lidos na ordem esperada.

Como os traversalIndexes definidos são relativos a outros índices no mesmo grupo, o restante da ordem da tela foi preservado. Em outras palavras, as mudanças semânticas mostradas no snippet de código anterior modificam apenas a ordem dentro do mostrador do relógio que tem isTraversalGroup = true definido.

Sem a definição da semântica CircularLayout's como isTraversalGroup = true, as mudanças traversalIndex ainda se aplicam. No entanto, sem o CircularLayout para vinculá-los, os doze dígitos do mostrador do relógio são lidos por último, depois que todos os outros elementos na tela são visitados. Isso ocorre porque todos os outros elementos têm um traversalIndex padrão de 0f, e os elementos de texto do relógio são lidos depois de todos os outros elementos 0f.

Considerações sobre APIs

Considere o seguinte ao usar as APIs de travessia:

  • isTraversalGroup = true precisa ser definido no pai que contém os elementos agrupados.
  • traversalIndex precisa ser definido em um componente filho que contém semântica e será selecionado pelos serviços de acessibilidade.
  • Verifique se todos os elementos que você está investigando estão no mesmo nível de zIndex, porque isso também afeta a semântica e a ordem de travessia.
  • Verifique se nenhuma semântica é mesclada desnecessariamente, já que isso pode afetar os componentes em que os índices de travessia são aplicados.