Modifica el orden de recorrido

El orden de recorrido es el orden en el que los servicios de accesibilidad navegan por los elementos de la IU. En una app de Compose, los elementos se organizan en el orden de lectura esperado, que suele ser de izquierda a derecha y, luego, de arriba abajo. Sin embargo, hay algunas situaciones en las que Compose podría necesitar sugerencias adicionales para determinar el orden de lectura correcto.

isTraversalGroup y traversalIndex son propiedades semánticas que te permiten influir en el orden de recorrido de los servicios de accesibilidad en situaciones en las que el algoritmo de ordenamiento predeterminado de Compose no es suficiente. isTraversalGroup identifica los grupos semánticamente importantes que necesitan personalización, mientras que traversalIndex ajusta el orden de los elementos individuales dentro de esos grupos. Puedes usar isTraversalGroup solo para indicar que todos los elementos de un grupo deben seleccionarse juntos, o con traversalIndex para personalizarlos aún más.

Usa isTraversalGroup y traversalIndex en tu app para controlar el orden de recorrido del lector de pantalla.

Agrupa elementos para la navegación

isTraversalGroup es una propiedad booleana que define si un nodo de semántica es un grupo de recorrido. Este tipo de nodo es aquel cuya función es servir como límite o borde para organizar los elementos secundarios del nodo.

Configurar isTraversalGroup = true en un nodo significa que se visitan todos los elementos secundarios de ese nodo antes de pasar a otros elementos. Puedes establecer isTraversalGroup en nodos que no sean enfocados por el lector de pantalla, como columnas, filas o cuadros.

En el siguiente ejemplo, se usa isTraversalGroup. Emite cuatro elementos de texto. Los dos elementos de la izquierda pertenecen a un elemento CardBox, mientras que los dos elementos de la derecha pertenecen a otro 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
        )
    }
}

El código produce un resultado similar al siguiente:

Diseño con dos columnas de texto, en la que la columna izquierda dice “Esta oración está en la columna izquierda” y la columna derecha dice “Esta oración está a la derecha”.
Figura 1: Un diseño con dos oraciones (una en la columna izquierda y una en la columna derecha).

Como no se estableció ninguna semántica, el comportamiento predeterminado del lector de pantalla es recorrer los elementos de izquierda a derecha y de arriba abajo. Debido a esta configuración predeterminada, TalkBack lee los fragmentos de oraciones en el orden incorrecto:

"Esta oración está en" → "Esta oración es" → "la columna izquierda". → "a la derecho".

Para ordenar los fragmentos correctamente, modifica el fragmento original para establecer isTraversalGroup en 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 }
        )
    }
}

Debido a que isTraversalGroup se establece específicamente en cada CardBox, los límites de CardBox se aplican cuando se ordenan sus elementos. En este caso, primero se lee el CardBox izquierdo y, luego, el CardBox derecho.

Ahora, TalkBack lee los fragmentos de la oración en el orden correcto:

"Esta oración está en" → "la columna izquierda". → “Esta oración está” → “a la derecha”.

Cómo personalizar el orden de recorrido

traversalIndex es una propiedad de número de punto flotante que te permite personalizar el orden de recorrido de TalkBack. Si agrupar elementos no es suficiente para que TalkBack funcione correctamente, usa traversalIndex junto con isTraversalGroup para personalizar aún más el orden del lector de pantalla.

La propiedad traversalIndex tiene las siguientes características:

  • Los elementos con valores de traversalIndex más bajos tienen la prioridad.
  • Puede ser positivo o negativo.
  • El valor predeterminado es 0f.
  • Para que el índice de recorrido influya en el comportamiento de recorrido, se debe establecer en un componente que los servicios de accesibilidad puedan seleccionar y enfocar, como los elementos en pantalla, como el texto o los botones.
    • Si solo configuras traversalIndex en, por ejemplo, un Column, no se producirá ningún efecto, a menos que la columna también tenga isTraversalGroup configurado.

En el siguiente ejemplo, se muestra cómo puedes usar traversalIndex y isTraversalGroup juntos.

Una cara de reloj es una situación común en la que no funciona el orden de recorrido estándar. El ejemplo de esta sección es un selector de hora, en el que un usuario puede recorrer los números de una cara de reloj y seleccionar dígitos para las ranuras de hora y minuto.

Una cara de reloj con un selector de hora encima.
Figura 2: Una imagen de una cara de reloj.

En el siguiente fragmento simplificado, hay un CircularLayout en el que se dibujan 12 números, comenzando con 12 y moviéndose en el sentido de las manecillas del reloj alrededor del 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())
    }
}

Debido a que la cara de reloj no se lee de forma lógica con el orden predeterminado de izquierda a derecha y de arriba abajo, TalkBack lee los números desordenados. Para rectificar esto, usa el valor del contador incremental, como se muestra en el siguiente fragmento:

@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 configurar correctamente el orden de recorrido, primero haz que CircularLayout sea un grupo de recorrido y establece isTraversalGroup = true. Luego, a medida que cada texto del reloj se dibuja en el diseño, establece su traversalIndex correspondiente en el valor del contador.

Debido a que el valor del contador aumenta de forma continua, el traversalIndex de cada valor de reloj es mayor a medida que se agregan números a la pantalla. El valor de reloj 0 tiene un traversalIndex de 0 y el valor de reloj 1 tiene un traversalIndex de 1. De esta manera, se establece el orden en que TalkBack los lee. Ahora, los números dentro de CircularLayout se leen en el orden esperado.

Debido a que los traversalIndexes que se establecieron solo son relativos a otros índices dentro de la misma agrupación, se conservó el resto del orden de la pantalla. En otras palabras, los cambios semánticos que se muestran en el fragmento de código anterior solo modifican el orden dentro de la cara de reloj que tiene configurado isTraversalGroup = true.

Ten en cuenta que, sin configurar la semántica de CircularLayout's en isTraversalGroup = true, los cambios de traversalIndex aún se aplican. Sin embargo, sin el CircularLayout para vincularlos, los doce dígitos de la cara de reloj se leen por último, después de que se visitaron todos los demás elementos de la pantalla. Esto ocurre porque todos los demás elementos tienen un traversalIndex predeterminado de 0f, y los elementos de texto del reloj se leen después de todos los demás elementos 0f.

Consideraciones de la API

Ten en cuenta lo siguiente cuando uses las APIs de recorrido:

  • isTraversalGroup = true se debe establecer en el elemento superior que contiene los elementos agrupados.
  • traversalIndex se debe establecer en un componente secundario que contenga semántica y que los servicios de accesibilidad seleccionarán.
  • Asegúrate de que todos los elementos que investigues estén en el mismo nivel de zIndex, ya que eso también afecta la semántica y el orden de recorrido.
  • Asegúrate de que no se combinen semánticas innecesariamente, ya que esto puede afectar a los índices de recorrido de componentes a los que se aplican.