Valores predeterminados de la API

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 = {})
}

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, no se incluye el padding, ya que no se puede interactuar con el componente directamente.

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

Una casilla de verificación junto al texto "Opción" que se selecciona y se deselecciona.
Figura 3: Es 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 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)
        )
    }
}

Una casilla en la que se puede hacer clic muy pequeña que se expande a un objetivo táctil más grande cuando se presiona junto a ella.
Figura 4: Una casilla en la que se puede hacer clic muy pequeña que se expande a un objetivo táctil más grande.

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

Se aumentó el tamaño de la caja muy pequeña del ejemplo anterior para crear un objetivo táctil más grande.
Figura 5: Un objetivo táctil de cuadro más grande

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:

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

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:

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, 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.