Caso seu aplicativo precise de um componente de visualização personalizado, vale a pena se esforçar para tornar a visualização mais acessível. Estas são as principais tarefas para melhorar a acessibilidade da sua visualização personalizada:
- Processar cliques do controle direcional.
- Implementar métodos da API de acessibilidade
- Enviar objetos
AccessibilityEvent
específicos para sua visualização personalizada. - Preencher
AccessibilityEvent
eAccessibilityNodeInfo
na visualização.
Processar cliques do controle direcional
Na maioria dos dispositivos, clicar em uma visualização usando um controle direcional envia um KeyEvent
com KEYCODE_DPAD_CENTER
para a visualização em foco
no momento. Todas as visualizações padrão do Android já processam KEYCODE_DPAD_CENTER
corretamente. Ao criar um controle View
personalizado, verifique se esse evento tem o mesmo efeito que tocar a visualização na tela
touchscreen.
Seu controle personalizado também precisa tratar o evento KEYCODE_ENTER
da mesma
forma que KEYCODE_DPAD_CENTER
. Essa abordagem facilita muito a interação a partir de um
teclado completo para os usuários.
Implementar métodos da API de acessibilidade
Os eventos de acessibilidade são mensagens sobre a interação do usuário com componentes da interface visual
no seu aplicativo. Essas mensagens são processadas pelos Serviços de acessibilidade,
que usam as informações nesses eventos para gerar feedback e solicitações complementares. No
Android 4.0 (API de nível 14) e versões mais recentes, os métodos para
gerar eventos de acessibilidade foram ampliados para oferecer informações mais detalhadas do que
a interface AccessibilityEventSource
introduzida no Android 1.6 (API de nível 4). Os métodos de acessibilidade ampliados fazem parte das classes View
e View.AccessibilityDelegate
. Os métodos são os seguintes:
sendAccessibilityEvent()
- (API de nível 4): esse método é chamado quando um usuário realiza ações em uma visualização. O evento é
classificado com um tipo de ação do usuário, como
TYPE_VIEW_CLICKED
. Normalmente, não é necessário implementar esse método, a menos que você esteja criando uma visualização personalizada. sendAccessibilityEventUnchecked()
- (API de nível 4): esse método é usado quando o código de chamada precisa controlar diretamente a verificação
de acessibilidade ativada no dispositivo (
AccessibilityManager.isEnabled()
). Se você implementar esse método, será necessário realizar a chamada como se a acessibilidade estivesse ativada, independentemente da configuração real do sistema. Normalmente, não é necessário implementar esse método para uma visualização personalizada. dispatchPopulateAccessibilityEvent()
- (API de nível 4): o sistema chama esse método quando sua exibição personalizada gera um
evento de acessibilidade. Na API de nível 14, a implementação padrão desse método chama
onPopulateAccessibilityEvent()
para a visualização. Em seguida, chama o métododispatchPopulateAccessibilityEvent()
para cada filha dessa visualização. Para oferecer compatibilidade com os serviços de acessibilidade nas revisões do Android anteriores à 4.0 (API de nível 14), é necessário modificar esse método e preenchergetText()
com texto descritivo para sua visualização personalizada, falada por serviços de acessibilidade como o "TalkBack". onPopulateAccessibilityEvent()
- (API de nível 14): esse método define a solicitação de texto falado do
AccessibilityEvent
para sua visualização. Esse método também será chamado se a visualização for filha de uma outra que gera um evento de acessibilidade.Observação: a modificação de outros atributos além do texto nesse método pode modificar as propriedades definidas por outros métodos. Embora você possa modificar os atributos do evento de acessibilidade com esse método, limite essas mudanças ao conteúdo de texto e use o método
onInitializeAccessibilityEvent()
para modificar outras propriedades do evento.Observação: se a implementação desse evento modificar completamente o texto de saída sem permitir que outras partes do layout modifiquem o próprio conteúdo, não chame a superimplementação desse método no seu código.
onInitializeAccessibilityEvent()
- (API de nível 14): o sistema chama esse método para acessar outras informações sobre o
estado da visualização, além do conteúdo de texto. Se a visualização personalizada fornecer controles de interação além de um
simples
TextView
ouButton
, modifique esse método e defina as outras informações sobre sua visualização no evento usando esse método, como tipo de campo de senha, tipo de caixa de seleção ou estados que oferecem feedback ou interação do usuário. Se você modificar esse método, será necessário chamar a superimplementação e, em seguida, modificar somente as propriedades que não tenham sido definidas pela superclasse. onInitializeAccessibilityNodeInfo()
- (API de nível 14): esse método disponibiliza informações sobre o estado da visualização aos serviços
de acessibilidade. A implementação padrão
View
tem um conjunto modelo de propriedades de visualização, mas se a visualização personalizada oferecer controle de interação além de um simplesTextView
ouButton
, modifique esse método e defina as outras informações sobre sua visualização no objetoAccessibilityNodeInfo
processado por esse método. onRequestSendAccessibilityEvent()
- (API de nível 14): o sistema chama esse método quando uma filha da visualização gera um
AccessibilityEvent
. Essa etapa permite que a visualização mãe mude o evento de acessibilidade com outras informações. Implemente esse método somente se a visualização personalizada puder ter visualizações filhas e se a mãe puder fornecer informações de contexto ao evento que possam ser importantes para os serviços de acessibilidade.
Para oferecer compatibilidade com esses métodos de acessibilidade para uma visualização personalizada, siga uma das seguintes abordagens:
- Se o aplicativo for direcionado ao Android 4.0 (nível 14 da API) ou versões mais recentes, substitua e implemente os métodos de acessibilidade listados acima diretamente na classe da sua visualização personalizada.
- Se você pretende que a visualização personalizada seja compatível com o Android 1.6 (nível 4 da API) ou versões mais recentes, adicione
ao seu projeto a Biblioteca de Suporte do Android, revisão 5 ou
mais recente. Em seguida, na classe de visualização personalizada, chame
o método
ViewCompat.setAccessibilityDelegate()
para implementar os métodos de acessibilidade acima. Para ver um exemplo dessa abordagem, consulte a amostraAccessibilityDelegateSupportActivity
da Biblioteca de Suporte do Android (revisão 5 ou mais recente) em (<sdk>/extras/android/support/v4/samples/Support4Demos/
).
Nos dois casos, implemente os seguintes métodos de acessibilidade para a classe da visualização personalizada:
dispatchPopulateAccessibilityEvent()
onPopulateAccessibilityEvent()
onInitializeAccessibilityEvent()
onInitializeAccessibilityNodeInfo()
Para saber mais sobre como implementar esses métodos, consulte Preencher eventos de acessibilidade.
Enviar eventos de acessibilidade
Dependendo das especificidades da sua visualização personalizada, pode ser necessário enviar objetos AccessibilityEvent
em momentos diferentes ou para eventos não
processados pela implementação padrão. A classe View
oferece uma implementação
padrão para esses tipos de evento:
- A partir da API de nível 4:
- A partir da API de nível 14:
Observação: os eventos de passagem de cursor são associados ao recurso "Explorar por toque", que usa esses eventos como acionadores para emitir avisos sonoros para elementos da interface do usuário.
Em geral, será necessário enviar um AccessibilityEvent
sempre que o
conteúdo da visualização personalizada mudar. Por exemplo, se você estiver implementando uma barra de controle deslizante personalizada que
permite ao usuário selecionar um valor numérico pressionando as setas para a esquerda ou para a direita, sua visualização personalizada
precisará emitir um evento do tipo TYPE_VIEW_TEXT_CHANGED
sempre que o valor do controle
deslizante mudar. A amostra de código a seguir demonstra o uso do método sendAccessibilityEvent()
para relatar esse evento.
Kotlin
override fun onKeyUp(keyCode: Int, event: KeyEvent): Boolean { return when(keyCode) { KeyEvent.KEYCODE_DPAD_LEFT -> { currentValue-- sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED) true } ... } }
Java
@Override public boolean onKeyUp (int keyCode, KeyEvent event) { if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT) { currentValue--; sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED); return true; } ... }
Preencher eventos de acessibilidade
Cada AccessibilityEvent
tem um conjunto de propriedades obrigatórias que
descrevem o estado atual da visualização. Essas propriedades incluem itens como o nome da classe da
visualização, a descrição do conteúdo e o estado verificado. As propriedades específicas obrigatórias para cada tipo de
evento são descritas na documentação de referência AccessibilityEvent
.
A implementação View
oferece valores padrão para essas propriedades. Muitos
desses valores, incluindo o nome da classe e o carimbo de data/hora do evento, são disponibilizados automaticamente. Se você estiver
criando um componente de visualização personalizado, será preciso fornecer algumas informações sobre o conteúdo e
as características da visualização. Essas informações podem ser simples como uma etiqueta de botão, mas também podem incluir outras informações de estado que você queira adicionar ao evento.
O requisito mínimo para fornecer informações para serviços de acessibilidade com uma visualização personalizada é a implementação de dispatchPopulateAccessibilityEvent()
. Esse método é chamado pelo sistema para solicitar
informações para um AccessibilityEvent
e torna sua visualização
personalizada compatível com os serviços de acessibilidade no Android 1.6 (nível 4 da API) e versões mais recentes. O código de exemplo a seguir mostra uma implementação básica desse método.
Kotlin
override fun dispatchPopulateAccessibilityEvent(event: AccessibilityEvent): Boolean { // Call the super implementation to populate its text to the event, which // calls onPopulateAccessibilityEvent() on API Level 14 and up. return super.dispatchPopulateAccessibilityEvent(event).let { completed -> // In case this is running on a API revision earlier that 14, check // the text content of the event and add an appropriate text // description for this custom view: if (text?.isNotEmpty() == true) { event.text.add(text) true } else { completed } } }
Java
@Override public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) { // Call the super implementation to populate its text to the event, which // calls onPopulateAccessibilityEvent() on API Level 14 and up. boolean completed = super.dispatchPopulateAccessibilityEvent(event); // In case this is running on a API revision earlier that 14, check // the text content of the event and add an appropriate text // description for this custom view: CharSequence text = getText(); if (!TextUtils.isEmpty(text)) { event.getText().add(text); return true; } return completed; }
Para o Android 4.0 (API de nível 14) ou versões mais recentes, use os métodos onPopulateAccessibilityEvent()
e onInitializeAccessibilityEvent()
para preencher ou modificar as informações em um AccessibilityEvent
. Use o método onPopulateAccessibilityEvent()
especificamente para adicionar ou modificar o conteúdo de texto do evento, que é transformado em solicitações audíveis por serviços de acessibilidade, como o "TalkBack". Use o método onInitializeAccessibilityEvent()
para preencher outras informações sobre o evento, como o estado de seleção da visualização.
Além disso, implemente o método onInitializeAccessibilityNodeInfo()
. Os objetos AccessibilityNodeInfo
preenchidos por esse método
são usados pelos serviços de acessibilidade para investigar a hierarquia de visualização que gerou um evento de
acessibilidade depois de receber esse evento, com o objetivo de conferir informações de contexto mais detalhadas e oferecer
feedback adequado aos usuários.
O exemplo de código abaixo mostra como substituir esses três métodos usando
ViewCompat.setAccessibilityDelegate()
. Observe que esta amostra de código requer que a Biblioteca de Suporte do Android para a API de nível 4 (revisão 5 ou mais recente) seja adicionada ao projeto.
Kotlin
ViewCompat.setAccessibilityDelegate(this, object : AccessibilityDelegateCompat() { override fun onPopulateAccessibilityEvent(host: View, event: AccessibilityEvent) { super.onPopulateAccessibilityEvent(host, event) // We call the super implementation to populate its text for the // event. Then we add our text not present in a super class. // Very often you only need to add the text for the custom view. if (text?.isNotEmpty() == true) { event.text.add(text) } } override fun onInitializeAccessibilityEvent(host: View, event: AccessibilityEvent) { super.onInitializeAccessibilityEvent(host, event); // We call the super implementation to let super classes // set appropriate event properties. Then we add the new property // (checked) which is not supported by a super class. event.isChecked = isChecked() } override fun onInitializeAccessibilityNodeInfo(host: View, info: AccessibilityNodeInfoCompat) { super.onInitializeAccessibilityNodeInfo(host, info) // We call the super implementation to let super classes set // appropriate info properties. Then we add our properties // (checkable and checked) which are not supported by a super class. info.isCheckable = true info.isChecked = isChecked() // Quite often you only need to add the text for the custom view. if (text?.isNotEmpty() == true) { info.text = text } } })
Java
ViewCompat.setAccessibilityDelegate(new AccessibilityDelegateCompat() { @Override public void onPopulateAccessibilityEvent(View host, AccessibilityEvent event) { super.onPopulateAccessibilityEvent(host, event); // We call the super implementation to populate its text for the // event. Then we add our text not present in a super class. // Very often you only need to add the text for the custom view. CharSequence text = getText(); if (!TextUtils.isEmpty(text)) { event.getText().add(text); } } @Override public void onInitializeAccessibilityEvent(View host, AccessibilityEvent event) { super.onInitializeAccessibilityEvent(host, event); // We call the super implementation to let super classes // set appropriate event properties. Then we add the new property // (checked) which is not supported by a super class. event.setChecked(isChecked()); } @Override public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfoCompat info) { super.onInitializeAccessibilityNodeInfo(host, info); // We call the super implementation to let super classes set // appropriate info properties. Then we add our properties // (checkable and checked) which are not supported by a super class. info.setCheckable(true); info.setChecked(isChecked()); // Quite often you only need to add the text for the custom view. CharSequence text = getText(); if (!TextUtils.isEmpty(text)) { info.setText(text); } } }
É possível implementar esses métodos
diretamente na sua classe de visualização personalizada. Para ver outro exemplo dessa abordagem, consulte a amostra AccessibilityDelegateSupportActivity
da Biblioteca de Suporte do Android (revisão 5 ou mais recente) em (<sdk>/extras/android/support/v4/samples/Support4Demos/
).
Oferecer um contexto de acessibilidade personalizado
No Android 4.0 (API de nível 14), o framework foi aprimorado para permitir que os serviços de acessibilidade inspecionem a hierarquia de visualização de um componente da interface do usuário que gera um evento de acessibilidade. Essa melhoria permite que os serviços de acessibilidade disponibilizem um conjunto de informações contextuais muito mais detalhadas para ajudar os usuários.
Há alguns casos em que os serviços de acessibilidade não recebem informações adequadas da hierarquia da visualização. Um exemplo disso é um controle de interface personalizado que tem duas ou mais áreas clicáveis separadas, como um controle de agenda. Nesse caso, os serviços não recebem as informações adequadas porque as subseções clicáveis não fazem parte da hierarquia de visualização.

Figura 1. Visualização de agenda personalizada com elementos de dia selecionáveis.
No exemplo mostrado na Figura 1, toda a agenda é implementada como uma única visualização. Portanto, se você não fizer mais nada, os serviços de acessibilidade não receberão informações suficientes sobre o conteúdo da visualização e a seleção do usuário nela. Por exemplo, se o usuário clicar no dia 17, o framework de acessibilidade receberá apenas as informações de descrição de todo o controle da agenda. Nesse caso, o serviço de acessibilidade do "TalkBack" apenas anunciaria "Agenda" ou, um pouco melhor, "Agenda de abril", mas o usuário ficaria sem saber que dia foi selecionado.
Para oferecer informações de contexto adequadas para serviços de acessibilidade em situações como essa, o framework traz uma maneira de especificar uma hierarquia de visualização virtual. Uma hierarquia de visualização virtual é uma maneira de os desenvolvedores de aplicativos fornecerem uma hierarquia de visualização complementar aos serviços de acessibilidade, que corresponde melhor às informações reais na tela. Essa abordagem permite que os serviços de acessibilidade ofereçam informações de contexto mais úteis aos usuários.
Outra situação em que uma hierarquia de visualização virtual pode ser necessária é uma interface do usuário que contém um conjunto de controles (visualizações) com funções estreitamente relacionadas, em que uma ação em um controle afeta o conteúdo de um ou mais elementos, como um seletor de número com botões diferentes para cima e para baixo. Nesse caso, os serviços de acessibilidade não recebem as informações adequadas porque a ação em um controle muda o conteúdo de outro, e a relação desses controles pode não estar visível para o serviço. Para lidar com essa situação, agrupe os controles relacionados a uma visualização contida e forneça uma hierarquia de visualização virtual desse contêiner para representar claramente as informações e o comportamento disponibilizados pelos controles.
Para oferecer uma hierarquia virtual para uma visualização, substitua o método
getAccessibilityNodeProvider()
na visualização personalizada ou no grupo e retorne uma implementação de
AccessibilityNodeProvider
.
É possível implementar uma hierarquia de visualização virtual compatível com o Android 1.6 e versões mais recentes usando a
Biblioteca de Suporte com o
método ViewCompat.getAccessibilityNodeProvider()
e fornecendo uma implementação com
AccessibilityNodeProviderCompat
.
Para simplificar muitos aspectos do fornecimento de informações aos serviços de acessibilidade e gerenciar
o foco em acessibilidade, considere implementar
ExploreByTouchHelper
,
que fornece um
AccessibilityNodeProviderCompat
e pode ser anexado como um AccessibilityDelegateCompat
de uma visualização.
Para conferir um exemplo, consulte
ExploreByTouchHelperActivity
.
ExploreByTouchHelper
também é usado por widgets de framework, como
CalendarView
,
pela visualização filha
SimpleMonthView
.
Processar eventos de toque personalizados
Os controles de visualização personalizados podem exigir um comportamento de evento de toque não padrão, conforme demonstrado nos exemplos a seguir.
Definir ações com base em cliques
Se o widget usar o
OnClickListener
,
o
OnLongClickListener
ou
a interface, o sistema gerenciará as ações
ACTION_CLICK
e
ACTION_LONG_CLICK
para você. No entanto, se o app usar um widget mais personalizado que depende da interface
OnTouchListener
,
você precisará definir gerenciadores personalizados para as ações de acessibilidade com base
em cliques. Para fazer isso, chame o método
replaceAccessibilityAction()
para cada ação, conforme mostrado no snippet de código a seguir.
Kotlin
// Assumes that the widget is designed to select text when tapped and select // all text when long-tapped. In its strings.xml file, this app has set // "select" to "Select" and "select_all" to "Select all", respectively. ViewCompat.replaceAccessibilityAction( WIDGET, ACTION_CLICK, context.getString(R.string.select) ) { view, commandArguments -> selectText() } ViewCompat.replaceAccessibilityAction( WIDGET, ACTION_LONG_CLICK, context.getString(R.string.select_all) ) { view, commandArguments -> selectAllText() }
Java
// Assumes that the widget is designed to select text when tapped and select // all text when long-tapped. In its strings.xml file, this app has set // "select" to "Select" and "select_all" to "Select all", respectively. ViewCompat.replaceAccessibilityAction(WIDGET, ACTION_CLICK, context.getString(R.string.select), (view, commandArguments) -> { selectText(); }); ViewCompat.replaceAccessibilityAction(WIDGET, ACTION_LONG_CLICK, context.getString(R.string.select_all), (view, commandArguments) -> { selectAllText(); });
Criar eventos de clique personalizados
Um controle personalizado pode usar o método listener onTouchEvent(MotionEvent)
para detectar os eventos
ACTION_DOWN
e ACTION_UP
e acionar um evento de clique especial. Para manter a compatibilidade com os serviços de acessibilidade, o código que processa esse evento de clique personalizado precisa fazer o seguinte:
- Gerar um
AccessibilityEvent
adequado para a ação de clique interpretada. - Ativar os serviços de acessibilidade para realizar a ação de clique personalizada para usuários que não podem usar uma tela touchscreen.
Para processar esses requisitos de maneira eficiente, seu código precisa modificar o método performClick()
, que precisa chamar a superimplementação desse método e executar as ações necessárias para o evento de clique. Quando a ação de clique personalizada for detectada, o código chamará o método performClick()
. O exemplo de código a seguir demonstra esse padrão.
Kotlin
class CustomTouchView(context: Context) : View(context) { var downTouch = false override fun onTouchEvent(event: MotionEvent): Boolean { super.onTouchEvent(event) // Listening for the down and up touch events return when (event.action) { MotionEvent.ACTION_DOWN -> { downTouch = true true } MotionEvent.ACTION_UP -> if (downTouch) { downTouch = false performClick() // Call this method to handle the response, and // thereby enable accessibility services to // perform this action for a user who cannot // click the touchscreen. true } else { false } else -> false // Return false for other touch events } } override fun performClick(): Boolean { // Calls the super implementation, which generates an AccessibilityEvent // and calls the onClick() listener on the view, if any super.performClick() // Handle the action for the custom click here return true } }
Java
class CustomTouchView extends View { public CustomTouchView(Context context) { super(context); } boolean downTouch = false; @Override public boolean onTouchEvent(MotionEvent event) { super.onTouchEvent(event); // Listening for the down and up touch events switch (event.getAction()) { case MotionEvent.ACTION_DOWN: downTouch = true; return true; case MotionEvent.ACTION_UP: if (downTouch) { downTouch = false; performClick(); // Call this method to handle the response, and // thereby enable accessibility services to // perform this action for a user who cannot // click the touchscreen. return true; } } return false; // Return false for other touch events } @Override public boolean performClick() { // Calls the super implementation, which generates an AccessibilityEvent // and calls the onClick() listener on the view, if any super.performClick(); // Handle the action for the custom click here return true; } }
O padrão mostrado acima garante que o evento de clique personalizado seja compatível com
os serviços de acessibilidade, usando o método performClick()
para gerar
um evento e para fornecer um ponto de entrada de serviços de modo a agir em nome de um
usuário e executar esse evento de clique personalizado.
Observação: se a visualização personalizada tiver regiões clicáveis diferentes, por exemplo,
uma visualização de agenda personalizada, é necessário implementar uma hierarquia de visualizações
virtual substituindo getAccessibilityNodeProvider()
na visualização
personalizada para que ela seja compatível com os serviços de acessibilidade.