Si tu aplicación requiere un componente de vistas personalizadas, debes hacer que la vista sea más accesible. Los siguientes pasos pueden mejorar la accesibilidad de tu vista personalizada, como se describe en esta página:
- Maneja los clics del controlador direccional.
- Implementa métodos de API de accesibilidad.
- Envía objetos
AccessibilityEvent
específicos a tu vista personalizada. - Completa
AccessibilityEvent
yAccessibilityNodeInfo
para tu vista.
Maneja los clics del controlador direccional
En la mayoría de los dispositivos, si haces clic en una vista que usa un controlador direccional, se envía un KeyEvent
con KEYCODE_DPAD_CENTER
a la vista que está en primer plano en ese momento. Todas las vistas estándar de Android manejan KEYCODE_DPAD_CENTER
de manera correcta. Si creas un control de View
personalizado, asegúrate de que este evento tenga el mismo efecto que presionar la vista en la pantalla táctil.
Tu control personalizado debe tratar el evento KEYCODE_ENTER
de la misma manera que a KEYCODE_DPAD_CENTER
. De esta manera, es más fácil para los usuarios interactuar con un teclado completo.
Implementa métodos de API de accesibilidad
Los eventos de accesibilidad son mensajes sobre las interacciones de los usuarios con los componentes de la interfaz visual de tu app. Estos mensajes se manejan mediante los servicios de accesibilidad, que usan la información en estos eventos para generar comentarios y solicitudes adicionales. Los métodos de accesibilidad forman parte de las clases View
y View.AccessibilityDelegate
. Los métodos son los siguientes:
dispatchPopulateAccessibilityEvent()
onPopulateAccessibilityEvent()
para esta vista y, luego, al método dispatchPopulateAccessibilityEvent()
para cada elemento secundario de esta vista. onInitializeAccessibilityEvent()
TextView
o Button
, anula este método y establece la información adicional sobre tu vista, como el tipo de campo de contraseña, el tipo de casilla de verificación o los estados que proporcionan interacción del usuario o comentarios al evento, utilizando este método. Si anulas este método, llama a su superimplementación y solo modifica las propiedades que la superclase no haya establecido.onInitializeAccessibilityNodeInfo()
View
tiene un conjunto estándar de propiedades de vistas, pero si tu vista personalizada proporciona control de interacción más allá de un simple TextView
o Button
, anula este método y establece la información adicional sobre tu vista en el objeto AccessibilityNodeInfo
controlado por este método.onPopulateAccessibilityEvent()
AccessibilityEvent
para tu vista. También se llama a este método si la vista es un elemento secundario de una vista que genera un evento de accesibilidad.
onRequestSendAccessibilityEvent()
AccessibilityEvent
. Este paso permite que la vista superior modifique el evento de accesibilidad con información adicional. Implementa este método solo si tu vista personalizada puede tener vistas secundarias y si la vista superior puede proporcionar información de contexto al evento de accesibilidad que es útil para los servicios de accesibilidad.sendAccessibilityEvent()
- El sistema llama a este método cuando un usuario realiza una acción en una vista. El evento se clasifica con un tipo de acción del usuario, por ejemplo,
TYPE_VIEW_CLICKED
. En general, debes enviar unAccessibilityEvent
siempre que cambie el contenido de tu vista personalizada. sendAccessibilityEventUnchecked()
- Este método se usa cuando el código de llamada necesita ocuparse de forma directa de verificar la habilitación de las funciones de accesibilidad en el dispositivo (
AccessibilityManager.isEnabled()
). Si implementas este método, realiza la llamada como si la accesibilidad estuviese habilitada, independientemente de la configuración del sistema. Por lo general, no es necesario implementar este método para una vista personalizada. dispatchPopulateAccessibilityEvent()
onInitializeAccessibilityEvent()
onInitializeAccessibilityNodeInfo()
onPopulateAccessibilityEvent()
TYPE_VIEW_CLICKED
TYPE_VIEW_FOCUSED
TYPE_VIEW_HOVER_ENTER
TYPE_VIEW_HOVER_EXIT
TYPE_VIEW_LONG_CLICKED
TYPE_VIEW_SCROLLED
- Generar un
AccessibilityEvent
apropiado para la acción de clic interpretada - Habilitar los servicios de accesibilidad para realizar la acción de clic personalizado para los usuarios que no pueden usar una pantalla táctil
Para admitir la accesibilidad, anula e implementa los métodos de accesibilidad anteriores directamente en tu clase de vistas personalizadas.
Como mínimo, implementa los siguientes métodos de accesibilidad para tu clase de vistas personalizadas:
Para saber más sobre la implementación de estos métodos, consulta la sección sobre carga de información para eventos de accesibilidad.
Envía eventos de accesibilidad
Según las características específicas de tu vista personalizada, es posible que necesites enviar objetos AccessibilityEvent
en momentos diferentes o para eventos que no se manejan con la implementación predeterminada. La clase View
proporciona una implementación predeterminada para estos tipos de eventos:
En general, debes enviar un AccessibilityEvent
siempre que cambie el contenido de tu vista personalizada. Por ejemplo, si implementas una barra de control deslizante personalizada que le permite al usuario seleccionar un valor numérico cuando presiona las teclas de flecha hacia la izquierda o la derecha, tu vista personalizada debe emitir un evento TYPE_VIEW_TEXT_CHANGED
cada vez que cambie el valor del control deslizante. En la siguiente muestra de código, se ilustra el uso del método sendAccessibilityEvent()
para informar este 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; } ... }
Propaga eventos de accesibilidad
Cada AccessibilityEvent
tiene un conjunto de propiedades obligatorias que describe el estado de la vista en un determinado momento. Estas propiedades incluyen datos como el nombre de clase de la vista, la descripción del contenido y el estado verificado. Las propiedades específicas obligatorias para cada tipo de evento se describen en la documentación de referencia de AccessibilityEvent
.
La implementación de View
proporciona valores predeterminados para estas propiedades. Muchos de estos valores, incluidos el nombre de la clase y la marca de tiempo del evento, se proporcionan automáticamente. Si creas un componente de vista personalizada, debes proporcionar información sobre el contenido y las características de la vista. Esta información puede ser tan simple como una etiqueta de botón e incluir información adicional sobre el estado que desees agregar al evento.
Usa los métodos onPopulateAccessibilityEvent()
y onInitializeAccessibilityEvent()
para completar o modificar la información de un AccessibilityEvent
. Usa el método onPopulateAccessibilityEvent()
específicamente para agregar o modificar contenido de texto del evento, que los servicios de accesibilidad como TalkBack convierten en solicitudes sonoras. Usa el método onInitializeAccessibilityEvent()
para cargar información adicional sobre el evento, como el estado de selección de la vista.
Además, implementa el método onInitializeAccessibilityNodeInfo()
. Los servicios de accesibilidad usan los objetos AccessibilityNodeInfo
cargados por este método para investigar la jerarquía de vistas que genera un evento de accesibilidad después de que se recibe y proporcionar comentarios adecuados a los usuarios.
En el siguiente ejemplo de código, se muestra cómo anular estos tres métodos en tu vista:
Kotlin
override fun onPopulateAccessibilityEvent(event: AccessibilityEvent?) { super.onPopulateAccessibilityEvent(event) // Call the super implementation to populate its text for the // event. Then, add text not present in a super class. // You typically only need to add the text for the custom view. if (text?.isNotEmpty() == true) { event?.text?.add(text) } } override fun onInitializeAccessibilityEvent(event: AccessibilityEvent?) { super.onInitializeAccessibilityEvent(event) // Call the super implementation to let super classes // set appropriate event properties. Then, add the new checked // property that is not supported by a super class. event?.isChecked = isChecked() } override fun onInitializeAccessibilityNodeInfo(info: AccessibilityNodeInfo?) { super.onInitializeAccessibilityNodeInfo(info) // Call the super implementation to let super classes set // appropriate info properties. Then, add the checkable and checked // properties that are not supported by a super class. info?.isCheckable = true info?.isChecked = isChecked() // You typically only need to add the text for the custom view. if (text?.isNotEmpty() == true) { info?.text = text } }
Java
@Override public void onPopulateAccessibilityEvent(AccessibilityEvent event) { super.onPopulateAccessibilityEvent(event); // Call the super implementation to populate its text for the // event. Then, add the text not present in a super class. // You typically 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(AccessibilityEvent event) { super.onInitializeAccessibilityEvent(event); // Call the super implementation to let super classes // set appropriate event properties. Then, add the new checked // property that is not supported by a super class. event.setChecked(isChecked()); } @Override public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { super.onInitializeAccessibilityNodeInfo(info); // Call the super implementation to let super classes set // appropriate info properties. Then, add the checkable and checked // properties that are not supported by a super class. info.setCheckable(true); info.setChecked(isChecked()); // You typically only need to add the text for the custom view. CharSequence text = getText(); if (!TextUtils.isEmpty(text)) { info.setText(text); } }
Puedes implementar estos métodos directamente en tu clase de vistas personalizadas.
Proporciona un contexto de accesibilidad personalizado
Los servicios de accesibilidad pueden inspeccionar la jerarquía de vistas de un componente de la interfaz de usuario que genera un evento de accesibilidad. Esto permite que los servicios de accesibilidad proporcionen información contextual más útil a los usuarios.
Hay casos en los que los servicios de accesibilidad no pueden obtener información adecuada de la jerarquía de vistas. Un ejemplo es un control de interfaz personalizada que tiene dos o más áreas en las que se puede hacer clic por separado, como un control de calendario. En este caso, los servicios no pueden obtener información adecuada, ya que las subsecciones en las que se puede hacer clic no forman parte de la jerarquía de vistas.
En el ejemplo de la figura 1, todo el calendario se implementa como una sola vista, por lo que los servicios de accesibilidad no reciben suficiente información sobre el contenido de la vista y la selección del usuario en ella, a menos que el desarrollador brinde información adicional. Por ejemplo, si un usuario hace clic en el día con la etiqueta 17, el framework de accesibilidad solo recibe la información de descripción de todo el control de calendario. En este caso, el servicio de accesibilidad de TalkBack anuncia "Calendario" o "Calendario de abril", y el usuario no sabe qué día está seleccionado.
Para proporcionar información de contexto adecuada para los servicios de accesibilidad en situaciones como esta, el framework proporciona una manera de especificar una jerarquía de vistas virtual. Una jerarquía de vistas virtual les permite a los desarrolladores de apps proporcionar una jerarquía de vistas complementaria a los servicios de accesibilidad que se ajusta más a la información en la pantalla. Este enfoque permite que los servicios de accesibilidad proporcionen información de contexto más útil a los usuarios.
Otra situación en la que podría ser necesaria una jerarquía de vistas virtual es cuando una interfaz de usuario contiene un conjunto de controles View
con funciones estrechamente relacionadas, en el que una acción en un control afecta el contenido de uno o más elementos, como un selector de números con botones hacia arriba y hacia abajo separados. En este caso, los servicios de accesibilidad no pueden obtener información adecuada, ya que la acción en un control cambia el contenido en otro y el servicio podría no identificar la relación de esos controles.
Para manejar esta situación, agrupa los controles relacionados con una vista contenedora y proporciona una jerarquía de vistas virtual desde este contenedor para representar claramente la información y el comportamiento que proporcionan los controles.
Para proporcionar una jerarquía de vistas virtual para una vista, anula el método getAccessibilityNodeProvider()
en tu vista personalizada o grupo de vistas y muestra una implementación de AccessibilityNodeProvider
.
Puedes implementar una jerarquía de vistas virtual mediante la biblioteca de compatibilidad con el método ViewCompat.getAccessibilityNodeProvider()
y brindar una implementación con AccessibilityNodeProviderCompat
.
Para simplificar la tarea de proporcionar información a los servicios de accesibilidad y administrar el enfoque de accesibilidad, puedes implementar ExploreByTouchHelper
.
Brinda un AccessibilityNodeProviderCompat
y se puede adjuntar como el AccessibilityDelegateCompat
de una vista si se llama a setAccessibilityDelegate
.
Para ver un ejemplo, consulta ExploreByTouchHelperActivity
.
Los widgets del framework, como CalendarView
, también usan ExploreByTouchHelper
a través de su vista secundaria SimpleMonthView
.
Controla eventos táctiles personalizados
Es posible que los controles de vistas personalizadas requieran un comportamiento no estándar de eventos táctiles, como se muestra en los ejemplos que aparecen más abajo.
Cómo definir acciones basadas en clics
Si tu widget usa la interfaz OnClickListener
o OnLongClickListener
, el sistema controlará las acciones ACTION_CLICK
y ACTION_LONG_CLICK
por ti. Si tu app usa un widget más personalizado que se basa en la interfaz OnTouchListener
, define controladores personalizados para las acciones de accesibilidad basadas en clics. Para hacerlo, llama al método replaceAccessibilityAction()
de cada acción, como se muestra en el siguiente fragmento de código:
Kotlin
override fun onCreate(savedInstanceState: Bundle?) { ... // Assumes that the widget is designed to select text when tapped, and selects // all text when tapped and held. In its strings.xml file, this app sets // "select" to "Select" and "select_all" to "Select all". ViewCompat.replaceAccessibilityAction( binding.textSelectWidget, ACTION_CLICK, getString(R.string.select) ) { view, commandArguments -> selectText() } ViewCompat.replaceAccessibilityAction( binding.textSelectWidget, ACTION_LONG_CLICK, getString(R.string.select_all) ) { view, commandArguments -> selectAllText() } }
Java
@Override protected void onCreate(Bundle savedInstanceState) { ... // Assumes that the widget is designed to select text when tapped, and select // all text when tapped and held. In its strings.xml file, this app sets // "select" to "Select" and "select_all" to "Select all". ViewCompat.replaceAccessibilityAction( binding.textSelectWidget, ACTION_CLICK, getString(R.string.select), (view, commandArguments) -> selectText()); ViewCompat.replaceAccessibilityAction( binding.textSelectWidget, ACTION_LONG_CLICK, getString(R.string.select_all), (view, commandArguments) -> selectAllText()); }
Cómo crear eventos de clics personalizados
Un control personalizado puede usar el método de objeto de escucha de onTouchEvent(MotionEvent)
para detectar los eventos de ACTION_DOWN
y ACTION_UP
, y activar un evento de clic especial. Para mantener la compatibilidad con los servicios de accesibilidad, el código que controla este evento de clic personalizado debe hacer lo siguiente:
Para manejar estos requisitos de manera eficiente, tu código debe anular el método performClick()
, que debe llamar a la superimplementación de este método y, luego, ejecutar las acciones que requiera el evento de clic. Cuando se detecta la acción de clic personalizado, el código debe llamar a tu método de performClick()
. En el siguiente ejemplo de código, se demuestra este patrón.
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 // enable accessibility services to // perform this action for a user who can't // tap 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 // enable accessibility services to // perform this action for a user who can't // tap 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; } }
El patrón anterior ayuda a garantizar que el evento de clic personalizado sea compatible con los servicios de accesibilidad. Para ello, usa el método performClick()
, genera un evento de accesibilidad y proporciona un punto de entrada para que los servicios de accesibilidad actúen en nombre de un usuario que realiza el evento de clic personalizado.