Semântica

Além das informações principais que um elemento combinável carrega, como uma string de texto de um elemento combinável Text, pode ser útil ter mais informações complementares sobre os elementos da IU.

As informações sobre o significado e a função de um componente no Compose são chamadas de semântica, que são uma maneira de fornecer mais contexto sobre elementos combináveis para serviços como acessibilidade, preenchimento automático e teste. Por exemplo, um ícone de câmera pode ser visualmente apenas uma imagem, mas o significado semântico pode ser "Tirar uma foto".

Ao combinar a semântica adequada com as APIs do Compose, você oferece o máximo de informações possível sobre seu componente aos serviços de acessibilidade, que decidem como representá-lo para o usuário.

As APIs Material e Compose UI e Foundation vêm com semânticas integradas que seguem a função e a função específica, mas você também pode modificar essas semânticas para APIs existentes ou definir novas para componentes personalizados, de acordo com seus requisitos específicos.

Propriedades semânticas

As propriedades semânticas transmitem o significado do elemento combinável correspondente. Por exemplo, o elemento combinável Text contém uma propriedade semântica text, porque esse é o significado do elemento combinável. Um Icon contém uma propriedade contentDescription, se definida pelo desenvolvedor, que transmite em texto o significado do ícone.

Considere como as propriedades semânticas transmitem o significado de um elemento combinável. Considere um Switch. Para o usuário, ele tem esta aparência:

Figura 1. Um Switch nos estados "Ativado" e "Desativado".

Para descrever o significado desse elemento, você pode dizer o seguinte: "Esta é uma chave, um elemento alternável no estado 'Ativado'. Você pode clicar nele para interagir com ele."

É exatamente para isso que servem as propriedades semânticas. O nó semântico deste elemento de interruptor contém as seguintes propriedades, conforme visualizado com o Layout Inspector:

Layout Inspector mostrando as propriedades semânticas de um elemento combinável de interruptor
Figura 2. O Layout Inspector mostrando as propriedades semânticas de um elemento combinável Switch.

O Role indica o tipo de elemento. O StateDescription descreve como o estado "Ativado" precisa ser referenciado. Por padrão, essa descrição é uma versão localizada da palavra "Ativado", mas ela pode ser mais específica (por exemplo, "Ativado") com base no contexto. O ToggleableState é o estado atual do interruptor. A propriedade OnClick faz referência ao método usado para interagir com esse elemento.

O monitoramento das propriedades semânticas de cada elemento combinável no app resulta em muitas possibilidades eficientes:

  • Os serviços de acessibilidade usam as propriedades para representar a interface mostrada na tela e permitir que os usuários interajam com ela. Para o elemento combinável do interruptor, o TalkBack pode anunciar: "Ativado; Interruptor; toque duas vezes para ativar/desativar". O usuário pode tocar duas vezes na tela para desativar o interruptor.
  • O framework de testes usa as propriedades para encontrar nós, interagir com eles e fazer declarações:
    val mySwitch = SemanticsMatcher.expectValue(
        SemanticsProperties.Role, Role.Switch
    )
    composeTestRule.onNode(mySwitch)
        .performClick()
        .assertIsOff()

Os elementos combináveis e os modificadores criados com base na biblioteca de base do Compose já definem as propriedades relevantes para você por padrão. Você também pode mudar essas propriedades manualmente para melhorar o suporte à acessibilidade em casos de uso específicos ou mudar a estratégia de mesclagem ou limpeza dos elementos combináveis.

Para indicar o tipo de conteúdo específico do componente aos serviços de acessibilidade, é possível aplicar várias semânticas diferentes. Essas adições vão oferecer suporte às principais informações semânticas e ajudar os serviços de acessibilidade a ajustar como o componente é representado, anunciado ou interagido.

Para uma lista completa de propriedades semânticas, consulte o objeto SemanticsProperties. Para conferir uma lista completa das possíveis ações de acessibilidade, consulte o objeto SemanticsActions.

Títulos

Os apps geralmente têm telas com conteúdo rico em texto, como artigos longos ou páginas de notícias, que geralmente são divididas em diferentes seções com títulos:

Uma postagem de blog com texto de artigo em um contêiner de rolagem.
Figura 3. Uma postagem de blog com o texto do artigo em um contêiner de rolagem.

Os usuários com necessidades de acessibilidade podem ter dificuldades para navegar nessa tela com facilidade. Para melhorar a experiência de navegação, alguns serviços de acessibilidade permitem uma navegação mais fácil diretamente entre seções ou títulos. Para ativar isso, indique que o componente é um heading definindo a propriedade semântica:

@Composable
private fun Subsection(text: String) {
    Text(
        text = text,
        style = MaterialTheme.typography.headlineSmall,
        modifier = Modifier.semantics { heading() }
    )
}

Alertas e pop-ups

Se o componente for um alerta ou pop-up, como um Snackbar, você pode indicar aos serviços de acessibilidade que uma nova estrutura ou atualizações do conteúdo podem ser transmitidas aos usuários.

Componentes semelhantes a alertas podem ser marcados com a propriedade semântica liveRegion. Isso permite que os serviços de acessibilidade notifiquem automaticamente o usuário sobre mudanças nesse componente ou nos filhos dele:

PopupAlert(
    message = "You have a new message",
    modifier = Modifier.semantics {
        liveRegion = LiveRegionMode.Polite
    }
)

Use liveRegionMode.Polite na maioria dos casos em que a atenção dos usuários precisa ser atraída brevemente para alertas ou conteúdo importante em mudança na tela.

Use liveRegion.Assertive com moderação para evitar feedbacks prejudiciais. Ela deve ser usada em situações em que é crucial que os usuários sejam informados sobre conteúdo urgente:

PopupAlert(
    message = "Emergency alert incoming",
    modifier = Modifier.semantics {
        liveRegion = LiveRegionMode.Assertive
    }
)

As regiões em tempo real não devem ser usadas em conteúdo que é atualizado com frequência, como contadores regressivos, para evitar que os usuários sejam sobrecarregados com feedback constante.

Componentes semelhantes a janelas

Componentes personalizados semelhantes a janelas, semelhantes a ModalBottomSheet, precisam de outros sinais para se diferenciar do conteúdo ao redor. Para isso, use a semântica paneTitle, para que todas as mudanças relevantes de janela ou painel possam ser representadas adequadamente pelos serviços de acessibilidade, junto com as principais informações semânticas:

ShareSheet(
    message = "Choose how to share this photo",
    modifier = Modifier
        .fillMaxWidth()
        .align(Alignment.TopCenter)
        .semantics { paneTitle = "New bottom sheet" }
)

Para referência, consulte como o Material 3 usa paneTitle para os componentes.

Componentes de erros

Para outros tipos de conteúdo, como componentes semelhantes a erros, talvez seja necessário expandir as informações semânticas principais para usuários com necessidades de acessibilidade. Ao definir estados de erro, você pode informar os serviços de acessibilidade sobre a semântica error e fornecer mensagens de erro detalhadas.

Neste exemplo, o TalkBack lê as informações principais do texto de erro, seguidas por mensagens adicionais e detalhadas:

Error(
    errorText = "Fields cannot be empty",
    modifier = Modifier
        .semantics {
            error("Please add both email and password")
        }
)

Componentes de acompanhamento do progresso

Para componentes personalizados que rastreiam o progresso, é recomendável notificar os usuários sobre as mudanças de progresso, incluindo o valor atual, o intervalo e o tamanho da etapa. Isso pode ser feito com a semântica progressBarRangeInfo. Isso garante que os serviços de acessibilidade estejam cientes das mudanças de progresso e possam atualizar os usuários de acordo. Diferentes tecnologias adaptativas também podem ter maneiras únicas de sugerir o aumento e a diminuição da progressão.

ProgressInfoBar(
    modifier = Modifier
        .semantics {
            progressBarRangeInfo =
                ProgressBarRangeInfo(
                    current = progress,
                    range = 0F..1F
                )
        }
)

Informações de lista e item

Em listas e grades personalizadas com muitos itens, pode ser útil para os serviços de acessibilidade receberem informações mais detalhadas, como o número total de itens e índices.

Ao usar a semântica collectionInfo e collectionItemInfo na lista e nos itens, respectivamente, nessa lista longa, os serviços de acessibilidade podem informar aos usuários qual índice de item eles estão na coleção total, além de informações semânticas textuais:

MilkyWayList(
    modifier = Modifier
        .semantics {
            collectionInfo = CollectionInfo(
                rowCount = milkyWay.count(),
                columnCount = 1
            )
        }
) {
    milkyWay.forEachIndexed { index, text ->
        Text(
            text = text,
            modifier = Modifier.semantics {
                collectionItemInfo =
                    CollectionItemInfo(index, 0, 0, 0)
            }
        )
    }
}

Descrição do estado

Um elemento combinável pode definir uma stateDescription como semântica, que é usada pelo framework do Android para ler o estado em que o elemento se encontra. Por exemplo, um elemento combinável comutável pode estar em um estado "marcado" ou "desmarcado". Em alguns casos, convém substituir os rótulos padrão de descrição de estado usados pelo Compose. Isso pode ser feito especificando explicitamente os rótulos de descrição de estado antes de definir um elemento combinável como alternável:

@Composable
private fun TopicItem(itemTitle: String, selected: Boolean, onToggle: () -> Unit) {
    val stateSubscribed = stringResource(R.string.subscribed)
    val stateNotSubscribed = stringResource(R.string.not_subscribed)
    Row(
        modifier = Modifier
            .semantics {
                // Set any explicit semantic properties
                stateDescription = if (selected) stateSubscribed else stateNotSubscribed
            }
            .toggleable(
                value = selected,
                onValueChange = { onToggle() }
            )
    ) {
        /* ... */
    }
}

Ações personalizadas

As ações personalizadas podem ser usadas para gestos mais complexos da tela touchscreen, como deslizar para dispensar ou arrastar e soltar, já que eles podem ser difíceis de interagir para usuários com dificuldades motoras ou outras deficiências.

Para tornar o gesto de deslizar para descartar mais acessível, você pode vinculá-lo a uma ação personalizada, transmitindo a ação de descarte e o rótulo:

SwipeToDismissBox(
    modifier = Modifier.semantics {
        // Represents the swipe to dismiss for accessibility
        customActions = listOf(
            CustomAccessibilityAction(
                label = "Remove article from list",
                action = {
                    removeArticle()
                    true
                }
            )
        )
    },
    state = rememberSwipeToDismissBoxState(),
    backgroundContent = {}
) {
    ArticleListItem()
}

Um serviço de acessibilidade, como o TalkBack, destaca o componente e sugere que há mais ações disponíveis no menu, representando a ação de deslizar para excluir:

Visualização do menu de ações do TalkBack
Figura 4. Visualização do menu de ação do TalkBack.

Outro caso de uso para ações personalizadas são listas longas com itens que têm mais ações disponíveis, já que pode ser tedioso para os usuários iterar cada ação para cada item individualmente:

=Visualização da navegação do acesso com interruptor na tela
Figura 5. Visualização da navegação do acesso com interruptor na tela.

Para melhorar a experiência de navegação, que é especialmente útil para tecnologias adaptativas baseadas em interação, como o acesso por interruptor ou por voz, é possível usar ações personalizadas no contêiner para mover ações de uma travessia individual para um menu de ações separado:

ArticleListItemRow(
    modifier = Modifier
        .semantics {
            customActions = listOf(
                CustomAccessibilityAction(
                    label = "Open article",
                    action = {
                        openArticle()
                        true
                    }
                ),
                CustomAccessibilityAction(
                    label = "Add to bookmarks",
                    action = {
                        addToBookmarks()
                        true
                    }
                ),
            )
        }
) {
    Article(
        modifier = Modifier.clearAndSetSemantics { },
        onClick = openArticle,
    )
    BookmarkButton(
        modifier = Modifier.clearAndSetSemantics { },
        onClick = addToBookmarks,
    )
}

Nesses casos, limpe manualmente a semântica das crianças originais com o modificador clearAndSetSemantics, já que você está movendo-as para ações personalizadas.

Usando o Acesso com interruptor como exemplo, o menu é aberto após a seleção do contêiner e lista as ações aninhadas disponíveis:

Destaque do acesso com interruptor do item da lista de artigos
Figura 6. O Acesso com interruptor destaca o item da lista de artigos.
Visualização do menu de ações do acesso com interruptor.
Figura 7. Visualização do menu de ações do Acesso com interruptor.

Árvore semântica

Uma composição descreve a interface do app e é produzida pela execução de elementos combináveis. A composição é uma estrutura em árvore formada pelos elementos combináveis que descrevem a IU.

Ao lado da composição, existe uma árvore paralela, chamada de árvore semântica. Essa árvore descreve a IU de uma forma alternativa, que é compreensível para os serviços de acessibilidade e para o framework de testes. Os serviços de acessibilidade usam a árvore para descrever o app para usuários com uma necessidade específica. O framework de teste usa a árvore para interagir com seu app e fazer declarações sobre ele. A árvore de semântica não contém informações sobre como exibir os elementos combináveis, mas sim sobre o significado semântico deles.

Uma hierarquia de interface típica e a árvore semântica dela
Figura 8. Uma hierarquia de interface típica e a árvore semântica dela.

Caso o app seja formado por funções que podem ser compostas e modificadores da biblioteca de base do Compose e da biblioteca do Material Design, a árvore semântica será preenchida e gerada automaticamente para você. No entanto, ao adicionar funções que podem ser compostas personalizadas de baixo nível, é necessário informar a semântica correspondente manualmente. Também pode haver situações em que a árvore não representa o significado dos elementos na tela de forma correta ou completa. Nesse caso, é possível adaptar a árvore.

Considere, por exemplo, esta função que pode ser composta de agenda personalizada:

Função que pode ser composta de agenda personalizada com elementos de dia selecionáveis
Figura 9. Função que pode ser composta de agenda personalizada com elementos de dia selecionáveis.

Neste exemplo, a agenda toda é implementada como um única função que pode ser composta de baixo nível, usando a função Layout que pode ser composta e exibindo diretamente em Canvas. Se você não fizer mais nada, os serviços de acessibilidade não receberão informações suficientes sobre o conteúdo do elemento combinável e a seleção do usuário na agenda. Por exemplo, se o usuário clicar no dia 17, o framework de acessibilidade receberá apenas as informações de descrição de todo o controle da agenda. Nesse caso, o serviço de acessibilidade do TalkBack anunciaria "Agenda" ou, em uma hipótese um pouco melhor, "Agenda de abril", mas o usuário ficaria sem saber que dia foi selecionado. Para tornar esse elemento combinável mais acessível, será necessário adicionar informações semânticas manualmente.

Árvores mescladas e não mescladas

Como mencionado anteriormente, é possível ter zero ou mais propriedades definidas para cada função que pode ser composta na árvore da IU. Quando não há propriedades semânticas definidas para uma função que pode ser composta, ela não é incluída como parte da árvore semântica. Dessa forma, a árvore semântica contém apenas os nós que realmente têm significado semântico. No entanto, algumas vezes pode ser útil mesclar subárvores de nós específicas e tratá-las como uma só para transmitir o significado correto do conteúdo exibido na tela. Dessa forma, é possível considerar um conjunto de nós como um todo, em vez de processar cada nó descendente individualmente. Como regra geral, cada nó da árvore representa um elemento focalizável ao usar os serviços de acessibilidade.

Um exemplo desse elemento combinável é Button. É possível considerar um botão como um único elemento, mesmo que ele contenha vários nós filhos:

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

Na árvore semântica, as propriedades dos descendentes do botão são mescladas, e o botão é apresentado como um único nó de folha na árvore:

Representação semântica de folha única mesclada
Figura 10. Representação semântica de folha única mesclada.

As funções que podem ser compostas e os modificadores podem indicar que querem mesclar as propriedades semânticas dos descendentes chamando o método Modifier.semantics (mergeDescendants = true) {}. Definir essa propriedade como true indica que as propriedades semânticas precisam ser mescladas. No exemplo do Button, o elemento combinável Button usa o modificador clickable internamente, que inclui o modificador semantics. Portanto, os nós descendentes do botão são mesclados. Leia a documentação de acessibilidade para saber mais sobre quando mudar o comportamento de mesclagem do elemento combinável.

Vários modificadores e funções que podem ser compostas nas bibliotecas Compose Foundation e Compose Material têm essa propriedade definida. Por exemplo, os modificadores clickable e toggleable mesclam os descendentes automaticamente. Além disso, a função ListItem mesclará os descendentes.

Inspecionar a árvore

A árvore semântica é, na verdade, duas árvores diferentes. Há uma árvore semântica mesclada, que mescla os nós descendentes quando mergeDescendants é definido como true. Há também uma árvore semântica não mesclada, que não aplica a mesclagem, mas mantém todos os nós intactos. Os serviços de acessibilidade usam a árvore não mesclada e aplicam os próprios algoritmos de mesclagem, considerando a propriedade mergeDescendants. Por padrão, o framework de teste usa a árvore mesclada.

É possível inspecionar as duas árvores usando o método printToLog(). Por padrão, e como nos exemplos anteriores, a árvore mesclada é registrada. Para mostrar a árvore não mesclada, defina o parâmetro useUnmergedTree do matcher onRoot() como true:

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

O Layout Inspector permite exibir a árvore semântica mesclada e a não mesclada, selecionando a árvore de preferência no filtro de visualização:

Opções de visualização do Layout Inspector, permitindo a exibição da árvore semântica mesclada e não mesclada
Figura 11. Opções de visualização do Layout Inspector, permitindo a exibição da árvore semântica mesclada e não mesclada.

O Layout Inspector mostra a semântica mesclada e a semântica definida de cada nó da árvore no painel de propriedades:

Propriedades semânticas mescladas e definidas
Figura 12. Propriedades semânticas mescladas e definidas.

Por padrão, os matchers do framework de testes usam a árvore semântica mesclada. É por isso que é possível interagir com um Button fazendo a correspondência do texto mostrado nele:

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

Para substituir esse comportamento, defina o parâmetro useUnmergedTree dos matchers como true, como fizemos com o matcher onRoot.

Adaptar a árvore

Como mencionado anteriormente, é possível substituir ou limpar algumas propriedades semânticas ou mudar o comportamento de mesclagem da árvore. Isso é especialmente relevante quando você cria seus próprios componentes personalizados. Sem definir as propriedades e o comportamento de mesclagem corretos, o app pode não ser acessível e os testes podem se comportar de maneira diferente do esperado. Para saber mais sobre testes, consulte o guia de testes.