Android 辅助功能:Talkback 以错误的顺序遍历元素
Android accessibility: Talkback traverses elements in wrong order
我有一个带有工具栏的布局和一个将承载其他控件的视图:
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<ViewStub
android:id="@+id/root_view_stub"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</FrameLayout>
使用了FrameLayout,使得有正"elevation"的工具栏可以半透明,视图延伸到工具栏下方。工具栏和视图的顶部位置相同但高度不同。当 Talkback 构建视图层次结构时,它会将工具栏放在底部,即使它是最先定义的。 "accessibilityTraversalBefore" 和 "accessibilityTraversalAfter" 对视图没有影响。到目前为止,我找到的唯一解决方案是向 ViewStub 添加 1px 或 0.1px 的上边距。这可能与此处的代码有关:
代码尝试根据节点的屏幕位置重新排序节点。添加顶部边距有点麻烦,可能有一天会坏掉。有更好的解决方案吗?
我在调试我的应用程序和 TalkBack 应用程序后弄清楚了发生了什么。可访问性遍历顺序不受布局中定义的视图顺序控制。在 activity_react_native.xml 中将 accessibilityTraversalBefore 或 accessibilityTraversalAfter 添加到工具栏或 ViewStub 没有任何效果。我找不到任何关于遍历顺序的文档,所以我检查了代码。以下是 TalkBack 的工作原理:
开启 TalkBack 后,所有单指手势都会发送到 TalkBack 而不是应用程序。这些手势可以在 TalkBack 设置中重新定义。上下滑动手势后,TalkBack 首先枚举当前可见页面上的所有可聚焦视图。这是通过向应用程序发送消息来完成的。
在应用程序进程中,可访问性视图层次结构是通过递归遍历所有子视图来构建的。它是在 ViewGroup 代码中完成的:https://github.com/aosp-mirror/platform_frameworks_base/blob/d18ed49f9dba09b85782c83999a9103dec015bf2/core/java/android/view/ViewGroup.java#L2314. The last argument of this call is a hardcoded true which sorts child views based on their positions on the screen. The comparison logic is here: https://github.com/aosp-mirror/platform_frameworks_base/blob/d18ed49f9dba09b85782c83999a9103dec015bf2/core/java/android/view/ViewGroup.java#L8397。当两个视图具有相同的顶部、左侧和宽度时,高度较大的视图位于高度或面积较小的视图之前,这在大多数情况下似乎是一个糟糕的选择。
构建树后,它被发送回 TalkBack,后者随后根据 traversalBefore 或 traversalAfter 属性对元素重新排序。由于 Toolbar 和 ViewStub 不是可聚焦元素,因此它们不会出现在该树中,这就是这两个属性不起作用的原因。
修复方法是将 android:importantForAccessibility 添加到工具栏和 ViewStub。这会将它们从 ViewGroup 添加到辅助功能树中,以便 TalkBack 可以对 accessibilityTraversalBefore/After 属性进行操作。
还有一个解决方法,将工具栏向上移动 1px,然后在内部添加 1px 的填充。额外的 1px 应该被 FrameLayout 剪掉,所以没有视觉变化:
android:layout_marginTop="-1px"
android:paddingTop="1px"
我有一个带有工具栏的布局和一个将承载其他控件的视图:
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<ViewStub
android:id="@+id/root_view_stub"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</FrameLayout>
使用了FrameLayout,使得有正"elevation"的工具栏可以半透明,视图延伸到工具栏下方。工具栏和视图的顶部位置相同但高度不同。当 Talkback 构建视图层次结构时,它会将工具栏放在底部,即使它是最先定义的。 "accessibilityTraversalBefore" 和 "accessibilityTraversalAfter" 对视图没有影响。到目前为止,我找到的唯一解决方案是向 ViewStub 添加 1px 或 0.1px 的上边距。这可能与此处的代码有关:
代码尝试根据节点的屏幕位置重新排序节点。添加顶部边距有点麻烦,可能有一天会坏掉。有更好的解决方案吗?
我在调试我的应用程序和 TalkBack 应用程序后弄清楚了发生了什么。可访问性遍历顺序不受布局中定义的视图顺序控制。在 activity_react_native.xml 中将 accessibilityTraversalBefore 或 accessibilityTraversalAfter 添加到工具栏或 ViewStub 没有任何效果。我找不到任何关于遍历顺序的文档,所以我检查了代码。以下是 TalkBack 的工作原理:
开启 TalkBack 后,所有单指手势都会发送到 TalkBack 而不是应用程序。这些手势可以在 TalkBack 设置中重新定义。上下滑动手势后,TalkBack 首先枚举当前可见页面上的所有可聚焦视图。这是通过向应用程序发送消息来完成的。 在应用程序进程中,可访问性视图层次结构是通过递归遍历所有子视图来构建的。它是在 ViewGroup 代码中完成的:https://github.com/aosp-mirror/platform_frameworks_base/blob/d18ed49f9dba09b85782c83999a9103dec015bf2/core/java/android/view/ViewGroup.java#L2314. The last argument of this call is a hardcoded true which sorts child views based on their positions on the screen. The comparison logic is here: https://github.com/aosp-mirror/platform_frameworks_base/blob/d18ed49f9dba09b85782c83999a9103dec015bf2/core/java/android/view/ViewGroup.java#L8397。当两个视图具有相同的顶部、左侧和宽度时,高度较大的视图位于高度或面积较小的视图之前,这在大多数情况下似乎是一个糟糕的选择。 构建树后,它被发送回 TalkBack,后者随后根据 traversalBefore 或 traversalAfter 属性对元素重新排序。由于 Toolbar 和 ViewStub 不是可聚焦元素,因此它们不会出现在该树中,这就是这两个属性不起作用的原因。
修复方法是将 android:importantForAccessibility 添加到工具栏和 ViewStub。这会将它们从 ViewGroup 添加到辅助功能树中,以便 TalkBack 可以对 accessibilityTraversalBefore/After 属性进行操作。
还有一个解决方法,将工具栏向上移动 1px,然后在内部添加 1px 的填充。额外的 1px 应该被 FrameLayout 剪掉,所以没有视觉变化:
android:layout_marginTop="-1px"
android:paddingTop="1px"