Las APIs de Material, Compose UI y Foundation implementan y ofrecen muchas prácticas accesibles de forma predeterminada. Contienen semántica integrada que sigue su rol y función específicos. Esto significa que la mayor parte de la compatibilidad con la accesibilidad se proporciona con poco o ningún trabajo adicional.
Usar las APIs adecuadas para el propósito correcto significa que los componentes suelen incluir comportamientos de accesibilidad predefinidos que abarcan casos de uso estándar. Sin embargo, siempre verifica si estos valores predeterminados se ajustan a tus necesidades de accesibilidad. De lo contrario, Compose proporciona formas de cumplir con requisitos más específicos.
Comprender la semántica y los patrones de accesibilidad predeterminados en las APIs de Compose te ayuda a usarlos teniendo en cuenta la accesibilidad. También te ayuda a admitir la accesibilidad en más componentes personalizados.
Tamaños mínimos de los objetivos táctiles
Todos los elementos en pantalla en los que se puede hacer clic, que se pueden tocar o con los que se puede interactuar deben tener un tamaño suficiente para garantizar una interacción confiable. Cuando definas el tamaño de estos elementos, asegúrate de establecer el tamaño mínimo en 48 dp para seguir correctamente los lineamientos de accesibilidad de Material Design.
Los componentes de Material, como Checkbox, RadioButton, Switch, Slider y Surface, establecen este tamaño mínimo de forma interna, pero solo cuando el componente puede recibir acciones del usuario. Por ejemplo, cuando un Checkbox tiene su parámetro onCheckedChange establecido en un valor no nulo, la casilla de verificación incluye padding para tener un ancho y una altura de, al menos, 48 dp.
@Composable private fun CheckableCheckbox() { Checkbox(checked = true, onCheckedChange = {}) }
Cuando el parámetro onCheckedChange se establece en nulo, no se incluye el padding, ya que no se puede interactuar con el componente directamente.
@Composable private fun NonClickableCheckbox() { Checkbox(checked = true, onCheckedChange = null) }
Cuando implementas controles de selección, como Switch, RadioButton o Checkbox, por lo general, elevas el comportamiento en el que se puede hacer clic a un contenedor superior. Para ello, configuras la devolución de llamada de clic en el elemento componible como null y agregas un modificador toggleable o selectable al elemento componible superior.
@Composable private fun CheckableRow() { MaterialTheme { var checked by remember { mutableStateOf(false) } Row( Modifier .toggleable( value = checked, role = Role.Checkbox, onValueChange = { checked = !checked } ) .padding(16.dp) .fillMaxWidth() ) { Text("Option", Modifier.weight(1f)) Checkbox(checked = checked, onCheckedChange = null) } } }
Cuando el tamaño de un elemento componible para hacer clic es menor que el tamaño del objetivo táctil mínimo, Compose aumenta el tamaño del objetivo táctil. Para ello, expande el tamaño del objetivo táctil fuera de los límites del elemento componible.
En el siguiente ejemplo, se incluye un Box muy pequeño en el que se puede hacer clic. El área del objetivo táctil se expande automáticamente más allá de los límites de Box, por lo que presionar junto a Box aún activa el evento de clic.
@Composable private fun SmallBox() { var clicked by remember { mutableStateOf(false) } Box( Modifier .size(100.dp) .background(if (clicked) Color.DarkGray else Color.LightGray) ) { Box( Modifier .align(Alignment.Center) .clickable { clicked = !clicked } .background(Color.Black) .size(1.dp) ) } }
Para evitar una posible superposición entre las áreas táctiles de diferentes elementos componibles, usa siempre un tamaño mínimo lo suficientemente grande para el elemento componible. En el ejemplo, eso significaría usar el modificador sizeIn para establecer el tamaño mínimo de la caja interior:
@Composable private fun LargeBox() { var clicked by remember { mutableStateOf(false) } Box( Modifier .size(100.dp) .background(if (clicked) Color.DarkGray else Color.LightGray) ) { Box( Modifier .align(Alignment.Center) .clickable { clicked = !clicked } .background(Color.Black) .sizeIn(minWidth = 48.dp, minHeight = 48.dp) ) } }
Si el elemento debe mantener un tamaño visible más pequeño, usa el modificador minimumInteractiveComponentSize de Material 3. Reserva al menos 48 dp de espacio de diseño en cada dimensión y centra el elemento en ese espacio, de modo que el elemento mantenga su tamaño dibujado y los elementos que lo rodean se muevan para dejar espacio. El modificador no expande el objetivo táctil en sí, sino que deja espacio en el diseño para la expansión descrita anteriormente.
Los componentes de Material, como Checkbox, usan este modificador de forma interna. Colócalo antes de cualquier modificador de tamaño, ya que un modificador de tamaño antes de él limita el espacio que puede reservar.
@Composable private fun MinimumInteractiveBox() { var clicked by remember { mutableStateOf(false) } Row(verticalAlignment = Alignment.CenterVertically) { Box( Modifier .clickable { clicked = !clicked } .minimumInteractiveComponentSize() .size(24.dp) .background(if (clicked) Color.DarkGray else Color.Black) ) Text("Option") } }
Elementos gráficos
Cuando defines un elemento de componibilidad Image o Icon, no hay una forma automática de que el framework de Android comprenda lo que muestra la app. Debes pasar una descripción textual del elemento gráfico.
Imagina una pantalla en la que el usuario pueda compartir la página actual con amigos. Esta pantalla contiene un ícono para compartir en el que se puede hacer clic:
Basándose solo en el ícono, el framework de Android no puede describirlo a un usuario con discapacidad visual. El framework de Android necesita una descripción textual adicional del ícono.
El parámetro contentDescription describe un elemento gráfico. Usa una cadena localizada, ya que es visible para el usuario.
@Composable private fun ShareButton(onClick: () -> Unit) { IconButton(onClick = onClick) { Icon( imageVector = Icons.Filled.Share, contentDescription = stringResource(R.string.label_share) ) } }
Algunos elementos gráficos son meramente decorativos y es posible que no quieras comunicárselos al usuario. Cuando configuras el parámetro contentDescription como null, le indicas al framework de Android que este elemento no tiene acciones ni estados asociados.
@Composable private fun PostImage(post: Post, modifier: Modifier = Modifier) { val image = post.imageThumb ?: painterResource(R.drawable.placeholder_1_1) Image( painter = image, // Specify that this image has no semantic meaning contentDescription = null, modifier = modifier .size(40.dp, 40.dp) .clip(MaterialTheme.shapes.small) ) }
contentDescription se usa principalmente para elementos gráficos, como imágenes. Los componentes de Material, como Button o Text, y los comportamientos que admiten acciones, como clickable o toggleable, incluyen otras semánticas predefinidas que describen su comportamiento intrínseco y se pueden cambiar a través de otras APIs de Compose.
Elementos interactivos
Las APIs de Material y Foundation Compose crean elementos de la IU con los que los usuarios pueden interactuar a través de las APIs de modificadores clickable y toggleable. Debido a que los componentes interactivos pueden constar de varios elementos, clickable y toggleable combinan la semántica de sus elementos secundarios de forma predeterminada, de modo que el componente se trate como una entidad lógica.
Por ejemplo, un Button de Material puede consistir en un ícono secundario y un texto. En lugar de tratar a los elementos secundarios como individuales, un Button de Material combina su semántica de forma predeterminada, de modo que los servicios de accesibilidad puedan agruparlos según corresponda:
Del mismo modo, el uso del modificador clickable también hace que un elemento componible combine la semántica de sus descendientes en una sola entidad, que se envía a los servicios de accesibilidad con una representación de acción correspondiente:
Row( // Uses `mergeDescendants = true` under the hood modifier = Modifier.clickable { openArticle() } ) { Icon( painter = painterResource(R.drawable.ic_logo), contentDescription = "Open", ) Text("Accessibility in Compose") }
También puedes establecer un onClickLabel específico en el elemento en el que se puede hacer clic superior para proporcionar información adicional a los servicios de accesibilidad y ofrecer una representación más refinada de la acción:
Row( modifier = Modifier .clickable(onClickLabel = "Open this article") { openArticle() } ) { Icon( painter = painterResource(R.drawable.ic_logo), contentDescription = "Open" ) Text("Accessibility in Compose") }
Si usamos TalkBack como ejemplo, este modificador clickable y su etiqueta de clic permitirían que TalkBack proporcione una sugerencia de acción de "Presiona dos veces para abrir este artículo", en lugar de los comentarios predeterminados más genéricos de "Presiona dos veces para activar".
Estos comentarios cambian según el tipo de acción. Un clic largo proporcionaría una sugerencia de TalkBack de "Presiona dos veces y mantén presionado para", seguida de una etiqueta:
Row( modifier = Modifier .combinedClickable( onLongClickLabel = "Bookmark this article", onLongClick = { addToBookmarks() }, onClickLabel = "Open this article", onClick = { openArticle() }, ) ) {}
En algunos casos, es posible que no tengas acceso directo al modificador clickable (por ejemplo, cuando se establece en algún lugar de una capa anidada inferior),pero que quieras cambiar la etiqueta de anuncio predeterminada. Para ello, divide la configuración de clickable de la modificación del anuncio con el modificador semantics y establece la etiqueta de clic allí para modificar la representación de la acción:
@Composable private fun ArticleList(openArticle: () -> Unit) { NestedArticleListItem( // Clickable is set separately, in a nested layer: onClickAction = openArticle, // Semantics are set here: modifier = Modifier.semantics { onClick( label = "Open this article", action = { // Not needed here: openArticle() true } ) } ) }
No es necesario que pases la acción de clic dos veces. Las APIs de Compose existentes, como clickable o Button, se encargan de esto por ti. La lógica de combinación verifica que se tomen la etiqueta y la acción del modificador más externo para la información presente. En el ejemplo anterior, NestedArticleListItem pasa automáticamente la acción de clic de openArticle() a su semántica de clickable. Puedes dejar la acción de clic nula en la segunda acción del modificador de semántica. Sin embargo, la etiqueta de clic se toma del segundo modificador de semántica onClick(label = "Open this document") porque no estaba presente en el primero.
Es posible que te encuentres con situaciones en las que esperas que la semántica secundaria se combine con una superior, pero eso no sucede. Consulta Cómo combinar y borrar para obtener información más detallada.
Componentes personalizados
Cuando compiles un componente personalizado, revisa la implementación de un componente similar en la biblioteca de Material o en otras bibliotecas de Compose. Luego, imita o modifica su comportamiento de accesibilidad según corresponda. Por ejemplo, si reemplazas el Checkbox de Material por tu propia implementación, observar la implementación de Checkbox existente te recordará que debes agregar el modificador triStateToggleable, que controla las propiedades de accesibilidad del componente. Además, usa mucho los modificadores de Foundation, ya que incluyen consideraciones de accesibilidad integradas y prácticas de Compose existentes que se tratan en esta sección.
También puedes encontrar un ejemplo de un componente de activación personalizado en la sección Borra y establece la semántica, así como información más detallada sobre cómo admitir la accesibilidad en componentes personalizados en los lineamientos de la API.
Recomendaciones para ti
- Nota: El texto del vínculo se muestra cuando JavaScript está desactivado
- Accesibilidad en Compose
- Cómo probar tu diseño de Compose