De forma predeterminada, el comportamiento del lector de pantalla de accesibilidad en una app de Compose se implementa en el orden de lectura esperado, que suele ser de izquierda a derecha y, luego, de arriba a abajo.
Sin embargo, hay algunos tipos de diseños de apps en los que el algoritmo no puede determinar el orden de lectura real sin sugerencias adicionales. En las apps basadas en vistas, puedes solucionar estos problemas con las propiedades traversalBefore
y traversalAfter
.
A partir de Compose 1.5, Compose proporciona una API igual de flexible, pero con un modelo conceptual nuevo.
isTraversalGroup
y traversalIndex
son propiedades semánticas que te permiten controlar la accesibilidad y el orden del enfoque de TalkBack en situaciones en las que el algoritmo de ordenamiento predeterminado no es apropiado. isTraversalGroup
identifica grupos con importancia semántica, mientras que traversalIndex
ajusta el orden de los elementos individuales dentro de esos grupos. Puedes usar isTraversalGroup
solo o con traversalIndex
para una mayor personalización.
Usa isTraversalGroup
y traversalIndex
en tu app para controlar el orden de recorrido del lector de pantalla.
Agrupa elementos con isTraversalGroup
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 en la organización de los elementos secundarios del nodo.
Si configuras isTraversalGroup = true
en un nodo, se visitarán todos los elementos secundarios de ese nodo antes de moverse a otros elementos. Puedes configurar isTraversalGroup
en nodos enfocables que no sean del 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 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:
Debido a que no se estableció ninguna semántica, el comportamiento predeterminado del lector de pantalla es desviar los elementos de izquierda a derecha y de arriba a abajo. Debido a este valor predeterminado, TalkBack lee los fragmentos de oraciones en el orden incorrecto:
"Esta oración se encuentra en" → "Esta oración es" → "la columna izquierda". → "a la derecha".
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, el CardBox
izquierdo se lee primero, seguido del CardBox
derecho.
Ahora TalkBack lee los fragmentos de las oraciones en el orden correcto:
"Esta oración está en" → "la columna izquierda". → "Esta oración es" → "a la derecha".
Personalizar más el orden de recorrido
traversalIndex
es una propiedad 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 de los lectores de pantalla.
La propiedad traversalIndex
tiene las siguientes características:
- Los elementos con valores de
traversalIndex
más bajos tienen prioridad. - Puede ser positivo o negativo.
- El valor predeterminado es
0f
. - Solo afecta a los nodos enfocables del lector de pantalla, como los elementos en pantalla, como el texto o los botones. Por ejemplo, configurar solo
traversalIndex
en una columna no tendría ningún efecto, a menos que la columna también tenga unisTraversalGroup
configurado.
En el siguiente ejemplo, se muestra cómo puedes usar traversalIndex
y isTraversalGroup
juntos.
Ejemplo: formato de reloj transversal
Una cara de reloj es una situación común en la que el ordenamiento de recorrido estándar no funciona. 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 franjas horarias y los minutos.
En el siguiente fragmento simplificado, hay un CircularLayout
en el que se dibujan 12 números, que comienzan con 12 y se mueven 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 a 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 establecer correctamente el orden de recorrido, primero haz que CircularLayout
sea un grupo de recorrido y configura isTraversalGroup = true
. Luego, a medida que se dibuja el texto de cada reloj 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 del reloj 0 tiene un traversalIndex
de 0, y el valor del reloj 1 tiene un traversalIndex
de 1).
De esta manera, se establece el orden en el que TalkBack las lee. Ahora, los números dentro de CircularLayout
se leen en el orden esperado.
Debido a que los traversalIndexes
que se configuraron 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 isTraversalGroup = true
establecido.
Ten en cuenta que, sin establecer la semántica de CircularLayout's
en isTraversalGroup =
true
, aún se aplican los cambios de traversalIndex
. Sin embargo, sin el CircularLayout
para vincularlos, los doce dígitos de la cara de reloj se leen en último lugar, después de visitar 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
.
Ejemplo: Personaliza el orden de recorrido para el botón de acción flotante
En este ejemplo, traversalIndex
y isTraversalGroup
controlan el orden de recorrido de un botón de acción flotante (BAF) de Material Design. La base de este ejemplo es el siguiente diseño:
De forma predeterminada, el diseño de este ejemplo tiene el siguiente orden de TalkBack:
Barra superior de la app → Textos de ejemplo del 0 al 6 → Botón de acción flotante (BAF) → Barra de la app inferior
Es posible que desees que el lector de pantalla se enfoque primero en el BAF. Para establecer un traversalIndex
en un elemento de Material, como un BAF, haz lo siguiente:
@Composable fun FloatingBox() { Box(modifier = Modifier.semantics { isTraversalGroup = true; traversalIndex = -1f }) { FloatingActionButton(onClick = {}) { Icon(imageVector = Icons.Default.Add, contentDescription = "fab icon") } } }
En este fragmento, crear un cuadro con isTraversalGroup
establecido en true
y configurar un traversalIndex
en el mismo cuadro (-1f
es menor que el valor predeterminado de 0f
) significa que el cuadro flotante se coloca antes que todos los demás elementos en pantalla.
A continuación, puedes colocar el cuadro flotante y otros elementos en un andamiaje, que implementa un diseño de 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") } } ) }
TalkBack interactúa con los elementos en el siguiente orden:
BAF → Barra superior de la app → Textos de ejemplo del 0 al 6 → Barra inferior de la app
Recursos adicionales
- Accesibilidad: Conceptos y técnicas esenciales comunes para todo el desarrollo de apps para Android
- Compila apps accesibles: Pasos clave que puedes seguir para que tu app sea más accesible
- Principios para mejorar la accesibilidad de las apps: Principios clave que debes tener en cuenta cuando trabajes para que tu app sea más accesible
- Prueba de accesibilidad: Prueba principios y herramientas de accesibilidad de Android.