Conceitos e implementação do Jetpack Compose
Para ajudar usuários com necessidades de acessibilidade, o framework do Android permite criar um serviço de acessibilidade que pode apresentar conteúdo de apps aos usuários e também operar apps em nome deles.
O Android fornece vários serviços de acessibilidade do sistema, incluindo:
- TalkBack: ajuda pessoas com baixa visão ou cegas. Ele anuncia o conteúdo por meio de uma voz sintetizada e realiza ações em um app em resposta a gestos do usuário.
- Acesso com interruptor: ajuda pessoas com deficiências motoras. Ele destaca elementos interativos e realiza ações em resposta ao pressionamento de um botão. Ele permite controlar o dispositivo usando apenas um ou dois botões.
Para ajudar pessoas com necessidades de acessibilidade a usar seu app, seu app precisa seguir as práticas recomendadas descritas nesta página, que se baseiam nas diretrizes descritas em Tornar os apps mais acessíveis.
Elementos de etiqueta
É importante oferecer aos usuários etiquetas descritivas e úteis para cada elemento da interface interativo do seu app. Cada etiqueta precisa explicar o significado e a finalidade de um elemento específico. Leitores de tela, como o TalkBack, podem anunciar essas etiquetas para os usuários.
Na maioria dos casos, a descrição de um elemento de IU é especificada no arquivo de recurso de layout que contém o elemento. Normalmente, você adiciona etiquetas usando
o atributo contentDescription, conforme explicado no guia para tornar os apps
mais acessíveis. Há várias outras técnicas de etiquetagem descritas nas seções a seguir.
Elementos editáveis
Ao classificar elementos editáveis, como
EditText objetos, é interessante mostrar
um texto que dê um exemplo de entrada válida no próprio elemento, além de
disponibilizar este exemplo de texto para os leitores de tela. Nessas situações, é possível usar o atributo android:hint, como mostrado no snippet a seguir:
<!-- The hint text for en-US locale would be "Apartment, suite, or building". --> <EditText android:id="@+id/addressLine2" android:hint="@string/aptSuiteBuilding" ... />
Nessa situação, o objeto View precisa ter o atributo android:labelFor
definido como o ID do elemento EditText. Confira mais detalhes na seção a seguir.
Pares de elementos em que um descreve o outro
É comum que um EditText elemento tenha um objeto correspondente
View que descreva o que os usuários precisam
inserir no elemento EditText. Você pode indicar essa relação definindo o atributo android:labelFor do objeto View.
Um exemplo de classificação desses pares de elementos aparece no snippet a seguir:
<!-- Label text for en-US locale would be "Username:" --> <TextView android:id="@+id/usernameLabel" ... android:text="@string/username" android:labelFor="@+id/usernameEntry" /> <EditText android:id="@+id/usernameEntry" ... /> <!-- Label text for en-US locale would be "Password:" --> <TextView android:id="@+id/passwordLabel" ... android:text="@string/password android:labelFor="@+id/passwordEntry" /> <EditText android:id="@+id/passwordEntry" android:inputType="textPassword" ... />
Elementos em uma coleção
Ao adicionar etiquetas aos elementos de uma coleção, cada etiqueta precisa ser única. Dessa forma, os serviços de acessibilidade do sistema podem se referir a exatamente um elemento na tela quando anunciam uma etiqueta. Essa correspondência permite que os usuários saibam quando percorreram a IU ou mudaram o foco para um elemento que já conhecem.
Especificamente, inclua texto extra ou informações contextuais em
elementos dentro de layouts reutilizados, como
RecyclerView
objetos, para que cada elemento filho seja identificado de forma exclusiva.
Para fazer isso, defina a descrição do conteúdo como parte da implementação do adaptador, conforme mostrado no snippet de código a seguir:
Kotlin
data class MovieRating(val title: String, val starRating: Integer) class MyMovieRatingsAdapter(private val myData: Array<MovieRating>): RecyclerView.Adapter<MyMovieRatingsAdapter.MyRatingViewHolder>() { class MyRatingViewHolder(val ratingView: ImageView) : RecyclerView.ViewHolder(ratingView) override fun onBindViewHolder(holder: MyRatingViewHolder, position: Int) { val ratingData = myData[position] holder.ratingView.contentDescription = "Movie ${position}: " + "${ratingData.title}, ${ratingData.starRating} stars" } }
Java
public class MovieRating { private String title; private int starRating; // ... public String getTitle() { return title; } public int getStarRating() { return starRating; } } public class MyMovieRatingsAdapter extends RecyclerView.Adapter<MyAdapter.MyRatingViewHolder> { private MovieRating[] myData; public static class MyRatingViewHolder extends RecyclerView.ViewHolder { public ImageView ratingView; public MyRatingViewHolder(ImageView iv) { super(iv); ratingView = iv; } } @Override public void onBindViewHolder(MyRatingViewHolder holder, int position) { MovieRating ratingData = myData[position]; holder.ratingView.setContentDescription("Movie " + position + ": " + ratingData.getTitle() + ", " + ratingData.getStarRating() + " stars") } }
Grupos de conteúdo relacionado
Caso seu app exiba vários elementos de IU que formam um grupo natural, como
detalhes de uma música ou atributos de uma mensagem, organize esses elementos em um
contêiner, que geralmente é uma subclasse de ViewGroup. Defina o atributo android:screenReaderFocusabledo objeto de contêiner como truee o atributo android:focusablede cada objeto interno como false. Dessa forma, os serviços de acessibilidade podem apresentar as descrições de conteúdo dos elementos internos, um após o outro, em um único anúncio.
Essa consolidação de elementos relacionados ajuda os usuários de tecnologia adaptativa a descobrir as informações exibidas na tela com mais eficiência.
android:focusable
O snippet a seguir contém partes de conteúdo que se relacionam. Portanto, o elemento de contêiner, uma instância de ConstraintLayout, tem o atributo android:screenReaderFocusable definido como true, e os elementos TextView internos têm o atributo android:focusable definido como false:
<!-- In response to a single user interaction, accessibility services announce both the title and the artist of the song. --> <ConstraintLayout android:id="@+id/song_data_container" ... android:screenReaderFocusable="true"> <TextView android:id="@+id/song_title" ... android:focusable="false" android:text="@string/my_song_title" /> <TextView android:id="@+id/song_artist" android:focusable="false" android:text="@string/my_songwriter" /> </ConstraintLayout>
Como os serviços de acessibilidade anunciam as descrições dos elementos internos em uma única expressão, é importante manter cada descrição o mais curta possível, sem deixar de transmitir o significado do elemento.
Observação:em geral, evite criar uma descrição de conteúdo para um grupo agregando o texto dos filhos. Isso torna a descrição do grupo frágil e, quando o texto de um filho muda, a descrição do grupo pode não corresponder mais ao texto visível.
Em um contexto de lista ou grade, um leitor de tela pode consolidar o texto dos nós de texto filhos de um elemento de lista ou grade. É melhor evitar modificar esse anúncio.
Grupos aninhados
Se a interface do seu app apresentar informações multidimensionais, como uma lista diária de eventos de um festival, use o atributo android:screenReaderFocusable nos contêineres do grupo interno. Esse esquema de classificação oferece um bom equilíbrio entre o número de anúncios necessários para descobrir o conteúdo da tela e a duração de cada anúncio.
O snippet de código a seguir mostra um método para classificar grupos dentro de outros maiores:
<!-- In response to a single user interaction, accessibility services announce the events for a single stage only. --> <ConstraintLayout android:id="@+id/festival_event_table" ... > <ConstraintLayout android:id="@+id/stage_a_event_column" android:screenReaderFocusable="true"> <!-- UI elements that describe the events on Stage A. --> </ConstraintLayout> <ConstraintLayout android:id="@+id/stage_b_event_column" android:screenReaderFocusable="true"> <!-- UI elements that describe the events on Stage B. --> </ConstraintLayout> </ConstraintLayout>
Títulos no texto
Alguns apps usam títulos para resumir grupos de texto que aparecem na tela. Se
determinado elemento View representa um título, você pode indicar a finalidade dele
para os serviços de acessibilidade definindo o atributo
android:accessibilityHeading do elemento como
true.
Os usuários de serviços de acessibilidade podem optar por navegar entre títulos em vez de entre parágrafos ou palavras. Essa flexibilidade melhora a experiência de navegação de texto.
Títulos do painel de acessibilidade
No Android 9 (API nível 28) ou versões mais recentes, você pode fornecer títulos de fácil acessibilidade para os painéis de uma tela. Para fins de acessibilidade, um painel é uma parte visualmente distinta de uma janela, como o conteúdo de um fragmento. Para que os serviços de acessibilidade entendam o comportamento semelhante a janelas de um painel, disponibilize títulos descritivos para os painéis do seu app. Os serviços de acessibilidade podem oferecer informações mais detalhadas aos usuários quando a aparência ou o conteúdo de um painel muda.
Para especificar o título de um painel, use o atributo
android:accessibilityPaneTitle,
conforme mostrado no snippet a seguir:
<!-- Accessibility services receive announcements about content changes that are scoped to either the "shopping cart view" section (top) or "browse items" section (bottom) --> <MyShoppingCartView android:id="@+id/shoppingCartContainer" android:accessibilityPaneTitle="@string/shoppingCart" ... /> <MyShoppingBrowseView android:id="@+id/browseItemsContainer" android:accessibilityPaneTitle="@string/browseProducts" ... />
Elementos decorativos
Se um elemento na sua IU existir apenas para fins de espaçamento visual ou aparência
defina o seu
android:importantForAccessibility
atributo como "no".
Adicionar ações de acessibilidade
É importante permitir que os usuários de serviços de acessibilidade realizem facilmente todos os fluxos de usuários no seu app. Por exemplo, se um usuário puder deslizar um item em uma lista, essa ação também poderá ser exposta aos serviços de acessibilidade para que os usuários tenham uma maneira alternativa de concluir o mesmo fluxo de usuários.
Tornar todas as ações acessíveis
Um usuário do TalkBack, Voice Access, ou do Acesso com interruptor pode precisar de maneiras alternativas de concluir determinados fluxos de usuários no app. Para ações associadas a gestos, como arrastar e soltar ou deslizar, seu app pode expor as ações de uma maneira acessível aos usuários de serviços de acessibilidade.
Usando ações de acessibilidade, o app pode oferecer maneiras alternativas para os usuários concluírem uma ação.
Por exemplo, se o app permitir que os usuários deslizem um item, você também poderá expor a funcionalidade por meio de uma ação de acessibilidade personalizada, como esta:
Kotlin
ViewCompat.addAccessibilityAction( // View to add accessibility action itemView, // Label surfaced to user by an accessibility service getText(R.id.archive) ) { _, _ -> // Same method executed when swiping on itemView archiveItem() true }
Java
ViewCompat.addAccessibilityAction( // View to add accessibility action itemView, // Label surfaced to user by an accessibility service getText(R.id.archive), (view, arguments) -> { // Same method executed when swiping on itemView archiveItem(); return true; } );
With the custom accessibility action implemented, users can access the action through the actions menu.
Make available actions understandable
When a view supports actions such as touch & hold, an accessibility service such as TalkBack announces it as "Double tap and hold to long press."
This generic announcement doesn't give the user any context about what a touch & hold action does.
To make this announcement more descriptive, you can replace the accessibility action’s announcement like so:
Kotlin
ViewCompat.replaceAccessibilityAction( // View that contains touch & hold action itemView, AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_LONG_CLICK, // Announcement read by TalkBack to surface this action getText(R.string.favorite), null )
Java
ViewCompat.replaceAccessibilityAction( // View that contains touch & hold action itemView, AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_LONG_CLICK, // Announcement read by TalkBack to surface this action getText(R.string.favorite), null );
This results in TalkBack announcing "Double tap and hold to favorite," helping users understand the purpose of the action.
Extend system widgets
Note: When you design your app's UI, use or extend
system-provided widgets that are as far down Android's class hierarchy as
possible. System-provided widgets that are far down the hierarchy already
have most of the accessibility capabilities your app needs. It's easier
to extend these system-provided widgets than to create your own from the more
generic View,
ViewCompat,
Canvas, and
CanvasCompat
classes.
If you must extend View or Canvas directly, which
might be necessary for a highly customized experience or a game level, see
Make custom views more
accessible.
This section uses the example of implementing a special type of
Switch called TriSwitch while following
best practices around extending system widgets. A TriSwitch
object works similarly to a Switch object, except that each instance of
TriSwitch allows the user to toggle among three possible states.
Extend from far down the class hierarchy
The Switch object inherits from several framework UI classes in its hierarchy:
View ↳ TextView ↳ Button ↳ CompoundButton ↳ Switch
É melhor que a nova TriSwitch classe seja estendida diretamente da Switch
classe. Dessa forma, o framework de acessibilidade do Android oferece a maioria dos recursos necessários para a classe TriSwitchneeds:
- Ações de acessibilidade:informações para o sistema sobre como os serviços de acessibilidade podem emular cada entrada do usuário possível realizada em um objeto
TriSwitch. Configuração herdada deView. - Eventos de acessibilidade:informações para serviços de acessibilidade sobre todas as maneiras possíveis em que a aparência de um objeto
TriSwitchpode mudar quando a tela é atualizada. Configuração herdada deView. - Características:detalhes sobre cada objeto
TriSwitch, como o conteúdo de qualquer texto exibido. Configuração herdada deTextView. - Informações de estado:uma descrição do estado atual de um objeto
TriSwitch, como "marcado" ou "desmarcado". Configuração herdada deCompoundButton. - Descrição em texto do estado:uma explicação escrita do que cada estado representa. Configuração herdada de
Switch.
Esse comportamento de Switch e das superclasses dele é quase o
mesmo comportamento dos objetos TriSwitch. Portanto, sua implementação pode se concentrar na expansão do número de estados possíveis de dois para três.
Definir eventos personalizados
Ao estender um widget do sistema, você provavelmente muda um aspecto de como os usuários interagem com esse widget. É importante definir essas mudanças de interação para que os serviços de acessibilidade possam atualizar o widget do app como se o usuário interagisse diretamente com ele.
Uma orientação geral é que, para cada callback baseado em visualização que você substituir,
também precisará redefinir a ação de acessibilidade correspondente modificando
ViewCompat.replaceAccessibilityAction().
Nos testes do app, você pode validar o comportamento dessas ações redefinidas chamando
calling
ViewCompat.performAccessibilityAction().
Como esse princípio pode funcionar para objetos TriSwitch
Ao contrário de um objeto Switch comum, tocar em um objeto TriSwitch alterna entre
três estados possíveis. Portanto, a ação de acessibilidade ACTION_CLICK correspondente precisa ser atualizada:
Kotlin
class TriSwitch(context: Context) : Switch(context) { // 0, 1, or 2 var currentState: Int = 0 private set init { updateAccessibilityActions() } private fun updateAccessibilityActions() { ViewCompat.replaceAccessibilityAction(this, ACTION_CLICK, action-label) { view, args -> moveToNextState() }) } private fun moveToNextState() { currentState = (currentState + 1) % 3 } }
Java
public class TriSwitch extends Switch { // 0, 1, or 2 private int currentState; public int getCurrentState() { return currentState; } public TriSwitch() { updateAccessibilityActions(); } private void updateAccessibilityActions() { ViewCompat.replaceAccessibilityAction(this, ACTION_CLICK, action-label, (view, args) -> moveToNextState()); } private void moveToNextState() { currentState = (currentState + 1) % 3; } }
Outros recursos
Para saber mais sobre como tornar seu app mais acessível, consulte estes recursos extras: