更改可聚焦 ViewGroup 中元素的阅读顺序
Change reading order of elements within a focusable ViewGroup
我的应用程序中有一些屏幕,其中 TalkBack 无法推断出正确的阅读顺序。根据文档,我可以使用 android:accessibilityTraversalAfter
和朋友来改变阅读顺序。但它不适用于我应该一起阅读的可聚焦 ViewGroup
中的元素。
整个布局如下所示:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:accessibilityTraversalBefore="@id/before"
android:focusable="true"
tools:context=".MainActivity">
<TextView
android:id="@+id/before"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:accessibilityTraversalAfter="@id/before"
android:text="Before"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent" />
<TextView
android:id="@+id/after"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:accessibilityTraversalBefore="@id/after"
android:text="After"
app:layout_constraintBottom_toTopOf="@+id/before"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
它在屏幕中间呈现 After
,在底部呈现 Before
。我希望 TalkBack 将整个屏幕视为一个连续的元素,因此我将 android:focusable
设置为 true
。默认情况下,TalkBack 显示:"After, before"。但我希望它显示为 "Before, after"。尽管我添加了 android:accessibilityTraversalBefore
和 android:accessibilityTraversalAfter
,它仍然显示为 "After, before"。这是节点树调试的输出:
TreeDebug: (-2147455381)429.FrameLayout:(0, 0 - 1080, 1920):A
TreeDebug: (30189)429.TextView:(42, 101 - 397, 172):TEXT{My Application}:A:supportsTextLocation
TreeDebug: (31150)429.ViewGroup:(0, 210 - 1080, 1794):Fa:focusable:accessibilityFocused
TreeDebug: (33072)429.TextView:(499, 951 - 581, 1002):TEXT{After}:A:supportsTextLocation
TreeDebug: (32111)429.TextView:(485, 1743 - 595, 1794):TEXT{Before}:A:supportsTextLocation
我做错了什么?
为了完整起见:minSdkVersion
是 26,targetSdkVersion
是 29。
我深入研究了这个问题并发现了以下内容:accessibilityTraversalBefore
和 accessibilityTraversalAfter
标志确实生效,但仅针对它们的预期用途 - 它们适用于辅助功能服务应用(例如对讲)。换句话说,如果您从根布局中删除 focusable
属性,您将看到导航是正确的。
但是这些标志不会影响 AccessibilityNode
是如何为根 ViewGroup
构造的。正如在 sources of ViewGroup#onInitializeAccessibilityNodeInfoInternal()
中所见,实际的文本构建逻辑 不会 考虑儿童如何使用上面提到的标志构建他们的导航。
为了解决这个问题,我从布局 xml 中删除了过多的标志,如下所示:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/root"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:focusable="true"
tools:context=".MainActivity">
<TextView
android:id="@+id/before"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:importantForAccessibility="no"
android:text="Before"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/after"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:importantForAccessibility="no"
android:text="After"
app:layout_constraintBottom_toTopOf="@+id/before"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
在主机内部 activity/fragment:
val root = findViewById<ViewGroup>(R.id.root)
ViewCompat.setAccessibilityDelegate(root, object : AccessibilityDelegateCompat() {
override fun onInitializeAccessibilityNodeInfo(
host: View?,
info: AccessibilityNodeInfoCompat?
) {
val stringBuilder = StringBuilder()
root.children.forEach { view ->
val label = if (view is TextView) view.text else ""
stringBuilder.append("$label, ")
}
info?.text = stringBuilder.toString()
super.onInitializeAccessibilityNodeInfo(host, info)
}
})
这将产生预期的结果:对讲将发音为 "Before, After"。
不幸的是,这是一个容易出错的代码,这意味着如果您以某种方式重新构建视图层次结构,则子级的顺序会被调换,然后此节点文本构造逻辑将被破坏。尽管如此,我无法想出更好的解决方案,也看不出有可能指示父级考虑子级排序标志(基于来源)。
我的应用程序中有一些屏幕,其中 TalkBack 无法推断出正确的阅读顺序。根据文档,我可以使用 android:accessibilityTraversalAfter
和朋友来改变阅读顺序。但它不适用于我应该一起阅读的可聚焦 ViewGroup
中的元素。
整个布局如下所示:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:accessibilityTraversalBefore="@id/before"
android:focusable="true"
tools:context=".MainActivity">
<TextView
android:id="@+id/before"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:accessibilityTraversalAfter="@id/before"
android:text="Before"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent" />
<TextView
android:id="@+id/after"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:accessibilityTraversalBefore="@id/after"
android:text="After"
app:layout_constraintBottom_toTopOf="@+id/before"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
它在屏幕中间呈现 After
,在底部呈现 Before
。我希望 TalkBack 将整个屏幕视为一个连续的元素,因此我将 android:focusable
设置为 true
。默认情况下,TalkBack 显示:"After, before"。但我希望它显示为 "Before, after"。尽管我添加了 android:accessibilityTraversalBefore
和 android:accessibilityTraversalAfter
,它仍然显示为 "After, before"。这是节点树调试的输出:
TreeDebug: (-2147455381)429.FrameLayout:(0, 0 - 1080, 1920):A
TreeDebug: (30189)429.TextView:(42, 101 - 397, 172):TEXT{My Application}:A:supportsTextLocation
TreeDebug: (31150)429.ViewGroup:(0, 210 - 1080, 1794):Fa:focusable:accessibilityFocused
TreeDebug: (33072)429.TextView:(499, 951 - 581, 1002):TEXT{After}:A:supportsTextLocation
TreeDebug: (32111)429.TextView:(485, 1743 - 595, 1794):TEXT{Before}:A:supportsTextLocation
我做错了什么?
为了完整起见:minSdkVersion
是 26,targetSdkVersion
是 29。
我深入研究了这个问题并发现了以下内容:accessibilityTraversalBefore
和 accessibilityTraversalAfter
标志确实生效,但仅针对它们的预期用途 - 它们适用于辅助功能服务应用(例如对讲)。换句话说,如果您从根布局中删除 focusable
属性,您将看到导航是正确的。
但是这些标志不会影响 AccessibilityNode
是如何为根 ViewGroup
构造的。正如在 sources of ViewGroup#onInitializeAccessibilityNodeInfoInternal()
中所见,实际的文本构建逻辑 不会 考虑儿童如何使用上面提到的标志构建他们的导航。
为了解决这个问题,我从布局 xml 中删除了过多的标志,如下所示:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/root"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:focusable="true"
tools:context=".MainActivity">
<TextView
android:id="@+id/before"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:importantForAccessibility="no"
android:text="Before"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/after"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:importantForAccessibility="no"
android:text="After"
app:layout_constraintBottom_toTopOf="@+id/before"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
在主机内部 activity/fragment:
val root = findViewById<ViewGroup>(R.id.root)
ViewCompat.setAccessibilityDelegate(root, object : AccessibilityDelegateCompat() {
override fun onInitializeAccessibilityNodeInfo(
host: View?,
info: AccessibilityNodeInfoCompat?
) {
val stringBuilder = StringBuilder()
root.children.forEach { view ->
val label = if (view is TextView) view.text else ""
stringBuilder.append("$label, ")
}
info?.text = stringBuilder.toString()
super.onInitializeAccessibilityNodeInfo(host, info)
}
})
这将产生预期的结果:对讲将发音为 "Before, After"。
不幸的是,这是一个容易出错的代码,这意味着如果您以某种方式重新构建视图层次结构,则子级的顺序会被调换,然后此节点文本构造逻辑将被破坏。尽管如此,我无法想出更好的解决方案,也看不出有可能指示父级考虑子级排序标志(基于来源)。