If your application requires a custom view component, you must make the view more accessible. The following steps can improve your custom view's accessibility, as described on this page:
- Handle directional controller clicks.
- Implement accessibility API methods.
- Send
AccessibilityEvent
objects specific to your custom view. - Populate
AccessibilityEvent
andAccessibilityNodeInfo
for your view.
Handle directional controller clicks
On most devices, clicking a view using a directional controller sends a
KeyEvent
with
KEYCODE_DPAD_CENTER
to the view currently in focus. All standard Android views handle
KEYCODE_DPAD_CENTER
appropriately. When building a custom
View
control, make sure this event has the same effect as tapping the view on the touchscreen.
Your custom control must treat the
KEYCODE_ENTER
event the same as KEYCODE_DPAD_CENTER
. This makes interactions with a full keyboard
easier for users.
Implement accessibility API methods
Accessibility events are messages about users' interactions with your app's visual interface
components. These messages are handled by accessibility services, which
use the information in these events to produce supplemental feedback and prompts. The accessibility
methods are part of the View
and
View.AccessibilityDelegate
classes. The methods are as follows:
dispatchPopulateAccessibilityEvent()
onPopulateAccessibilityEvent()
for this view
and then the dispatchPopulateAccessibilityEvent()
method for each child of this
view. onInitializeAccessibilityEvent()
TextView
or
Button
, override this method
and set the additional information about your view—such as password field type, checkbox
type, or states that provide user interaction or feedback into the event—using this
method. If you override this method, call its super implementation and only modify properties
that are not set by the super class.onInitializeAccessibilityNodeInfo()
View
implementation has a standard set of view properties, but if your
custom view provides interactive control beyond a simple TextView
or
Button
, override this method and set the additional information about your view
into the AccessibilityNodeInfo
object handled by this method.onPopulateAccessibilityEvent()
AccessibilityEvent
for your
view. It is also called if the view is a child of a view that generates an accessibility
event.
onRequestSendAccessibilityEvent()
AccessibilityEvent
. This step lets the parent view amend the accessibility
event with additional information. Implement this method only if your custom view can have
child views and if the parent view can provide context information to the accessibility
event that is useful to accessibility services.sendAccessibilityEvent()
- The system calls this method when a user takes action on a view. The event is classified with
a user action type, such as
TYPE_VIEW_CLICKED
. In general, you must send anAccessibilityEvent
whenever the content of your custom view changes. sendAccessibilityEventUnchecked()
- This method is used when the calling code needs to directly control the check for
accessibility being enabled on the device
(
AccessibilityManager.isEnabled()
). If you implement this method, perform the call as if accessibility is enabled, regardless of the system setting. You typically don't need to implement this method for a custom view. 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
- Generate an appropriate
AccessibilityEvent
for the interpreted click action. - Enable accessibility services to perform the custom click action for users who are unable to use a touch screen.
To support accessibility, override and implement the preceding accessibility methods directly in your custom view class.
At minimum, implement the following accessibility methods for your custom view class:
For more information about implementing these methods, see the section about populating accessibility events.
Send accessibility events
Depending on the specifics of your custom view, it might need to send
AccessibilityEvent
objects at different times or for events not handled by the default
implementation. The View
class provides a default implementation for these event
types:
In general, you must send an AccessibilityEvent
whenever the content of your custom
view changes. For example, if you are implementing a custom slider bar that lets the user select a
numeric value by pressing the left or right arrow key, your custom view must emit an event of
TYPE_VIEW_TEXT_CHANGED
whenever the slider value changes. The following code sample demonstrates the use of the
sendAccessibilityEvent()
method to report this event.
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; } ... }
Populate accessibility events
Each AccessibilityEvent
has a set of required properties that describe the current
state of the view. These properties include things such as the view's class name, content
description, and checked state. The specific properties required for each event type are described
in the
AccessibilityEvent
reference documentation.
The View
implementation provides default values for these
required properties. Many of these values, including the class name and event timestamp, are
provided automatically. If you are creating a custom view component, you must provide information
about the content and characteristics of the view. This information can be as simple as a button
label and can include additional state information that you want to add to the event.
Use the
onPopulateAccessibilityEvent()
and
onInitializeAccessibilityEvent()
methods to populate or modify the information in an AccessibilityEvent
. Use the
onPopulateAccessibilityEvent()
method specifically for adding or modifying the text
content of the event, which is turned into audible prompts by accessibility services such as
TalkBack. Use the onInitializeAccessibilityEvent()
method for populating additional
information about the event, such as the selection state of the view.
In addition, implement the
onInitializeAccessibilityNodeInfo()
method. Accessibility services use the AccessibilityNodeInfo
objects populated by this
method to investigate the view hierarchy that generates an accessibility event after it is received
and provide appropriate feedback to users.
The following code example shows how to override these three methods in your view:
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); } }
You can implement these methods directly in your custom view class.
Provide a customized accessibility context
Accessibility services can inspect the containing view hierarchy of a user interface component that generates an accessibility event. This lets accessibility services provide richer contextual information to aid users.
There are cases where accessibility services can't get adequate information from the view hierarchy. An example of this is a custom interface control that has two or more separately clickable areas, such as a calendar control. In this case, the services can't get adequate information because the clickable subsections are not part of the view hierarchy.
In the example in figure 1, the entire calendar is implemented as a single view, so accessibility services don't receive enough information about the content of the view and the user's selection within the view unless the developer provides additional information. For example, if a user clicks on the day labeled 17, the accessibility framework only receives the description information for the whole calendar control. In this case, the TalkBack accessibility service announces "Calendar" or "April Calendar," and the user doesn't know what day is selected.
To provide adequate context information for accessibility services in situations like this, the framework provides a way to specify a virtual view hierarchy. A virtual view hierarchy is a way for app developers to provide a complementary view hierarchy to accessibility services that more closely matches the information on screen. This approach lets accessibility services provide more useful context information to users.
Another situation where a virtual view hierarchy might be needed is a user interface containing
a set of View
controls that have closely related functions, where an action on one
control affects the contents of one or more elements—such as a number picker with separate up
and down buttons. In this case, accessibility services can't get adequate information because an
action on one control changes content in another, and the relationship of those controls might not
be apparent to the service.
To handle this situation, group the related controls with a containing view and provide a virtual view hierarchy from this container to clearly represent the information and behavior provided by the controls.
To provide a virtual view hierarchy for a view, override the
getAccessibilityNodeProvider()
method in your custom view or view group and return an implementation of
AccessibilityNodeProvider
.
You can implement a virtual view hierarchy by using the Support Library with the
ViewCompat.getAccessibilityNodeProvider()
method and provide an implementation with
AccessibilityNodeProviderCompat
.
To simplify the task of providing information to accessibility services and managing
accessibility focus, you can instead implement
ExploreByTouchHelper
.
It provides an AccessibilityNodeProviderCompat
and can be attached as a view's
AccessibilityDelegateCompat
by calling
setAccessibilityDelegate
.
For an example, see
ExploreByTouchHelperActivity
.
ExploreByTouchHelper
is also used by framework widgets such as
CalendarView
, through its
child view
SimpleMonthView
.
Handle custom touch events
Custom view controls might require non-standard touch event behavior, as demonstrated in the following examples.
Define click-based actions
If your widget uses the
OnClickListener
or
OnLongClickListener
interface, the system handles the
ACTION_CLICK
and
ACTION_LONG_CLICK
actions for you. If your app uses a more customized widget that relies on the
OnTouchListener
interface,
define custom handlers for the click-based accessibility actions. To do so, call the
replaceAccessibilityAction()
method for each action, as shown in the following code snippet:
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()); }
Create custom click events
A custom control can use the onTouchEvent(MotionEvent)
listener method to detect the
ACTION_DOWN
and
ACTION_UP
events and
trigger a special click event. To maintain compatibility with accessibility services, the code that
handles this custom click event must do the following:
To handle these requirements efficiently, your code must override the
performClick()
method,
which must call the super implementation of this method and then execute whatever actions are
required by the click event. When the custom click action is detected, that code must then call your
performClick()
method. The following code example demonstrates this pattern.
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; } }
The preceding pattern helps ensure that the custom click event is compatible with accessibility
services by using the performClick()
method to generate an accessibility event and
provide an entry point for accessibility services to act on behalf of a user performing the custom
click event.