一个 组合 描述了您应用程序的 UI,并且是通过运行可组合项生成的。组合是一个树形结构,由描述您的 UI 的可组合项组成。
除了组合之外,还有一个并行的树,称为 *语义树*。这棵树以一种可供 无障碍 服务和 测试 框架理解的替代方式来描述您的 UI。无障碍服务使用该树向有特殊需求的用户描述应用程序。测试框架使用该树与您的应用程序交互并对其进行断言。语义树不包含有关如何 *绘制* 可组合项的信息,但它包含有关可组合项的 **语义 *含义*** 的信息。
如果您的应用程序由 Compose 基础库和 Material 库中的可组合项和修饰符组成,则语义树会自动为您填充和生成。但是,**当您添加自定义的低级可组合项时,您必须手动提供其语义**。也可能存在您的树无法正确或完整地表示屏幕上元素的含义的情况,在这种情况下,您可以调整该树。
例如,考虑这个自定义日历可组合项
在这个示例中,整个日历是作为一个单个低级可组合项实现的,使用 Layout
可组合项并直接绘制到 Canvas
。如果您不做任何其他操作,无障碍服务将无法获得有关可组合项内容和用户在日历中选择的足够信息。例如,如果用户点击包含 17 的日期,无障碍框架只会收到有关整个日历控件的描述信息。在这种情况下,TalkBack 无障碍服务会宣布“日历”或,稍微好一点,“四月日历”,用户就会感到困惑,不知道选择了哪一天。为了使这个可组合项更易于访问,您需要手动添加语义信息。
语义属性
UI 树中所有具有语义意义的节点在语义树中都有一个并行节点。语义树中的节点包含那些传达相应可组合项含义的属性。例如,Text
可组合项包含一个语义属性 text
,因为这是该可组合项的 *含义*。一个 Icon
包含一个 contentDescription
属性(如果由开发人员设置),它以文本形式传达 Icon
的含义。构建在 Compose 基础库 之上的可组合项和修饰符会为您设置相关的属性。您可以选择使用 semantics
和 clearAndSetSemantics
修饰符来自己设置或覆盖这些属性。例如,向节点添加 自定义无障碍操作,为可切换元素提供替代的 状态描述,或指示某个文本可组合项应被视为 标题。
要可视化语义树,请使用 布局检查器 工具或使用 printToLog()
方法在测试中。这会将当前语义树打印到 Logcat 中。
class MyComposeTest { @get:Rule val composeTestRule = createComposeRule() @Test fun MyTest() { // Start the app composeTestRule.setContent { MyTheme { Text("Hello world!") } } // Log the full semantics tree composeTestRule.onRoot().printToLog("MY TAG") } }
此测试的输出将是
Printing with useUnmergedTree = 'false'
Node #1 at (l=0.0, t=63.0, r=221.0, b=120.0)px
|-Node #2 at (l=0.0, t=63.0, r=221.0, b=120.0)px
Text = '[Hello world!]'
Actions = [GetTextLayoutResult]
考虑语义属性如何传达可组合项的含义。考虑一个 Switch
。这是它在用户看来是什么样子
要描述此元素的 *含义*,您可以说以下内容:*“这是一个 Switch,它是一个可切换的元素,处于‘打开’状态。您可以点击它与之交互。”*。
这正是语义属性的用途。此 Switch 元素的语义节点包含以下属性,如布局检查器所示
The Role
表示元素类型。The StateDescription
描述了如何引用“打开”状态。默认情况下,这是一个“打开”一词的本地化版本,但可以根据上下文使其更具体(例如,“已启用”)。The ToggleableState
是 Switch 的当前状态。The OnClick
属性引用了用于与该元素交互的方法。有关语义属性的完整列表,请查看 SemanticsProperties
对象。有关可能的无障碍操作的完整列表,请查看 SemanticsActions
对象。
跟踪应用程序中每个可组合项的语义属性会解锁许多强大的可能性。一些例子
- Talkback 使用这些属性朗读屏幕上显示的内容,并允许用户平滑地与之交互。对于 Switch 可组合项,Talkback 可能会说:“打开;Switch;双击切换”。用户可以双击屏幕将 Switch 切换为关闭状态。
- 测试框架使用这些属性来查找节点、与之交互并进行断言。Switch 的示例测试可能是
val mySwitch = SemanticsMatcher.expectValue( SemanticsProperties.Role, Role.Switch ) composeTestRule.onNode(mySwitch) .performClick() .assertIsOff()
合并和未合并的语义树
如前所述,UI 树中的每个可组合项可能设置了零个或多个语义属性。当可组合项未设置任何语义属性时,它不会包含在语义树中。这样,语义树只包含实际包含语义意义的节点。但是,有时为了传达屏幕上显示内容的正确含义,将某些节点子树合并并将其视为一个整体也很有用。这样,您可以将一组节点作为一个整体来理解,而不是分别处理每个子节点。作为经验法则,这棵树中的每个节点都表示在使用无障碍服务时的可聚焦元素。
这种可组合项的一个例子是 Button
。您可以将按钮视为一个单一元素,即使它可能包含多个子节点
Button(onClick = { /*TODO*/ }) { Icon( imageVector = Icons.Filled.Favorite, contentDescription = null ) Spacer(Modifier.size(ButtonDefaults.IconSpacing)) Text("Like") }
在语义树中,按钮的子节点的属性被合并,按钮在树中被呈现为一个单一的叶节点
可组合项和修饰符可以通过调用 Modifier.semantics (mergeDescendants = true) {}
来指示它们希望合并其子节点的语义属性。将此属性设置为 true
表示应该合并语义属性。在 Button
示例中,Button
可组合项在内部使用 clickable
修饰符,该修饰符包含此 semantics
修饰符。因此,按钮的子节点被合并。阅读无障碍文档,了解您应该在何时 更改可组合项中的合并行为。
Foundation 和 Material Compose 库中的多个修饰符和可组合项都设置了此属性。例如,clickable
和 toggleable
修饰符会自动合并其子节点。此外,ListItem
可组合项将合并其子节点。
检查树木
语义树实际上是两棵不同的树。有一棵 *合并的* 语义树,它在 mergeDescendants
设置为 true
时合并子节点。还有一棵 *未合并的* 语义树,它不应用合并,而是保持每个节点完整。无障碍服务使用未合并的树并应用自己的合并算法,同时考虑 mergeDescendants
属性。测试框架默认使用合并的树。
您可以使用 printToLog()
方法检查这两棵树。默认情况下,以及在前面的示例中,会记录合并的树。要改为打印未合并的树,请将 onRoot()
匹配器的 useUnmergedTree
参数设置为 true
composeTestRule.onRoot(useUnmergedTree = true).printToLog("MY TAG")
布局检查器允许您显示合并的语义树和未合并的语义树,方法是在视图过滤器中选择首选的树
对于树中的每个节点,布局检查器都会在属性面板中显示合并的语义和在该节点上设置的语义
默认情况下,测试框架中的匹配器使用合并的语义树。这就是为什么您可以通过匹配显示在其中的文本来与 Button
交互的原因
composeTestRule.onNodeWithText("Like").performClick()
通过将匹配器的 useUnmergedTree
参数设置为 true
来覆盖此行为,就像 onRoot
匹配器一样。
合并行为
当一个可组合项指示其子节点应该被合并时,这个合并究竟是如何发生的?
每个语义属性都有一个定义的合并策略。例如,ContentDescription
属性将所有子级 ContentDescription 值添加到一个列表中。通过检查 SemanticsProperties.kt
中的 mergePolicy
实现来检查语义属性的合并策略。属性可以采用父级或子级值、将值合并到列表或字符串中、完全不允许合并并改为抛出异常,或任何其他自定义合并策略。
需要注意的是,本身设置了 mergeDescendants = true
的子节点不会包含在合并中。看一个例子
这是一个可点击的列表项。当用户按下该行时,应用程序会导航到文章详情页面,用户可以在该页面上阅读文章。在列表项中,有一个按钮可以为文章添加书签,它形成一个嵌套的可点击元素,因此按钮在合并的树中单独显示。该行中的其余内容被合并了
调整语义树
如前所述,您可以覆盖或清除某些语义属性,或更改树的合并行为。这在您创建自己的自定义组件时尤其重要。如果没有设置正确的属性和合并行为,您的应用程序可能无法访问,并且测试的行为可能与您预期不同。要了解有关您应该调整语义树的一些常见用例的更多信息,请阅读 无障碍文档。如果您想了解有关测试的更多信息,请查看 测试指南。
其他资源
- 无障碍:所有 Android 应用程序开发通用的基本概念和技术
- 构建无障碍应用: 让您的应用更易于访问的关键步骤
- 改善应用无障碍性的原则: 在努力使您的应用更易于访问时需要牢记的关键原则
- 无障碍性测试: Android 无障碍性测试原则和工具
为您推荐
- 注意:当 JavaScript 关闭时,将显示链接文本
- Compose 中的无障碍性
- Compose 中的 Material Design 2
- 测试您的 Compose 布局