Apps programados no Compose precisam oferecer acessibilidade para usuários com diferentes necessidades. Os serviços de acessibilidade são usados para transformar o que é mostrado na tela em um formato mais adequado para usuários com uma necessidade específica. Para que um app seja compatível com serviços de acessibilidade, é necessário usar APIs no framework do Android para expor informações semânticas sobre os elementos da IU. Em seguida, o framework do Android transmitirá essas informações semânticas aos serviços de acessibilidade; Cada serviço de acessibilidade pode escolher a melhor forma de descrever o app para o usuário. O Android oferece diversos serviços de acessibilidade, incluindo o Talkback e o acesso com interruptor.
Semântica
O Compose usa propriedades semânticas para transmitir informações aos serviços
de acessibilidade. As propriedades semânticas fornecem informações sobre elementos da IU que são
exibidos ao usuário. A maioria dos elementos de composição integrados, como
Text
e
Button
preenchem essas propriedades semânticas com informações relacionadas ao elemento e
aos filhos dele. Alguns modificadores, como
toggleable
e
clickable
,
também definem algumas propriedades de semântica. No entanto, às vezes o framework
precisa de mais informações para entender como descrever um elemento de IU para o usuário.
Este documento descreve várias situações em que é necessário adicionar informações complementares, de forma explícita a um elemento de composição para que ele possa ser descrito corretamente para o framework do Android. Ele também explica como substituir completamente as informações de semântica por um elemento de composição específico. É necessário ter conhecimento básico sobre a acessibilidade no Android.
Casos de uso comuns
Para ajudar pessoas com necessidades de acessibilidade a usar seu app, é necessário seguir as práticas recomendadas descritas nesta página.
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, 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, ele inclui o padding com uma largura e uma altura de
pelo menos 48 dp.
@Composable
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 com o componente de forma direta.
@Composable
fun NonClickableCheckbox() {
Checkbox(checked = true, onCheckedChange = null)
}
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 de composição como null
e adiciona um modificador toggleable
ou
selectable
ao elemento pai.
@Composable
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. Isso é feito expandindo o tamanho da área de toque para fora dos limites do elemento de composição.
No exemplo a seguir, criamos 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
fun DefaultPreview() {
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 áreas de toque de diferentes elementos e composição
é importante sempre usar um tamanho mínimo grande o suficiente. No nosso
exemplo, isso significa usar o modificador sizeIn
para definir o tamanho mínimo da
caixa interna:
@Composable
fun DefaultPreview() {
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 de composição. Os rótulos de clique descrevem o que acontece quando o usuário interage com o elemento de composição. 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
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 puder acessar o modificador "clickable", é possível definir a etiqueta de clique no modificador de semântica:
@Composable
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 de composição
Image
ou Icon
,
não há como o framework do Android entender de forma automática
o que está sendo mostrado. É necessário fornecer 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:
Tendo somente o ícone como base, o framework do Android não consegue encontrar uma forma de 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
é usado para descrever um elemento visual. Use
uma string localizada, porque isso será comunicada ao usuário.
@Composable
fun ShareButton(onClick: () -> Unit) {
IconButton(onClick = onClick) {
Icon(
imageVector = Icons.Filled.Share,
contentDescription = stringResource(R.string.label_share)
)
}
}
Alguns elementos visuais 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 contém
ações ou estados associados.
@Composable
fun PostImage(post: Post, modifier: Modifier = Modifier) {
val image = post.imageThumb ?: imageResource(R.drawable.placeholder_1_1)
Image(
bitmap = 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
. Analise se o elemento transmite informações necessárias
para que o usuário realize uma tarefa. Se esse não for o caso, é melhor não
incluir uma 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. Se cada função de baixo nível na tela tiver um foco independente, o usuário precisará fazer muitas interações para navegar pela tela. Se os elementos forem combinados de forma exagerada, os usuários podem não compreender quais elementos estão agrupados.
Ao aplicar um modificador
clickable
a um elemento de composição, o Compose mescla automaticamente todos os elementos que ele contém. Isso também funciona para
ListItem
.
Os elementos de um item de lista vão ser mesclados, e os serviços de acessibilidade verão
um único 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 seria preferível que os serviços de acessibilidade vissem esses elementos como um só. Por exemplo, imagine um elemento de composição que mostre o avatar de um usuário, o nome e algumas informações complementares:
Você pode instruir o Compose a mesclar esses elementos usando o parâmetro mergeDescendants
no modificador semantics
. Dessa forma, os serviços de acessibilidade
selecionarão apenas o elemento mesclado, e todas as propriedades semânticas dos descendentes
serão mescladas.
@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")
}
}
}
Agora, os serviços de acessibilidade terão como foco o contêiner todo, mesclando os conteúdos:
Adicionar ações personalizadas
Veja o seguinte item de lista:
Quando você usa um leitor de tela, como o Talkback, para ouvir o que é exibido na tela, ele primeiro selecionará o item todo e, depois, o ícone "adicionar aos favoritos".
Em uma lista longa, isso pode ficar muito repetitivo. Uma abordagem melhor seria
definir uma ação personalizada que permita que o usuário adicione o item aos favoritos. Lembre-se
de que também será necessário remover explicitamente o comportamento do ícone
"adicionar aos favoritos" para garantir que ele não será selecionado pelo serviço de acessibilidade.
Isso pode ser mais bem feito com o
clearAndSetSemantics
:
@Composable
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 de composição 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 alternável pode se encontrar no estado "Marcado" ou "Desmarcado". Em alguns casos, convém substituir as etiquetas padrão de descrição de estado usados pelo Compose. Isso pode ser feito especificando explicitamente as etiquetas de descrição de estado, antes de definir a função 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
Algumas vezes, apps exibem muito conteúdo em uma tela, em um contêiner de rolagem. Por exemplo, uma tela pode mostrar todo o conteúdo de um artigo que o usuário está lendo:
Para usuários com necessidades de acessibilidade, será difícil navegar nessa tela. Para auxiliar na navegação, indique quais elementos são os cabeçalhos. No exemplo acima, cada título de subseção poderia ser definido como um cabeçalho para fins de acessibilidade. Alguns serviços de acessibilidade, como o Talkback, permitem que os usuários naveguem diretamente de cabeçalho a cabeçalho.
No Compose, para indicar que uma função que pode ser composta é um cabeçalho, defina a propriedade semântica:
@Composable
private fun Subsection(text: String) {
Text(
text = text,
style = MaterialTheme.typography.h5,
modifier = Modifier.semantics { heading() }
)
}
Como criar funções de composição personalizadas de baixo nível
Um caso de uso mais avançado envolve a substituição de determinados componentes do Material Design em
versões personalizadas do app. É essencial considerar as
necessidades de acessibilidade nesse caso. Digamos que você esteja substituindo a
Checkbox
do Material Design por uma implementação própria. Você poderia facilmente se esquecer de adicionar o
modificador triStateToggleable
,
que processa as propriedades de acessibilidade desse componente.
Como regra geral, é necessário analisar a implementação do componente na biblioteca do Material Design e simular qualquer comportamento de acessibilidade que possa ser encontrado. 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. Teste a implementação do componente personalizado com vários serviços de acessibilidade para verificar o comportamento.
Saiba mais
Para saber mais sobre o suporte à acessibilidade no seu código do Compose, consulte o codelab Acessibilidade no Jetpack Compose.