Principais etapas para melhorar a acessibilidade do Compose

Para ajudar pessoas com necessidades de acessibilidade a usar seu app, projete seu app para atender aos principais requisitos de acessibilidade.

Considerar os 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, certifique-se de defina o tamanho mínimo como 48 dp para seguir corretamente a especificação do Material Design diretrizes de acessibilidade.

componentes do Material Design, como Checkbox, RadioButton e 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 seu parâmetro onCheckedChange definido com um valor não nulo, a caixa de seleção inclui o padding tenha uma altura e largura de pelo menos 48 dp.

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

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

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

Figura 1. 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, define o callback de clique no elemento combinável para null e adicione um 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)
        }
    }
}

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. Ele faz isso expandindo tamanho da área de toque fora dos limites do elemento combinável

O exemplo a seguir contém um Box clicável muito pequeno. Área de toque é automaticamente expandida para além dos limites do Box. Portanto, tocar ao lado de 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)
        )
    }
}

Para evitar possíveis sobreposições entre as áreas de toque de diferentes elementos combináveis, sempre use um tamanho mínimo grande o suficiente para o elemento combinável. No exemplo, isso seria significa usar o modificador sizeIn para definir o tamanho mínimo para a 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)
        )
    }
}

Adicionar etiquetas de clique

Você pode usar uma etiqueta de clique para adicionar um significado semântico ao comportamento de clique de uma função que pode ser composta. Os marcadores de clique descrevem o que acontece quando o usuário interage com o combinável. Os serviços de acessibilidade usam rótulos de clique para ajudar a descrever o app para usuários com necessidades específicas.

Defina a etiqueta de clique transmitindo um parâmetro no modificador clickable:

@Composable
private fun ArticleListItem(openArticle: () -> Unit) {
    Row(
        Modifier.clickable(
            // R.string.action_read_article = "read article"
            onClickLabel = stringResource(R.string.action_read_article),
            onClick = openArticle
        )
    ) {
        // ..
    }
}

Como alternativa, se você não tiver acesso ao modificador clicável, defina Clique na etiqueta no modificador semantics:

@Composable
private fun LowLevelClickLabel(openArticle: () -> Boolean) {
    // R.string.action_read_article = "read article"
    val readArticleLabel = stringResource(R.string.action_read_article)
    Canvas(
        Modifier.semantics {
            onClick(label = readArticleLabel, action = openArticle)
        }
    ) {
        // ..
    }
}

Descrever elementos visuais

Quando você define um elemento combinável Image ou Icon, não há maneira automática para o framework do Android entender o que é o app exibidos. Você precisa transmitir uma descrição textual do elemento visual.

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 ícones clicáveis, com o

Com base apenas no ícone, o framework do Android não pode descrevê-lo para uma com deficiência. O framework do Android precisa de uma descrição textual adicional do no ícone.

O parâmetro contentDescription descreve um elemento visual. Usar um tipo já que ela é 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 visuais são meramente decorativos e você pode não querer comunicar ao usuário. Ao definir o parâmetro contentDescription como null, você indicar ao framework do Android que esse elemento não foi associado ações ou estados.

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

Cabe a você decidir se um determinado elemento visual precisa de uma contentDescription. Pergunte a si mesmo se o elemento transmite informações que que o usuário vai precisar para realizar a tarefa. Caso contrário, é melhor deixar descrição.

Mesclar elementos

Os serviços de acessibilidade, como o TalkBack e o acesso com interruptor, permitem que os usuários coloquem o foco em diferentes elementos na tela. É importante que o foco nos elementos tenha a granularidade correta. Quando cada elemento combinável de baixo nível na tela é e focados de forma independente, os usuários têm que interagir muito para se mover pela tela. Se os elementos se mesclarem de forma muito agressiva, os usuários podem não entender quais elementos estão juntos

Quando você aplica um modificador clickable a um elemento combinável, o Compose mescla automaticamente todos os elementos que podem ser compostos. Isso também vale para ListItem elementos de um item de lista se mesclam, e a acessibilidade serviços os veem como um elemento.

É possível definir um conjunto de funções que podem ser compostas que formem um grupo lógico, mas esse grupo não será clicável nem fará parte de um item de lista. Ainda é melhor ter acessibilidade serviços para vê-los como um elemento. Por exemplo, imagine um elemento combinável que mostra o avatar de um usuário, seu nome e algumas informações extras:

Um grupo de elementos da IU, incluindo o nome de um usuário. O nome está selecionado.

Você pode permitir que o Compose mescle esses elementos usando o mergeDescendants. no modificador semantics. Dessa forma, os serviços de acessibilidade seleciona apenas o elemento mesclado e todas as propriedades semânticas dos descendentes são mesclados.

@Composable
private fun PostMetadata(metadata: Metadata) {
    // Merge elements below for accessibility purposes
    Row(modifier = Modifier.semantics(mergeDescendants = true) {}) {
        Image(
            imageVector = Icons.Filled.AccountCircle,
            contentDescription = null // decorative
        )
        Column {
            Text(metadata.author.name)
            Text("${metadata.date}${metadata.readTimeMinutes} min read")
        }
    }
}

Os serviços de acessibilidade agora se concentram em todo o contêiner de uma só vez, mesclando seus conteúdos:

Um grupo de elementos da IU, incluindo o nome de um usuário. Todos os elementos estão selecionados juntos.

Adicionar ações personalizadas

Veja o seguinte item de lista:

Item de lista típico, contendo um título de artigo, autor e ícone "adicionar aos favoritos".

Quando você usa um leitor de tela, como o TalkBack, para ouvir o que é exibido no ele primeiro seleciona o item inteiro e, em seguida, o ícone de favorito.

Item de lista, com todos os elementos selecionados juntos.

Item de lista, com apenas o ícone "adicionar aos favoritos" selecionado.

Em uma lista longa, isso pode ficar muito repetitivo. Uma abordagem melhor é definir uma ação personalizada que permita que um usuário adicione o item aos favoritos. Observação importante que você também tem que remover explicitamente o comportamento do ícone de favorito para garantir que não seja selecionado pelo serviço de acessibilidade. Isso usa o modificador clearAndSetSemantics:

@Composable
private fun PostCardSimple(
    /* ... */
    isFavorite: Boolean,
    onToggleFavorite: () -> Boolean
) {
    val actionLabel = stringResource(
        if (isFavorite) R.string.unfavorite else R.string.favorite
    )
    Row(
        modifier = Modifier
            .clickable(onClick = { /* ... */ })
            .semantics {
                // Set any explicit semantic properties
                customActions = listOf(
                    CustomAccessibilityAction(actionLabel, onToggleFavorite)
                )
            }
    ) {
        /* ... */
        BookmarkButton(
            isBookmarked = isFavorite,
            onClick = onToggleFavorite,
            // Clear any semantics properties set on this node
            modifier = Modifier.clearAndSetSemantics { }
        )
    }
}

Descrever o estado de um elemento

Um elemento combinável pode definir uma stateDescription como semântica que o O framework do Android usa para ler o estado em que o elemento combinável se encontra. Para por exemplo, um elemento combinável alternável pode estar em um ambiente ou "desmarcada" estado. Em alguns casos, convém substituir a descrição de estado padrão rótulos que o Compose usa. Para isso, especifique explicitamente o estado rótulos de descrição 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() }
            )
    ) {
        /* ... */
    }
}

Definir cabeçalhos

Às vezes, os apps mostram muito conteúdo em uma tela em um contêiner rolável. Por exemplo, uma tela pode mostrar todo o conteúdo de um artigo que o usuário está lendo:

Captura de tela de uma postagem de blog, com o texto de um artigo em um contêiner de rolagem.

Usuários com necessidades de acessibilidade têm dificuldade para navegar nessa tela. Para ajudar de navegação, indicam quais elementos são cabeçalhos. No exemplo anterior, cada o título da subseção pode ser definido como um cabeçalho para acessibilidade. Algumas serviços de acessibilidade, como o Talkback, permitem que os usuários naveguem diretamente de indo para o cabeçalho.

No Compose, para indicar que um elemento combinável é um cabeçalho, defina o Propriedade semantics:

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

Processar elementos combináveis personalizados

Sempre que você substituir determinados componentes do Material Design no seu app por é necessário considerar a acessibilidade.

Digamos que você esteja substituindo o Material Checkbox pela sua própria implementação. Você pode se esquecer de adicionar o modificador triStateToggleable, que processa as propriedades de acessibilidade desse componente.

Como regra geral, observe a implementação do componente em a biblioteca do Material Design e imitar qualquer comportamento de acessibilidade que você encontrar. Além disso, use os modificadores do Compose Foundation, e não modificadores no nível da IU, porque eles incluem considerações de acessibilidade prontas.

Testar a implementação do componente personalizado com vários serviços de acessibilidade para verificar o comportamento dele.

Outros recursos