A medida que los servicios de accesibilidad navegan por los elementos de la pantalla, es importante que estos elementos se agrupen, se separen o incluso se oculten con el nivel de detalle adecuado. Cuando cada elemento de componibilidad de bajo nivel de tu pantalla se destaca de forma independiente, los usuarios deben interactuar mucho para moverse por la pantalla. Si los elementos se fusionan de forma demasiado agresiva, es posible que los usuarios no comprendan qué elementos pertenecen lógicamente entre sí. Si hay elementos en la pantalla que son puramente decorativos, estos podrían ocultarse de los servicios de accesibilidad. En estos casos, puedes usar las APIs de Compose para combinar, borrar y ocultar la semántica.
Semántica de combinación
Cuando aplicas un modificador clickable a un elemento componible superior, Compose combina automáticamente todos los elementos secundarios que se encuentran debajo de él. Para comprender cómo
los componentes interactivos de Compose Material y Foundation usan estrategias de combinación
de forma predeterminada, consulta la sección Elementos interactivos.
Es común que un componente conste de varios elementos componibles. Estos elementos componibles podrían formar un grupo lógico y cada uno podría contener información importante, pero es posible que quieras que los servicios de accesibilidad los vean como un solo elemento.
Por ejemplo, piensa en un elemento componible que muestre el avatar de un usuario, su nombre y cierta información adicional:
Puedes habilitar Compose para que combine estos elementos con el parámetro mergeDescendants en el modificador de semántica. De esta manera, los servicios de accesibilidad tratan el componente como una entidad, y se combinan todas las propiedades de semántica de los descendientes:
@Composable private fun PostMetadata(metadata: Metadata) { // Merge elements below for accessibility purposes Row(modifier = Modifier.semantics(mergeDescendants = true) {}) { Image( imageVector = Icons.Filled.AccountCircle, contentDescription = null // decorative ) Column { Text(metadata.author.name) Text("${metadata.date} • ${metadata.readTimeMinutes} min read") } } }
Los servicios de accesibilidad ahora se enfocan en todo el contenedor a la vez y combinan su contenido:
Cada propiedad semántica tiene una estrategia de combinación definida. Por ejemplo, la propiedad ContentDescription agrega todos los valores ContentDescription descendientes a una lista. Para verificar la estrategia de combinación de una propiedad de semántica,
comprueba su implementación de mergePolicy en SemanticsProperties.kt.
Las propiedades pueden tomar el valor superior o secundario, combinar los valores en una lista
o cadena, no permitir la combinación y, en su lugar, arrojar una excepción, o cualquier
otra estrategia de combinación personalizada.
Hay otras situaciones en las que esperas que la semántica secundaria se combine con una superior, pero eso no sucede. En el siguiente ejemplo, tenemos un elemento superior de la lista clickable con elementos secundarios, y podríamos esperar que el elemento superior los combine todos:
@Composable private fun ArticleListItem( openArticle: () -> Unit, addToBookmarks: () -> Unit, ) { Row(modifier = Modifier.clickable { openArticle() }) { // Merges with parent clickable: Icon( painter = painterResource(R.drawable.ic_logo), contentDescription = "Article thumbnail" ) ArticleDetails() // Defies the merge due to its own clickable: BookmarkButton(onClick = addToBookmarks) } }
Cuando el usuario presiona el elemento clickable Row, se abre el artículo. Anidado en su interior, hay un BookmarkButton para agregar el artículo a favoritos. Este botón de Nest anidado aparece como no combinado, mientras que el resto del contenido secundario dentro de la fila está combinado:
Row. El árbol sin combinar contiene nodos separados para cada elemento de componibilidad Text.Por diseño, algunos elementos de componibilidad no se combinan automáticamente en un elemento superior. Un elemento superior no puede combinar sus elementos secundarios cuando estos también se combinan, ya sea configurando mergeDescendants = true de forma explícita o siendo componentes que se combinan por sí mismos, como botones o elementos en los que se puede hacer clic. Saber cómo se combinan o no ciertas APIs puede ayudarte a depurar algunos comportamientos potencialmente inesperados.
Usa la combinación cuando los elementos secundarios constituyan un grupo lógico y sensato
en su elemento superior. Sin embargo, si los elementos secundarios anidados necesitan un ajuste manual o la eliminación de su propia semántica, es posible que otras APIs se adapten mejor a tus necesidades (por ejemplo, clearAndSetSemantics).
Cómo borrar y establecer la semántica
Si la información semántica se debe borrar o reemplazar por completo, una API potente que puedes usar es clearAndSetSemantics.
Cuando un componente necesite que se borre su propia semántica y la de sus descendientes, usa esta API con una expresión lambda vacía. Cuando se deba reemplazar su semántica, incluye tu contenido nuevo dentro de la lambda.
Ten en cuenta que, cuando se borra con una lambda vacía, la semántica borrada no se envía a ningún consumidor que use esta información, como la accesibilidad, el autocompletado o las pruebas. Cuando se reemplaza el contenido por
clearAndSetSemantics{/*semantic information*/}, la semántica nueva reemplaza toda
la semántica anterior del elemento y sus descendientes.
A continuación, se muestra un ejemplo de un componente de activación personalizado, representado por una fila interactiva con un ícono y texto:
// Developer might intend this to be a toggleable. // Using `clearAndSetSemantics`, on the Row, a clickable modifier is applied, // a custom description is set, and a Role is applied. @Composable fun FavoriteToggle() { val checked = remember { mutableStateOf(true) } Row( modifier = Modifier .toggleable( value = checked.value, onValueChange = { checked.value = it } ) .clearAndSetSemantics { stateDescription = if (checked.value) "Favorited" else "Not favorited" toggleableState = ToggleableState(checked.value) role = Role.Switch }, ) { Icon( imageVector = Icons.Default.Favorite, contentDescription = null // not needed here ) Text("Favorite?") } }
Aunque el ícono y el texto tienen cierta información semántica, juntos no indican que este componente se puede activar o desactivar. La combinación no es suficiente porque debes proporcionar información adicional sobre el componente.
Como el fragmento anterior crea un componente de activación personalizado, debes agregar la capacidad de activación, así como la semántica de stateDescription, toggleableState y role. De esta manera, el estado del componente y la acción asociada están disponibles; por ejemplo, TalkBack anuncia "Presiona dos veces para activar o desactivar" en lugar de "Presiona dos veces para activar".
Si borras la semántica original y estableces una nueva más descriptiva, los servicios de accesibilidad ahora pueden ver que se trata de un componente que se puede activar o desactivar y que puede alternar el estado.
Cuando uses clearAndSetSemantics, ten en cuenta lo siguiente:
- Debido a que los servicios no reciben información cuando se configura esta API, es mejor
usarla con moderación.
- Los agentes de IA y servicios similares pueden usar la información de semántica para comprender la pantalla y, por lo tanto, solo se debe borrar cuando sea necesario.
- Se puede establecer una semántica personalizada dentro de la lambda de la API.
- El orden de los modificadores es importante: esta API borra toda la semántica que se encuentra después de donde se aplica, independientemente de otras estrategias de combinación.
Oculta la semántica
En algunas situaciones, no es necesario enviar elementos a los servicios de accesibilidad, tal vez porque su información adicional es redundante para la accesibilidad o porque es puramente decorativa y no interactiva. En estos casos, puedes
ocultar elementos con la API de hideFromAccessibility.
En los siguientes ejemplos, se muestran componentes que podrían necesitar ocultarse: una marca de agua redundante que abarca un componente y un carácter que se usa para separar información de forma decorativa.
@Composable fun WatermarkExample( watermarkText: String, content: @Composable () -> Unit, ) { Box { WatermarkedContent() // Mark the watermark as hidden to accessibility services. WatermarkText( text = watermarkText, color = Color.Gray.copy(alpha = 0.5f), modifier = Modifier .align(Alignment.BottomEnd) .semantics { hideFromAccessibility() } ) } } @Composable fun DecorativeExample() { Text( modifier = Modifier.semantics { hideFromAccessibility() }, text = "A dot character that is used to decoratively separate information, like •" ) }
Usar hideFromAccessibility aquí garantiza que la marca de agua y la decoración se oculten de los servicios de accesibilidad, pero que conserven su semántica para otros casos de uso, como las pruebas.
Desglose de casos de uso
A continuación, se presenta un resumen de los casos de uso para comprender cómo diferenciar claramente las APIs anteriores:
- Cuando el contenido no está destinado a los servicios de accesibilidad:
- Usa
hideFromAccessibilitycuando el contenido sea posiblemente decorativo o redundante, pero aún deba probarse. - Usa
clearAndSetSemantics{}con una expresión lambda vacía cuando se deban borrar las semánticas de elementos superiores y secundarios para todos los servicios. - Usa
clearAndSetSemantics{/*content*/}con contenido dentro de la expresión lambda cuando la semántica de un componente deba establecerse de forma manual.
- Usa
- Cuando el contenido se deba tratar como una sola entidad y necesite toda la información de sus elementos secundarios para estar completo:
- Usa descendientes semánticos de combinación.
Recomendaciones para ti
- Nota: El texto del vínculo se muestra cuando JavaScript está desactivado
- Accesibilidad en Compose
- [Material Design 2 en Compose][19]
- Cómo probar tu diseño de Compose