Controlar a ordem de travessia

Por padrão, o comportamento do leitor de tela de acessibilidade em um app do Compose é implementado na ordem de leitura esperada, que geralmente é da esquerda para a direita e de cima para baixo. No entanto, há alguns tipos de layouts de apps em que o algoritmo não consegue determinar a ordem de leitura real sem dicas adicionais. Em apps baseados em visualização, é possível corrija esses problemas usando as propriedades traversalBefore e traversalAfter. A partir do Compose 1.5, o Compose oferece uma API igualmente flexível, mas com um novo modelo conceitual.

isTraversalGroup e traversalIndex são propriedades semânticas que permitem controlar a acessibilidade e a ordem de foco do TalkBack em cenários em que o algoritmo de classificação padrão não é apropriado. isTraversalGroup identifica grupos semanticamente importantes, enquanto traversalIndex ajusta a ordem de elementos individuais dentro desses grupos. Você pode usar apenas isTraversalGroup, ou traversalIndex para mais personalização.

Use isTraversalGroup e traversalIndex na sua para controlar a ordem de apresentação do leitor de tela.

Agrupar elementos com isTraversalGroup

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

Definir isTraversalGroup = true em um nó significa que todos os filhos dele são visitadas antes de ir para outros elementos. Você pode definir o isTraversalGroup como nós que não são focalizáveis para leitores de tela, como colunas, linhas ou caixas.

O exemplo a seguir usa isTraversalGroup. Ela emite quatro elementos de texto. O Os dois elementos da esquerda pertencem a um elemento CardBox, enquanto os dois elementos da direita pertencem a pertencer 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 "Este
  frase está na coluna da esquerda e a coluna direita com a mensagem "Esta frase está à direita".
Figura 1. Um layout com duas frases (uma à esquerda e outra à direita).

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

"Esta frase está em" → "Esta frase é" → "a coluna da esquerda". → "no direito".

Para ordenar os fragmentos corretamente, modifique o snippet original para definir De isTraversalGroup a 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, o CardBox limites se aplicam ao classificar seus elementos. Nesse caso, o lado esquerdo A CardBox é lida primeiro, seguida pela CardBox direita.

Agora, o TalkBack vai ler os fragmentos de frases na ordem correta:

"Esta frase está em" → "a coluna da esquerda". → "Esta frase é" → "no direito".

Personalizar ainda mais a ordem de apresentação

traversalIndex é uma propriedade de ponto flutuante que permite personalizar o TalkBack. ordem de travessia. Se agrupar elementos não for suficiente para que o TalkBack funcionar 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:

  • Elementos com menores valores de traversalIndex são priorizados.
  • Pode ser positivo ou negativo.
  • O valor padrão é 0f.
  • Afeta apenas os nós focalizáveis para o leitor de tela, como elementos na tela como textos ou botões. Por exemplo, definir apenas traversalIndex em uma coluna não terão efeito, a menos que a coluna também tenha isTraversalGroup definido.

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

Exemplo: atravessar aparência do relógio

Uma aparência do relógio é um cenário comum em que a ordem de travessia padrão não funcionam. O exemplo desta seção é um seletor de horário, em que um usuário pode percorrer através dos números no mostrador de relógio e selecione dígitos para a hora e o minuto slots.

Uma aparência do relógio com um seletor de horário acima dela.
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 movendo-se 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 logicamente com os valores padrão de esquerda para direita e de cima para baixo, o TalkBack lê os números fora de ordem. Para retificar 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 travessia, primeiro faça da CircularLayout uma grupo de travessia e definir isTraversalGroup = true. Então, como cada texto de relógio é renderizado no layout, defina o traversalIndex correspondente para o contador .

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

Como os traversalIndexes que foram definidos são relativos somente a outros índices dentro do mesmo agrupamento, o restante da ordenação de telas foi preservados. Em outras palavras, as mudanças semânticas mostradas no código anterior só modificar a ordem dentro do mostrador de relógio que tem isTraversalGroup = true definido.

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

Exemplo: personalizar a ordem de apresentação do botão de ação flutuante

Neste exemplo, traversalIndex e isTraversalGroup controlam o Ordenação de travessia de um botão de ação flutuante (FAB, na sigla em inglês) do Material Design. Base deste exemplo é o seguinte layout:

Um layout com uma barra de apps superior, texto de exemplo, um botão de ação flutuante e
  uma barra de apps inferior.
Figura 3. Layout com uma barra de apps na parte de cima, texto de exemplo, um botão de ação flutuante, e uma barra de apps na parte de baixo.

Por padrão, o layout neste exemplo tem a seguinte ordem do TalkBack:

Barra de apps na parte de cima → Textos de exemplo de 0 a 6 → botão de ação flutuante (FAB) → parte de baixo Barra de apps

Talvez você queira que o leitor de tela se concentre no FAB. Para definir um traversalIndex em um elemento do Material Design, como um FAB, faça o seguinte:

@Composable
fun FloatingBox() {
    Box(modifier = Modifier.semantics { isTraversalGroup = true; traversalIndex = -1f }) {
        FloatingActionButton(onClick = {}) {
            Icon(imageVector = Icons.Default.Add, contentDescription = "fab icon")
        }
    }
}

Neste snippet, criar uma caixa com isTraversalGroup definido como true e definindo um traversalIndex na mesma caixa (-1f é menor que o valor padrão de 0f) significa que a caixa flutuante vem antes de todos os outros elementos na tela.

Em seguida, você pode colocar a caixa flutuante e outros elementos em um scaffold. implementa um layout do Material Design:

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun ColumnWithFABFirstDemo() {
    Scaffold(
        topBar = { TopAppBar(title = { Text("Top App Bar") }) },
        floatingActionButtonPosition = FabPosition.End,
        floatingActionButton = { FloatingBox() },
        content = { padding -> ContentColumn(padding = padding) },
        bottomBar = { BottomAppBar { Text("Bottom App Bar") } }
    )
}

O TalkBack interage com os elementos na seguinte ordem:

FAB → barra de apps superior → textos de exemplo de 0 a 6 → barra de apps inferior

Outros recursos