Semántica en Compose

Una composición describe la IU de tu app y se produce ejecutando elementos componibles. La composición es una estructura de árbol que consta de los elementos elementos componibles que describen tu IU.

Junto a la composición, existe un árbol paralelo denominado semántica árbol. En este árbol se describe tu IU de una manera alternativa que se comprensible para los servicios de accesibilidad y para el curso Testing en un framework de aplicaciones. Los servicios de accesibilidad usan el árbol para describir la app a los usuarios con una necesidad específica. El framework de pruebas usa el árbol para interactuar con tu app y hacer aserciones al respecto. El árbol semántico no contiene las información para dibujar tus elementos componibles, pero contiene información sobre el significado semántico de tus elementos componibles.

Una jerarquía de IU típica y su árbol semántico
Figura 1: Una jerarquía de IU típica y su árbol semántico

Si la app se conforma de elementos que admiten composición y modificadores de la biblioteca base y Material de Compose, el árbol semántico se completará y generará automáticamente. Sin embargo, cuando agregas elementos componibles personalizados de bajo nivel, tienes para proporcionar su semántica de forma manual. También, en algunas situaciones, es posible que el árbol no represente de forma correcta o completa el significado de los elementos en la pantalla. En este caso, puedes adaptar el árbol.

Por ejemplo, ten en cuenta este calendario personalizado que admite composición:

Un calendario personalizado componible con elementos de día seleccionables
Figura 2: Calendario personalizado componible con elementos de día seleccionables

En este ejemplo, todo el calendario se implementa como un solo elemento que admite composición de bajo nivel si se usa el objeto Layout que admite composición y se dibuja directamente en Canvas. Si no realizas ninguna otra acción, los servicios de accesibilidad no recibirán suficientes información sobre el contenido del elemento componible y la selección del usuario dentro el calendario. Por ejemplo, si un usuario hace clic en el día que contiene 17, el marco de trabajo de accesibilidad solamente recibe la información de descripción de todo el control de calendario. En este caso, el servicio de accesibilidad TalkBack anunciar "Calendario" o, mejor, "Calendario de abril". y el usuario estaría preguntándose qué día se seleccionó. Para que este elemento componible sea más accesible, deberás agregar información semántica de forma manual.

Propiedades semánticas

Todos los nodos en el árbol de IU con algún significado semántico tienen un nodo paralelo en el árbol semántico. El nodo en el árbol semántico incluye esas propiedades que transmiten el significado del elemento correspondiente que admite composición. Por ejemplo, Text. componible contiene una propiedad semántica text, porque ese es el significado de ese elemento componible. Un Icon contiene una propiedad contentDescription (si lo establece el desarrollador) que transmite por texto el significado de Icon. Elementos componibles y modificadores que se compilan sobre la base de Compose existente ya establecieron las propiedades relevantes por ti. Opcionalmente, establecer o anula las propiedades tú mismo con semantics y clearAndSetSemantics. Por ejemplo, agrega datos de accesibilidad a un nodo, proporcionan un estado alternativo descripción de un elemento que se puede activar o desactivar, o bien indicar que cierto texto elemento componible debe considerarse como un encabezado.

Para visualizar el árbol semántico, usa la herramienta Inspector de diseño o la printToLog() en las pruebas. Esto imprime la información Árbol semántico dentro de Logcat

class MyComposeTest {

    @get:Rule
    val composeTestRule = createComposeRule()

    @Test
    fun MyTest() {
        // Start the app
        composeTestRule.setContent {
            MyTheme {
                Text("Hello world!")
            }
        }
        // Log the full semantics tree
        composeTestRule.onRoot().printToLog("MY TAG")
    }
}

El resultado de esta prueba sería el siguiente:

    Printing with useUnmergedTree = 'false'
    Node #1 at (l=0.0, t=63.0, r=221.0, b=120.0)px
     |-Node #2 at (l=0.0, t=63.0, r=221.0, b=120.0)px
       Text = '[Hello world!]'
       Actions = [GetTextLayoutResult]

Ten en cuenta cómo las propiedades semánticas transmiten el significado de un elemento componible. Consider a Switch El usuario lo verá de la siguiente manera:

Figura 3: Un interruptor en su estado "Activado" y "Desactivado" estado...

Para describir el significado de este elemento, podrías decir lo siguiente: "Este es un interruptor, que es un elemento que se puede activar o desactivar en su estado para cada estado. Puedes hacer clic en ella interactuar con ella".

Las propiedades semánticas se usan exactamente para este fin. El nodo semántico de este elemento Switch contiene las siguientes propiedades, como se visualizan con el Inspector de diseño:

El Inspector de diseño muestra las propiedades semánticas de un elemento Switch componible.
Figura 4: El Inspector de diseño muestra las propiedades semánticas de un elemento Switch componible.

El Role indica el tipo de elemento. En el StateDescription, se describe cómo el botón de encendido se debe hacer referencia a ese estado. Por defecto, es una versión localizada del la palabra "Activado", pero se puede hacer más específica (por ejemplo, "Habilitada") en función según el contexto. ToggleableState es el estado actual del interruptor. El La propiedad OnClick hace referencia al método que se usa para interactuar con este elemento. Para una lista completa de las propiedades semánticas, consulta la SemanticsProperties . Para obtener una lista completa de las acciones de accesibilidad posibles, consulta el objeto SemanticsActions.

Hacer un seguimiento de las propiedades semánticas de cada elemento que admite composición en la app ofrece muchas posibilidades potentes. Estos son algunos ejemplos:

  • TalkBack usa las propiedades para leer en voz alta lo que se muestra en la pantalla y permite al usuario interactuar con él sin problemas. Para el elemento Switch componible, es posible que TalkBack diga: "Activado; Switch; presiona dos veces para activar o desactivar". El usuario puede presionar dos veces su para desactivarlo.
  • El marco de trabajo de prueba usa las propiedades para encontrar nodos, interactuar con ellos y realizar aserciones. Una muestra de prueba para el Switch podría ser la siguiente:
    val mySwitch = SemanticsMatcher.expectValue(
        SemanticsProperties.Role, Role.Switch
    )
    composeTestRule.onNode(mySwitch)
        .performClick()
        .assertIsOff()

Árbol semántico combinado y separado

Como se mencionó antes, es posible que cada elemento que admite composición en el árbol de IU no tenga propiedades semánticas establecidas o que sí las tenga. Cuando un elemento componible no tiene propiedades semánticas establecidas, no se incluye como parte del árbol semántico. De esa manera, el árbol semántico solamente incluye los nodos que, en realidad, tienen significado semántico. Sin embargo, con frecuencia, para transmitir el significado correcto de lo que se muestra en la pantalla, también es útil combinar subárboles determinados de nodos y tratarlos como uno solo. De esa manera puedes pensar en un conjunto de nodos como un todo, en lugar de ocuparte de cada su nodo subordinado de forma individual. Como regla general, cada nodo de este árbol representa un elemento enfocable cuando se usan los servicios de accesibilidad.

Un ejemplo de este tipo de elemento componible es Button. Puedes razonar sobre un botón como un solo elemento, aunque pueda contener varios nodos secundarios:

Button(onClick = { /*TODO*/ }) {
    Icon(
        imageVector = Icons.Filled.Favorite,
        contentDescription = null
    )
    Spacer(Modifier.size(ButtonDefaults.IconSpacing))
    Text("Like")
}

En el árbol semántico, se combinan las propiedades de los elementos subordinados del botón. y el botón se presenta como un nodo de hoja única en el árbol:

Representación semántica de hoja única combinada
Figura 5: Representación semántica de hoja combinada.

Los elementos que admiten composición y los modificadores pueden indicar que quieren combinar las propiedades semánticas de sus elementos subordinados mediante una llamada a Modifier.semantics (mergeDescendants = true) {}. Establecer esta propiedad en true indica que se deben combinar las propiedades semánticas. En el ejemplo de Button, Button componible usa el modificador clickable de forma interna que incluye este Modificador semantics. Por lo tanto, se combinan los nodos subordinados del botón. Lee la documentación sobre accesibilidad para obtener más información sobre cuándo deberías cambiar de combinación en tu elemento componible.

Varios modificadores y elementos que admiten composición en las bibliotecas base y material de Compose tienen esta propiedad establecida. Por ejemplo, los modificadores clickable y toggleable combinarán automáticamente sus elementos subordinados. El elemento ListItem que admite composición también los combinará.

Inspecciona los árboles

De hecho, el árbol semántico son dos árboles diferentes. Hay una semántica combinada , que combina nodos subordinados cuando mergeDescendants se establece en true También hay un árbol semántico separado, que no aplica la combinación, pero mantiene todos los nodos intactos. Los servicios de accesibilidad usan el árbol separado y aplican sus propios algoritmos de combinación, teniendo en cuenta mergeDescendants propiedad. El framework de prueba usa el árbol combinado de forma predeterminada.

Puedes inspeccionar ambos árboles con el método printToLog(). De forma predeterminada, y en los ejemplos anteriores, se registra el árbol combinado. Para imprimir el árbol separado en su lugar, establece el parámetro useUnmergedTree del comparador onRoot() true:

composeTestRule.onRoot(useUnmergedTree = true).printToLog("MY TAG")

El Inspector de diseño te permite mostrar la semántica combinada y la separada. seleccionando el preferido en el filtro de vista:

Opciones de vista del Inspector de diseño, que permiten mostrar el árbol semántico combinado y el separado.
Figura 6: Opciones de vista del Inspector de diseño, que permiten mostrar el árbol semántico combinado y el separado.

Para cada nodo del árbol, el Inspector de diseño muestra la semántica combinada y la que se establece en ese nodo en el panel de propiedades:

Se combinaron y configuraron las propiedades semánticas
Figura 7: Se combinaron y configuraron las propiedades semánticas.

De forma predeterminada, los comparadores en el marco de trabajo de prueba usan el árbol semántico combinado. Por eso, puedes interactuar con un Button haciendo coincidir el texto que se muestra dentro de la siguiente manera:

composeTestRule.onNodeWithText("Like").performClick()

Puedes anular este comportamiento configurando el parámetro useUnmergedTree del comparadores a true, al igual que con el comparador onRoot.

Comportamiento de combinación

Cuando un elemento que admite composición indica que sus elementos subordinados deben combinarse, ¿cómo ocurre exactamente la combinación?

Cada propiedad semántica tiene una estrategia de combinación definida. Por ejemplo, el La propiedad ContentDescription agrega todos los valores subordinados de ContentDescription a un lista. Para comprobar la estrategia de combinación de una propiedad semántica, revisa su Implementación de mergePolicy en SemanticsProperties.kt. Las propiedades pueden tomar el valor superior o secundario, combinar los valores en una lista o una cadena, no permitir combinaciones y, en su lugar, arroja una excepción, o cualquier otra una estrategia de combinación personalizada.

Una nota importante es que los elementos subordinados que hayan configurado mergeDescendants = true por su cuenta no se incluyen en la combinación. Veamos un ejemplo:

Elemento de la lista con imagen, texto y un ícono de favorito
Figura 8: Elemento de la lista con imagen, un poco de texto y un ícono de favorito.

A continuación, se muestra un elemento de lista en el que se puede hacer clic. Cuando el usuario presiona la fila, la app navega a la página de detalles del artículo, en la que el usuario puede leerlo. Dentro del elemento de la lista, hay un botón para agregar el artículo a favoritos, que se forma un elemento anidado en el que se puede hacer clic, de modo que el botón aparezca por separado en el árbol combinado. Se combina el resto del contenido en la fila:

El árbol combinado incluye varios textos en una lista dentro del nodo Row El árbol separado incluye nodos que no están combinados para cada elemento Text que admite composición.
Figura 9: El árbol combinado incluye varios textos en una lista dentro del nodo Row El árbol separado contiene nodos independientes para cada elemento Text componible.

Adapta el árbol semántico

Como se mencionó antes, puedes anular o borrar propiedades semánticas o cambiar el comportamiento de combinación del árbol. Esto es particularmente relevante cuando estás creando tus propios componentes personalizados. Sin establecer la configuración correcta y el comportamiento de combinación, es posible que no se pueda acceder a tu app y que las pruebas comportan de manera diferente a lo que esperabas. Para leer más sobre algunos casos de uso comunes dónde debes adaptar el árbol semántico, lee el artículo de accesibilidad documentación. Si deseas obtener más información sobre las pruebas, consulta el Centro de de la guía de YouTube.

Recursos adicionales