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

As informações sobre o significado e a função de um componente no Compose são chamadas de semântica, que é uma forma de fornecer mais contexto sobre os elementos combináveis para serviços como acessibilidade, preenchimento automático e testes. 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 apropriadas, você fornece o máximo de informações possível sobre o componente aos serviços de acessibilidade, que decidem como representá-lo para o usuário.

As APIs Material, Compose UI e Foundation vêm com semântica integrada que segue o papel e a função específicos delas, mas você também pode modificar essa semântica para APIs atuais 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 desse elemento. 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ê poderia dizer o seguinte: "Esta é uma chave, um elemento alternável no estado 'Ativado'. Clique nele para interagir."

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

Layout Inspector mostrando as propriedades semânticas de um elemento combinável Switch
Figura 2. 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 é uma versão localizada da palavra "Ativado", mas 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.

Acompanhar as propriedades semânticas de cada elemento combinável no app abre muitas possibilidades:

  • Os serviços de acessibilidade usam as propriedades para representar a IU mostrada na tela e permitir que os usuários interajam com ela. Para o elemento de composição Switch, 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 teste 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 que podem ser compostos e os modificadores criados de acordo com a biblioteca Compose Foundation já definem as propriedades relevantes para você por padrão. Se quiser, você 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 que podem ser compostos.

Para sinalizar o tipo de conteúdo específico do componente para os serviços de acessibilidade, é possível aplicar uma variedade de semânticas diferentes. Essas adições vão dar suporte às principais informações semânticas em vigor e ajudar os serviços de acessibilidade a ajustar como o componente é representado, anunciado ou interagido.

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

Títulos

Os apps geralmente contêm telas com conteúdo avançado, como artigos longos ou páginas de notícias, que costumam ser divididas em diferentes subseções com títulos:

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

Usuários com necessidades de acessibilidade podem ter dificuldades para navegar em uma tela como essa. 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 esse recurso, indique que o componente é um heading definindo a propriedade semântica dele:

@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 um pop-up, como um Snackbar, talvez seja necessário sinalizar aos serviços de acessibilidade que uma nova estrutura ou atualizações de conteúdo podem ser transmitidas aos usuários.

Os componentes semelhantes a alertas podem ser marcados com a propriedade de 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 direcionada brevemente para alertas ou mudanças importantes no conteúdo da tela.

Use o liveRegion.Assertive com moderação para evitar feedback que atrapalhe. Ele deve ser usado 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 dinâmicas não devem ser usadas em conteúdo que é atualizado com frequência, como timers de contagem regressiva, para evitar sobrecarregar os usuários com feedback constante.

Componentes semelhantes a janelas

Componentes personalizados semelhantes a janelas, como ModalBottomSheet, precisam de outros indicadores para se diferenciarem do conteúdo ao redor. Para isso, use a semântica paneTitle para que todas as mudanças relevantes de janela ou painel sejam 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 nos componentes.

Componentes de erro

Para outros tipos de conteúdo, como componentes semelhantes a erros, talvez seja necessário expandir as principais informações semânticas 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 expandidas.

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

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

Componentes de monitoramento do progresso

Para componentes personalizados que acompanham o progresso, talvez seja interessante notificar os usuários sobre as mudanças no progresso, incluindo o valor atual, o intervalo e o tamanho da etapa. Para isso, use a semântica progressBarRangeInfo, que garante que os serviços de acessibilidade estejam cientes das mudanças no progresso e possam atualizar os usuários de acordo. Diferentes tecnologias adaptativas também podem ter maneiras exclusivas 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 listas e itens

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 em qual índice de item eles estão em relação ao total da coleção, 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, uma função combinável alternável pode se encontrar no estado "marcado" ou "desmarcado". Em alguns casos, pode ser necessário substituir os rótulos de descrição de estado padrão usados pelo Compose. Para fazer isso, especifique 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 na tela touchscreen, como deslizar para dispensar ou arrastar e soltar, já que essas ações podem ser difíceis para usuários com deficiências motoras ou outras.

Para tornar o gesto de deslizar para dispensar mais acessível, vincule-o a uma ação personalizada, transmitindo a ação de dispensar 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 indica que há mais ações disponíveis no menu, representando a ação de deslizar para dispensar:

Visualização do menu de ações do TalkBack
Figura 4. Visualização do menu de ações 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 cansativo 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, o que é especialmente útil para tecnologias adaptativas baseadas em interação, como o acesso com interruptor ou o Voice Access, use 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 dos filhos originais com o modificador clearAndSetSemantics, já que você os está movendo para ações personalizadas.

Usando o acesso com interruptor como exemplo, o menu é aberto ao selecionar o contêiner e lista as ações aninhadas disponíveis:

Destaque do acesso com interruptor do item da lista de artigos
Figura 6. Destaque do item da lista de artigos no acesso com interruptor.
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 de árvore que consiste nos elementos combináveis que descrevem a interface.

Ao lado da composição, há uma árvore paralela, chamada de árvore de semântica. Essa árvore descreve a IU de uma maneira alternativa e compreensível para os serviços de acessibilidade e os frameworks 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 o app e fazer declarações sobre ele. A árvore semântica não contém informações sobre como mostrar os elementos que podem ser compostos, 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 IU típica e a árvore de 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 elementos combináveis personalizados de baixo nível, é necessário fornecer manualmente a semântica deles. 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:

Um elemento de agenda personalizado com elementos de dia selecionáveis
Figura 9. Um elemento combinável de calendário personalizado 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 vão receber 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 apenas "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, você precisa adicionar informações semânticas manualmente.

Árvore mesclada e não mesclada

Como mencionado anteriormente, é possível ter zero ou mais propriedades definidas para cada função que pode ser composta na árvore da IU. Quando um elemento que pode ser composto não tem propriedades semânticas definidas, ele não é incluído 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, você pode pensar em um conjunto de nós como um todo, em vez de lidar com 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 tipo de elemento combinável é Button. Você pode pensar em 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 de 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 de 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 no elemento que pode ser composto.

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 de semântica mesclada, que mescla 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. O framework de teste usa a árvore mesclada por padrão.

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

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

O Layout Inspector permite mostrar as árvores de semântica mesclada e não mesclada. Basta selecionar a opção de sua preferência no filtro de visualização:

Opções de visualização do Layout Inspector, que permitem mostrar a árvore semântica mesclada e não mesclada
Figura 11. Opções de visualização do Layout Inspector, que permitem mostrar as árvores semânticas mescladas e não mescladas.

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, é possível interagir com um Button correspondendo ao texto mostrado nele:

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

Substitua esse comportamento definindo o parâmetro useUnmergedTree dos comparadores como true, como no comparador onRoot.

Adaptar a árvore

Como mencionado antes, é possível modificar ou limpar determinadas propriedades de semântica ou mudar o comportamento de mesclagem da árvore. Isso é particularmente relevante quando você está criando seus próprios componentes personalizados. Sem definir as propriedades e o comportamento de mesclagem corretos, seu 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.