Valores predeterminados de la API

Las APIs de Material, Compose UI y Foundation implementan y ofrecen muchas prácticas accesibles de forma predeterminada. Contienen una 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 adecuado 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 cubrir 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 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.

Existen componentes en Material, como Checkbox, RadioButton, Switch, Slider y Surface, que establecen este tamaño mínimo de manera interna, pero solo cuando el componente puede recibir acciones del usuario. Por ejemplo, si un 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 un alto 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: Es una casilla de verificación con relleno 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)
}

Casilla de verificación sin padding.
Figura 2: 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 principal configurando la devolución de llamada de clics en el elemento componible a null y agregando un modificador toggleable o selectable al elemento componible principal.

@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 deselecciona.
Figura 3: Es una casilla de verificación con comportamiento de 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 táctil 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)
        )
    }
}

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 al cuadro.
Figura 4: 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 significaría usar el modificador sizeIn para establecer el tamaño mínimo de la casilla interna:

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

La caja muy pequeña del ejemplo anterior se agranda para crear un objetivo táctil más grande.
Figura 5: Un objetivo táctil de caja más grande.

Elementos gráficos

Cuando defines un elemento Image o Icon componible, 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 tira 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 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 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 está diseñado principalmente para usarse con 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. Dado 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 sola entidad lógica.

Por ejemplo, un Button de Material podría constar de un ícono secundario y algo de 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:

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

Del mismo modo, usar el modificador clickable también hace que un elemento componible combine la semántica de sus elementos secundarios 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 puede hacer clic para proporcionar información adicional a los servicios de accesibilidad y ofrecer una representación más pulida 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 del tipo "Presiona dos veces para abrir este artículo", en lugar de la respuesta predeterminada más genérica de "Presiona dos veces para activar".

Estos comentarios cambian según el tipo de acción. Si se mantiene presionado, se proporcionará una sugerencia de TalkBack que diga "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 aun así quieras cambiar la etiqueta del anuncio del valor predeterminado. 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 la etiqueta y la acción del modificador más externo se apliquen a la información presente. En el ejemplo anterior, el NestedArticleListItem pasa automáticamente la acción de clic openArticle() a su semántica clickable. Puedes dejar la acción de clic como nula en la segunda acción del modificador de semántica. Sin embargo, la etiqueta de clic se toma del segundo modificador semántico onClick(label = "Open this document") porque no estaba presente en el primero.

Es posible que te encuentres con situaciones en las que esperes que la semántica de los elementos secundarios se combine con la de un elemento principal, pero eso no sucede. Consulta Combinación y borrado 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 objeto Checkbox de Material con tu propia implementación, consultar la implementación existente de Checkbox te recordará que debes agregar el modificador triStateToggleable, que controla las propiedades de accesibilidad del componente. Además, usa activamente los modificadores de Foundation, ya que incluyen consideraciones de accesibilidad integradas y prácticas existentes de Compose que se abordan 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.