默认情况下,Compose 应用中会实现无障碍屏幕阅读器行为
按照预期的阅读顺序排列,通常是从左到右,然后从上到下。
不过,对于某些类型的应用布局,算法无法确定
实际阅读顺序,没有额外提示。在基于视图的应用中,您可以
请使用 traversalBefore
和 traversalAfter
属性修复此类问题。
从 Compose 1.5 开始,Compose 提供了同样灵活的 API,但
一种新的概念模式。
isTraversalGroup
和 traversalIndex
是
可让您控制无障碍功能和 TalkBack 焦点顺序,
默认排序算法并不合适。isTraversalGroup
标识
语义重要性分组,而 traversalIndex
会调整
这些组中的各个元素您可以单独使用 isTraversalGroup
,
或使用 traversalIndex
进一步自定义。
在您的isTraversalGroup
traversalIndex
应用来控制屏幕阅读器遍历顺序。
使用 isTraversalGroup
将元素分组
isTraversalGroup
是一个布尔值属性,用于定义语义
代表一个遍历组。这种节点的功能是
用作边界或边界来组织节点的子节点。
在某个节点上设置 isTraversalGroup = true
意味着该节点的所有子节点
在移动到其他元素之前会遭到访问。您可以将isTraversalGroup
设为
非屏幕阅读器可聚焦节点,例如“列”“行”或“框”。
以下示例使用 isTraversalGroup
。它会发出四个文本元素。通过
左侧两个元素属于一个 CardBox
元素,而右侧两个元素属于
属于另一个 CardBox
元素:
// CardBox() function takes in top and bottom sample text. @Composable fun CardBox( topSampleText: String, bottomSampleText: String, modifier: Modifier = Modifier ) { Box(modifier) { Column { Text(topSampleText) Text(bottomSampleText) } } } @Composable fun TraversalGroupDemo() { val topSampleText1 = "This sentence is in " val bottomSampleText1 = "the left column." val topSampleText2 = "This sentence is " val bottomSampleText2 = "on the right." Row { CardBox( topSampleText1, bottomSampleText1 ) CardBox( topSampleText2, bottomSampleText2 ) } }
代码生成类似于以下内容的输出:
由于未设置语义,因此屏幕阅读器的默认行为是 从左到右、从上到下遍历元素。因此 默认情况下,TalkBack 以错误的顺序读出句子片段:
“这句话是”→“这句话是”→“左列”。→“在 。”
要正确地对 Fragment 进行排序,请修改原始代码段,将其设为
isTraversalGroup
至 true
:
@Composable fun TraversalGroupDemo2() { val topSampleText1 = "This sentence is in " val bottomSampleText1 = "the left column." val topSampleText2 = "This sentence is" val bottomSampleText2 = "on the right." Row { CardBox( // 1, topSampleText1, bottomSampleText1, Modifier.semantics { isTraversalGroup = true } ) CardBox( // 2, topSampleText2, bottomSampleText2, Modifier.semantics { isTraversalGroup = true } ) } }
由于针对每个 CardBox
专门设置了 isTraversalGroup
,因此 CardBox
对元素进行排序时会应用边界。在本示例中,左侧
先读取 CardBox
,然后读取正确的 CardBox
。
现在,TalkBack 按正确的顺序读出句子片段:
“这句话是”→“左列”。→“这句话是”→“在 。”
进一步自定义遍历顺序
traversalIndex
是一个浮动属性,可让您自定义 TalkBack
遍历顺序。如果将元素组合在一起还不足以让 TalkBack 无法
正常运行,请将 traversalIndex
与以下函数结合使用
isTraversalGroup
,用于进一步自定义屏幕阅读器排序方式。
traversalIndex
属性具有以下特征:
traversalIndex
值较低的元素优先。- 可以是正面或负面。
- 默认值为
0f
。 - 仅影响屏幕阅读器可聚焦节点,例如屏幕上的元素,例如
文字或按钮例如,只在列上设置
traversalIndex
会 将不会产生任何影响,除非相应列也设置了isTraversalGroup
。
以下示例展示了如何使用 traversalIndex
和
一起isTraversalGroup
。
例如:遍历钟面
钟面是一种常见的情形,其中标准遍历排序不支持 工作。本部分中的示例是时间选择器,用户可以在该选择器中 穿过钟面上的数字,然后选择小时和分钟的数字 。
以下简化代码段中有一个 CircularLayout
,其中 12
从 12 开始,按顺时针方向绕圆圈移动:
@Composable fun ClockFaceDemo() { CircularLayout { repeat(12) { hour -> ClockText(hour) } } } @Composable private fun ClockText(value: Int) { Box(modifier = Modifier) { Text((if (value == 0) 12 else value).toString()) } }
由于钟面没有默认从左到右进行逻辑读取, TalkBack 会不按顺序读出数字。纠正 为此,请使用递增计数器值,如以下代码段所示:
@Composable fun ClockFaceDemo() { CircularLayout(Modifier.semantics { isTraversalGroup = true }) { repeat(12) { hour -> ClockText(hour) } } } @Composable private fun ClockText(value: Int) { Box(modifier = Modifier.semantics { this.traversalIndex = value.toFloat() }) { Text((if (value == 0) 12 else value).toString()) } }
如需正确设置遍历顺序,请先将 CircularLayout
设为
然后设置 isTraversalGroup = true
。然后,由于每个时钟文本
绘制到布局上,请将其对应的 traversalIndex
设置为计数器
值。
由于计数器值不断增加,因此每个时钟值的
traversalIndex
越大,因为屏幕上会增加数字,即时钟值 0
traversalIndex
为 0,时钟值为 1,traversalIndex
为 1。
这样,TalkBack 读出这些文字的顺序即可。这些数字
按预期顺序读取 CircularLayout
内的数据。
由于已设置的 traversalIndexes
仅相对于其他
索引,系统会按顺序排列其余屏幕顺序
保留。换句话说,上述代码中显示的语义变化
代码段仅修改具有
已设置 isTraversalGroup = true
。
请注意,如果不将 CircularLayout's
语义设置为 isTraversalGroup =
true
,traversalIndex
更改仍然适用。不过,如果没有
CircularLayout
用于绑定它们,系统会读取表盘的 12 位数字
最后,在访问完屏幕上的所有其他元素之后。发生这种情况
因为所有其他元素的默认 traversalIndex
均为 0f
,并且
时钟文本元素在所有其他 0f
元素之后读取。
示例:自定义悬浮操作按钮的遍历顺序
在此示例中,traversalIndex
和 isTraversalGroup
控制着
Material Design 悬浮操作按钮 (FAB) 的遍历顺序。基础
该示例的布局如下所示:
默认情况下,此示例中的布局具有以下 TalkBack 顺序:
顶部应用栏 → 示例文本 0 至 6 → 悬浮操作按钮 (FAB) → 底部 应用栏
您可能希望屏幕阅读器先将焦点放在 FAB 上。要设置
traversalIndex
时,请执行以下操作:
@Composable fun FloatingBox() { Box(modifier = Modifier.semantics { isTraversalGroup = true; traversalIndex = -1f }) { FloatingActionButton( { Icon(imageVector = Icons.Default.Add, contentDescription = "fab icon") } } }
在此代码段中,创建一个
将 isTraversalGroup
设置为 true
并在同一框中设置 traversalIndex
(-1f
小于默认值 0f
)表示浮动框
置于所有其他屏幕上的元素之前
接下来,您可以将浮动框和其他元素放入基架中, 实现 Material Design 布局:
@OptIn(ExperimentalMaterial3Api::class) @Composable fun ColumnWithFABFirstDemo() { Scaffold( topBar = { TopAppBar(title = { Text("Top App Bar") }) }, floatingActionButtonPosition = FabPosition.End, floatingActionButton = { FloatingBox() }, content = { padding -> ContentColumn(padding = padding) }, bottomBar = { BottomAppBar { Text("Bottom App Bar") } } ) }
TalkBack 按以下顺序与这些元素交互:
FAB → 顶部应用栏 → 示例文本 0 到 6 → 底部应用栏
其他资源
- 无障碍功能:基本概念和 所有 Android 应用开发通用的技术
- 构建无障碍应用:关键步骤 使您的应用使用起来更没有障碍
- 改进应用程序的原则 无障碍功能:确保实现无障碍功能的主要原则 提高应用的无障碍性
- 测试无障碍功能: Android 无障碍功能的测试原则和工具