Valores predeterminados de la API

Las APIs de Material, Compose UI y Foundation implementan y ofrecen muchas prácticas de accesibilidad de forma predeterminada. Contienen semánticas integradas que siguen su rol y función específicos, lo que significa que la mayor parte de la compatibilidad con la accesibilidad se proporciona con poco o ningún trabajo adicional.

El uso de las APIs adecuadas para el propósito adecuado suele significar que los componentes incluyen comportamientos de accesibilidad predefinidos que abarcan casos de uso estándar, pero recuerda verificar si estos valores predeterminados se ajustan a tus necesidades de accesibilidad. De lo contrario, Compose también proporciona formas de abordar requisitos más específicos.

Conocer la semántica y los patrones de accesibilidad predeterminados en las APIs de Compose ayuda a comprender cómo usarlos teniendo en cuenta la accesibilidad, así como a admitir la accesibilidad en componentes más personalizados.

Tamaños mínimos del objetivo táctil

Todos los elementos de la pantalla en los que se puede hacer clic, que se pueden tocar o con los que se puede interactuar deben ser lo suficientemente grandes para permitir una interacción confiable. Cuando establezcas el tamaño de esos elementos, asegúrate de configurar su tamaño mínimo en 48 dp para seguir 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 manera interna, pero solo cuando el componente puede recibir acciones del usuario. Por ejemplo, si Checkbox tiene su parámetro onCheckedChange establecido en un valor no nulo, la casilla de verificación incluye un padding para tener un ancho y una altura de 48 dp como mínimo.

@Composable
private fun CheckableCheckbox() {
    Checkbox(checked = true, onCheckedChange = {})
}

Una casilla de verificación con el padding predeterminado con un ancho y una altura de 48 dp.
Figura 1: Una casilla de verificación con padding predeterminado.

Cuando el parámetro onCheckedChange se establece en nulo, el padding no se incluye, ya que no se puede interactuar directamente con el componente.

@Composable
private fun NonClickableCheckbox() {
    Checkbox(checked = true, onCheckedChange = null)
}

Una casilla de verificación que no tiene padding
Figura 2: Una casilla de verificación sin padding.

Cuando implementas controles de selección como Switch, RadioButton o Checkbox, por lo general, quitas el comportamiento de hacer clic en un contenedor superior, configuras la devolución de llamada de clics en el elemento componible a null y agregas un modificador toggleable o selectable al elemento superior que admite composición.

@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)
        }
    }
}

Una casilla de verificación junto al texto "Opción" que se selecciona y se anula.
Figura 3: Una casilla de verificación con comportamiento en el que se puede hacer clic.

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 fuera de los límites del elemento componible.

El siguiente ejemplo contiene un objeto Box muy pequeño para hacer clic. El área del objetivo táctil se expande automáticamente más allá de los límites de Box, por lo que, cuando se presione junto a Box, se activará 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)
        )
    }
}

Es un cuadro muy pequeño en el que se puede hacer clic y que se expande a un objetivo táctil más grande cuando se presiona junto a él.
Figura 4: Es un cuadro muy pequeño en el que se puede hacer clic y que se expande a un objetivo táctil más grande.

Para evitar una posible superposición entre áreas táctiles de diferentes elementos componibles, siempre usa un tamaño mínimo lo suficientemente grande para el elemento componible. En el ejemplo, eso implicaría usar el modificador sizeIn para establecer el tamaño mínimo del cuadro interno:

@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)
        )
    }
}

El cuadro muy pequeño del ejemplo anterior aumenta de tamaño para crear un objetivo táctil más grande.
Figura 5: Un objetivo táctil de cuadro más grande.

Elementos gráficos

Cuando defines un elemento componible Image o Icon, no existe una manera 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:

Una barra de cuatro íconos en los que se puede hacer clic, con el ícono para compartir resaltado
Figura 6: Una fila de íconos en los que se puede hacer clic con el ícono "Compartir" seleccionado.

Solamente en función del í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 el usuario la puede ver.

@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 puramente 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 ese elemento no tiene acciones ni estado 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 diseñó principalmente para usarse en elementos gráficos, como imágenes. Los componentes de Material, como Button o Text, y los comportamientos prácticos, 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 las semánticas 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 constar de un ícono secundario y un poco de texto. En lugar de tratar a los elementos secundarios como elementos individuales, un botón de Material combina su semántica secundaria de forma predeterminada, de modo que los servicios de accesibilidad puedan agruparlos de la siguiente manera:

Botones con semántica de elementos secundarios combinados y no combinados.
Figura 7: Botones con semántica de elementos secundarios combinados y no combinados.

De manera similar, usar el 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 superior en el que se pueda hacer clic 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")
}

Tomando como ejemplo TalkBack, 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 del mensaje predeterminado más genérico 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 "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 aún así quieras cambiar la etiqueta de anuncio de la 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
                }
            )
        }
    )
}

En este caso, no necesitas pasar la acción de clic dos veces, ya que las APIs existentes de Compose, como clickable o Button, lo controlan por ti. Esto se debe a que la lógica de combinación garantiza que se tomen la etiqueta y la acción de modificador más externos para la información que está presente.

En el ejemplo anterior, NestedArticleListItem pasa automáticamente la acción de clic openArticle() a su semántica clickable y se puede dejar nula en la segunda acción de modificador de semántica. Sin embargo, la etiqueta de clic se toma del segundo modificador de semántica onClick(label = "Open this article"), ya que no estaba presente en el primero.

Es posible que te encuentres con situaciones en las que esperas que la semántica de los elementos secundarios se combine en una superior, pero eso no sucede. Consulta Cómo combinar y borrar para obtener información más detallada.

Componentes personalizados

En el caso de los componentes personalizados, como regla general, observa la implementación de un componente similar en la biblioteca de Material o en otras bibliotecas de Compose, y fíjate en su comportamiento de accesibilidad para imitarlo o modificarlo cuando sea conveniente.

Por ejemplo, si reemplazas el objeto Checkbox de Material con tu propia implementación, observar la implementación existente de la casilla de verificación te recordará que debes agregar el modificador triStateToggleable, que controla las propiedades de accesibilidad de este componente.

Además, usa activamente los modificadores de Foundation, ya que incluyen consideraciones de accesibilidad de forma inmediata, así como las prácticas existentes de Compose que se abordan en esta sección.

También puedes encontrar un ejemplo de un componente de botón de activación personalizado en la sección de semántica clara y establecida, así como información más detallada sobre cómo admitir accesibilidad en componentes personalizados en los lineamientos de la API.