虽然许多 Android TV 应用都是使用原生 Android 组件构建的,但考虑第三方框架或组件的可访问性也很重要,尤其是在使用自定义视图时。
直接与 OpenGL 或 Canvas 交互的自定义视图组件可能无法很好地与 TalkBack 和开关控制等无障碍服务搭配使用。
请考虑以下在开启 TalkBack 时可能会出现的问题:
- 应用中的无障碍功能焦点(绿色矩形)可能会消失。
- 无障碍功能焦点可能会选择整个屏幕的边界。
- 无障碍焦点可能无法移动。
- 即使您的代码正在处理 D-pad 上的四个方向键,这些按键也可能不起作用。
如果您在应用中发现上述任何问题,请检查应用是否向无障碍服务公开了其 AccessibilityNodeInfo 树。
本指南的其余部分提供了一些解决方案和最佳实践来解决这些问题。
方向键事件由无障碍服务使用
此问题的根本原因是关键事件被无障碍服务消耗。
如图 1 所示,当 Talkback 开启时,方向键 事件不会传递给开发者定义的 方向键 处理程序。相反,无障碍服务会接收按键事件,以便移动无障碍焦点。由于自定义 Android 组件默认情况下不会向无障碍服务公开有关其在屏幕上的位置的信息,因此无障碍服务无法移动无障碍焦点来突出显示它们。
其他无障碍服务也会受到类似影响:使用开关控制时,方向键事件也可能会被消耗。
由于方向键事件会提交给无障碍服务,而该服务不知道自定义视图中界面组件的位置,因此您必须实现 AccessibilityNodeInfo,以便应用正确转发按键事件。
向无障碍服务公开信息
为了向无障碍服务提供有关自定义视图的位置和说明的足够信息,请实现 AccessibilityNodeInfo 以公开每个组件的详细信息。如需定义视图的逻辑关系,以便无障碍服务可以管理焦点,请实现 ExploreByTouchHelper 并使用 ViewCompat.setAccessibilityDelegate(View, AccessibilityDelegateCompat) 为自定义视图设置该属性。
实现 ExploreByTouchHelper 时,请替换其四个抽象方法:
Kotlin
// Return the virtual view ID whose view is covered by the input point (x, y).
protected fun getVirtualViewAt(x: Float, y: Float): Int
// Fill the virtual view ID list into the input parameter virtualViewIds.
protected fun getVisibleVirtualViews(virtualViewIds: List<Int>)
// For the view whose virtualViewId is the input virtualViewId, populate the
// accessibility node information into the AccessibilityNodeInfoCompat parameter.
protected fun onPopulateNodeForVirtualView(virtualViewId: Int, @NonNull node: AccessibilityNodeInfoCompat)
// Set the accessibility handling when perform action.
protected fun onPerformActionForVirtualView(virtualViewId: Int, action: Int, @Nullable arguments: Bundle): Boolean
Java
// Return the virtual view ID whose view is covered by the input point (x, y).
protected int getVirtualViewAt(float x, float y)
// Fill the virtual view ID list into the input parameter virtualViewIds.
protected void getVisibleVirtualViews(List<Integer> virtualViewIds)
// For the view whose virtualViewId is the input virtualViewId, populate the
// accessibility node information into the AccessibilityNodeInfoCompat parameter.
protected void onPopulateNodeForVirtualView(int virtualViewId, @NonNull AccessibilityNodeInfoCompat node)
// Set the accessibility handling when perform action.
protected boolean onPerformActionForVirtualView(int virtualViewId, int action, @Nullable Bundle arguments)
如需了解详情,请观看 Google I/O 2013 - 在 Android 上为盲人和低视力用户启用无障碍功能,或详细了解如何填充无障碍功能事件。
最佳做法
必需:
AccessibilityNodeInfo.getBoundsInScreen()必须定义组件的位置。必需:
AccessibilityNodeInfo.setVisibleToUser()必须反映组件的可见性。必需:
AccessibilityNodeInfo.getContentDescription()必须指定 Talkback 要朗读的内容说明。指定
AccessibilityNodeInfo.setClassName(),以便服务区分组件类型。实现
performAction()时,请使用相应的AccessibilityEvent反映该操作。如需实现更多操作类型(例如
ACTION_CLICK),请使用performAction()中的相应逻辑调用AccessibilityNodeInfo.addAction(ACTION_CLICK)。在适用的情况下,反映
setFocusable()、setClickable()、setScrollable()和类似方法的组件状态。查看
AccessibilityNodeInfo的文档,了解无障碍服务可以更好地与您的组件互动的方式。
示例
请参阅 Android TV 的自定义视图无障碍功能示例,了解如何以最佳实践方式为使用自定义视图的应用添加无障碍功能支持。