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 do 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 cenários em que o Compose pode precisar de mais dicas para determinar a ordem de leitura correta.

isTraversalGroup e traversalIndex são propriedades semânticas que permitem influenciar a ordem de apresentação dos 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 dentro desses grupos. É possível usar isTraversalGroup sozinho para indicar que todos os elementos de um grupo precisam ser selecionados juntos ou com traversalIndex para uma personalização mais detalhada.

Use isTraversalGroup e traversalIndex no seu app para controlar a ordem de apresentação do leitor de tela.

Agrupar elementos para 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 um limite ou borda na organização dos nós filhos.

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 que não podem ser focados por leitores de tela, como colunas, linhas ou caixas.

O exemplo a seguir usa isTraversalGroup. Ele emite quatro elementos do 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. A coluna da esquerda diz "Esta
  frase está na coluna da esquerda", e a da direita diz "Esta frase está na coluna da direita".
Figura 1. Um layout com duas frases (uma na coluna da esquerda e outra na coluna da 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. Devido a esse padrão, o TalkBack lê os fragmentos da frase na ordem errada:

"Esta frase está na" → "Esta frase está" → "coluna da esquerda." → "on the right."

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 se aplicam ao classificar os elementos. Nesse caso, o CardBox da esquerda é lido primeiro, seguido pelo CardBox da direita.

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

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

Personalizar a ordem de apresentação

traversalIndex é uma propriedade flutuante que permite personalizar a ordem de apresentação do TalkBack. Se o agrupamento de elementos não for suficiente para o TalkBack funcionar corretamente, use traversalIndex em conjunto com isTraversalGroup para personalizar ainda mais a ordenação 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 travessia influencie o comportamento de travessia, ele precisa ser definido em um componente que possa ser selecionado e focado 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 efeito, a menos que a coluna também tenha isTraversalGroup definido.

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

Um mostrador do relógio é um cenário comum em que a ordem de apresentação padrão não funciona. O exemplo nesta 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 espaços de hora e minuto.

Um mostrador de relógio com um seletor de horário acima dele.
Figura 2. Imagem de um mostrador de relógio.

No snippet simplificado a seguir, há um 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 do relógio não é lido de forma lógica com a ordenação 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 do contador de incremento, 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 apresentação, primeiro transforme o CircularLayout em um grupo de apresentação 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 lê os elementos é definida. Agora, os números dentro de CircularLayout são lidos na ordem esperada.

Como os traversalIndexes definidos são relativos apenas a outros índices no mesmo agrupamento, 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 no mostrador do relógio que tem isTraversalGroup = true definido.

Sem definir a semântica de CircularLayout's como isTraversalGroup = true, as mudanças de 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 elemento pai que contém os elementos agrupados.
  • traversalIndex precisa ser definido em um componente filho que contenha semântica e seja selecionado por serviços de acessibilidade.
  • Verifique se todos os elementos que você está investigando estão no mesmo nível de zIndex, já que isso também afeta a semântica e a ordem de apresentação.
  • Verifique se nenhuma semântica foi mesclada desnecessariamente, porque isso pode afetar os componentes em que os índices de travessia são aplicados.