Padrões da API

As APIs Material, Compose UI e Foundation implementam e oferecem muitas práticas acessíveis por padrão. Eles contêm semânticas integradas que seguem a função e o papel específicos, o que significa que a maioria do suporte à acessibilidade é fornecida com pouco ou nenhum trabalho extra.

Usar as APIs adequadas para a finalidade certa geralmente significa que os componentes vêm com comportamentos de acessibilidade predefinidos que abrangem casos de uso padrão, mas lembre-se de verificar se esses padrões atendem às suas necessidades de acessibilidade. Caso contrário, o Compose também oferece maneiras de atender a requisitos mais específicos.

Conhecer a semântica e os padrões de acessibilidade padrão nas APIs do Compose ajuda a entender como usá-los com foco na acessibilidade, além de oferecer suporte à acessibilidade em mais componentes personalizados.

Tamanhos mínimos de área de toque

Todos os elementos mostrados na tela que possam ser clicados, tocados ou com os quais é possível interagir de alguma outra forma precisam ser grandes o suficiente para uma interação confiável. Ao dimensionar esses elementos, defina o tamanho mínimo como 48 dp para seguir corretamente as diretrizes de acessibilidade do Material Design (link em inglês).

Os componentes do Material Design (como Checkbox, RadioButton, Switch, Slider e Surface) definem esse tamanho mínimo internamente, mas apenas quando o componente pode receber ações do usuário. Por exemplo, quando um Checkbox tem o parâmetro onCheckedChange definido como um valor não nulo, a caixa de seleção inclui o padding para ter uma largura e altura de pelo menos 48 dp.

@Composable
private fun CheckableCheckbox() {
    Checkbox(checked = true, onCheckedChange = {})
}

Uma caixa de seleção com o padding padrão e largura e altura de 48 dp.
Figura 1. Uma caixa de seleção com padding padrão.

Quando o parâmetro onCheckedChange é definido como nulo, o padding não é incluído, porque não é possível interagir com o componente de forma direta.

@Composable
private fun NonClickableCheckbox() {
    Checkbox(checked = true, onCheckedChange = null)
}

Uma caixa de seleção sem padding.
Figura 2. Uma caixa de seleção sem padding.

Ao implementar controles de seleção, como Switch, RadioButton ou Checkbox, você normalmente aumenta o comportamento clicável para um contêiner pai, definindo o callback de clique no elemento combinável como null e adicionando um modificador toggleable ou selectable ao elemento combinável pai.

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

Uma caixa de seleção ao lado do texto "Opção" que está sendo selecionado e desmarcado.
Figura 3. Uma caixa de seleção com comportamento clicável.

Quando o tamanho de um elemento clicável é menor que o tamanho mínimo da área de toque, o Compose aumenta o tamanho dessa área. Isso é feito expandindo o tamanho da área de toque para fora dos limites do elemento combinável.

O exemplo a seguir contém um Box clicável muito pequeno. A área de destino do toque é expandida automaticamente para além dos limites do elemento Box. Portanto, tocar ao lado do Box ainda aciona o evento de clique.

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

Uma caixa muito pequena que pode ser clicada e que é expandida para uma área de toque maior ao tocar ao lado dela.
Figura 4. Uma caixa clicável muito pequena que é expandida para uma área de toque maior.

Para evitar possíveis sobreposições entre áreas de toque de diferentes elementos combináveis, sempre use um tamanho mínimo grande o suficiente. No exemplo, isso significaria usar o modificador sizeIn para definir o tamanho mínimo da caixa interna:

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

A caixa muito pequena do exemplo anterior é aumentada para criar um alvo de toque maior.
Figura 5. Uma área de toque de caixa maior.

Elementos gráficos

Quando você define um elemento combinável Image ou Icon, não há como o framework do Android entender de forma automática o que o app está mostrando. É necessário transmitir uma descrição textual do elemento gráfico.

Imagine uma tela em que o usuário pode compartilhar a página atual com amigos. Essa tela contém um ícone de compartilhamento clicável:

Faixa de quatro ícones clicáveis, com o ícone "compartilhar" destacado.
Figura 6. Uma linha de ícones clicáveis com o ícone "Compartilhar" selecionado.

Tendo somente o ícone como base, o framework do Android não consegue descrevê-lo para um usuário com deficiência visual. O framework do Android precisa de uma descrição textual complementar do ícone.

O parâmetro contentDescription descreve um elemento gráfico. Use uma string localizada, porque ela fica visível para o usuário.

@Composable
private fun ShareButton(onClick: () -> Unit) {
    IconButton(onClick = onClick) {
        Icon(
            imageVector = Icons.Filled.Share,
            contentDescription = stringResource(R.string.label_share)
        )
    }
}

Alguns elementos gráficos são puramente decorativos, e você pode optar por não descrevê-los ao usuário. Ao definir o parâmetro contentDescription como null, você indica ao framework do Android que esse elemento não tem ações ou estados associados.

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

O contentDescription é usado principalmente para elementos gráficos, como imagens. Os componentes do Material Design, como Button ou Text, e os comportamentos acionáveis, como clickable ou toggleable, vêm com outras semânticas predefinidas que descrevem o comportamento intrínseco e podem ser alteradas por outras APIs do Compose.

Elementos interativos

As APIs Material e Foundation Compose criam elementos de interface com os quais os usuários podem interagir usando as APIs clickable e toggleable. Como os componentes interativos podem consistir em vários elementos, clickable e toggleable mesclaram a semântica dos filhos por padrão, para que o componente seja tratado como uma entidade lógica.

Por exemplo, um Button do Material Design pode consistir em um ícone filho e um texto. Em vez de tratar os filhos como indivíduos, um botão do Material Design mescla a semântica dos filhos por padrão, para que os serviços de acessibilidade possam agrupá-los:

Botões com semântica de filhos não mesclados e mesclados.
Figura 7. Botões com semântica de filhos não mesclados e mesclados.

Da mesma forma, o uso do modificador clickable também faz com que um elemento combinável mescle a semântica dos descendentes em uma única entidade, que é enviada aos serviços de acessibilidade com uma representação de ação correspondente:

Row(
    // Uses `mergeDescendants = true` under the hood
    modifier = Modifier.clickable { openArticle() }
) {
    Icon(
        painter = painterResource(R.drawable.ic_logo),
        contentDescription = "Open",
    )
    Text("Accessibility in Compose")
}

Você também pode definir um onClickLabel específico no elemento clicável pai para fornecer mais informações aos serviços de acessibilidade e oferecer uma representação mais refinada da ação:

Row(
    modifier = Modifier
        .clickable(onClickLabel = "Open this article") {
            openArticle()
        }
) {
    Icon(
        painter = painterResource(R.drawable.ic_logo),
        contentDescription = "Open"
    )
    Text("Accessibility in Compose")
}

Usando o TalkBack como exemplo, esse modificador clickable e o rótulo de clique permitiriam que o TalkBack fornecesse uma sugestão de ação de "Toque duas vezes para abrir este artigo", em vez do feedback padrão mais genérico de "Toque duas vezes para ativar".

Esse feedback muda dependendo do tipo de ação. Um clique longo forneceria uma dica do TalkBack de "Toque duas vezes e pressione para", seguida por um rótulo:

Row(
    modifier = Modifier
        .combinedClickable(
            onLongClickLabel = "Bookmark this article",
            onLongClick = { addToBookmarks() },
            onClickLabel = "Open this article",
            onClick = { openArticle() },
        )
) {}

Em alguns casos, talvez você não tenha acesso direto ao modificador clickable (por exemplo, quando ele está definido em uma camada aninhada inferior),mas ainda quer mudar o rótulo de anúncio padrão. Para fazer isso, divida a configuração do clickable da modificação do anúncio usando o modificador semantics e definindo o identificador de clique para modificar a representação da ação:

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

Nesse caso, não é necessário transmitir a ação de clique duas vezes, porque as APIs do Compose, como clickable ou Button, fazem isso por você. Isso ocorre porque a lógica de mesclagem garante que o rótulo e a ação do modificador mais externo sejam usados para as informações presentes.

No exemplo anterior, a ação de clique openArticle() é transmitida em profundidade pelo NestedArticleListItem automaticamente para a semântica clickable e pode ser deixada nula na segunda ação do modificador de semântica. No entanto, o rótulo de clique é extraído do segundo modificador de semântica onClick(label = "Open this article"), já que não estava presente no primeiro.

Você pode encontrar cenários em que espera que a semântica das crianças seja mesclada a uma mãe, mas isso não acontece. Consulte Como mesclar e limpar para mais informações detalhadas.

Componentes personalizados

Para componentes personalizados, como regra geral, analise a implementação de um componente semelhante na biblioteca do Material Design ou em outras bibliotecas do Compose e imite ou modifique o comportamento de acessibilidade quando for sensato fazer isso.

Por exemplo, se você estiver substituindo o Checkbox do Material Design por sua própria implementação, a visualização da implementação de caixa de seleção existente vai lembrar você de adicionar o modificador triStateToggleable, que processa as propriedades de acessibilidade para esse componente.

Além disso, use bastante os modificadores do Compose Foundation, porque eles incluem considerações de acessibilidade prontas, bem como práticas do Compose abordadas nesta seção.

Você também pode encontrar um exemplo de componente de alternância personalizado na seção Limpar e definir semânticas, além de informações mais detalhadas sobre como oferecer suporte à acessibilidade em componentes personalizados nas diretrizes da API.