Padrões da API

As APIs Material, Compose UI e Foundation implementam e oferecem muitas práticas acessíveis por padrão. Elas contêm semântica integrada que segue a função e o papel específicos. Isso significa que a maior parte do suporte à acessibilidade é fornecida com pouco ou nenhum trabalho extra.

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

Entender a semântica e os padrões de acessibilidade padrão nas APIs do Compose ajuda você a usá-los com foco na acessibilidade. Ela também ajuda você a oferecer suporte à acessibilidade em componentes mais 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 padding com uma largura e uma 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 uma 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 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 marcada e desmarcada.
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 elemento Box clicável muito pequeno. A área de toque é expandida automaticamente para além dos limites do elemento Box. Então, tocar ao lado de Box ainda acionará 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 clicável muito pequena 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 significa 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 uma meta de toque maior.
Figura 5. Uma área de toque 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 fornecer 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:

Uma 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, já que 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 apenas decorativos, e você pode optar por não os descrever 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. Componentes do Material, como Button ou Text, e comportamentos acionáveis, como clickable ou toggleable, vêm com outras semânticas predefinidas que descrevem o comportamento intrínseco deles e podem ser mudadas por outras APIs do Compose.

Elementos interativos

As APIs Material e Foundation Compose criam elementos de UI com que os usuários podem interagir usando as APIs de modificador clickable e toggleable. Como componentes interativos podem consistir em vários elementos, clickable e toggleable mesclam 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 pode consistir em um ícone filho e algum texto. Em vez de tratar as crianças como indivíduos, um Button do Material mescla a semântica delas por padrão para que os serviços de acessibilidade possam agrupá-las de acordo com a necessidade:

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

Da mesma forma, usar o modificador clickable 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 principal 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 permitem que o TalkBack forneça uma dica de ação "Toque duas vezes para abrir este artigo", em vez do feedback padrão mais genérico "Toque duas vezes para ativar".

Esse feedback muda de acordo com o tipo de ação. Um clique longo forneceria uma dica do TalkBack "Toque duas vezes e mantenha pressionado para", seguida de 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 é definido em algum lugar em uma camada aninhada inferior),mas ainda queira mudar o rótulo do anúncio do padrão. Para fazer isso, divida a definição do clickable da modificação do anúncio usando o modificador semantics e defina o identificador de clique lá 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
                }
            )
        }
    )
}

Não é necessário passar a ação de clique duas vezes. As APIs Compose atuais, como clickable ou Button, fazem isso por você. A lógica de fusão verifica se o rótulo e a ação do modificador mais externos são usados para as informações presentes. No exemplo anterior, o NestedArticleListItem transmite automaticamente a ação de clique openArticle() à semântica clickable. Você pode deixar a ação de clique nula na segunda ação do modificador de semântica. No entanto, o rótulo de clique é extraído do segundo modificador semântico onClick(label = "Open this document") porque não estava presente no primeiro.

Você pode encontrar cenários em que espera que a semântica dos filhos seja mesclada em uma semântica mãe, mas isso não acontece. Consulte Mesclar e limpar para mais informações.

Componentes personalizados

Ao criar um componente personalizado, revise a implementação de um componente semelhante na biblioteca Material ou em outras bibliotecas do Compose. Em seguida, imite ou modifique o comportamento de acessibilidade conforme apropriado. Por exemplo, se você substituir o Checkbox do Material Design por uma implementação própria, consulte a implementação Checkbox atual para lembrar de adicionar o modificador triStateToggleable, que processa as propriedades de acessibilidade do componente. Além disso, use os modificadores do Foundation porque eles incluem considerações de acessibilidade integradas e práticas do Compose abordadas nesta seção.

Você também encontra um exemplo de componente de alternância personalizado na seção Semântica clara e definida, além de informações mais detalhadas sobre como oferecer suporte à acessibilidade em componentes personalizados nas diretrizes da API.