当无障碍服务在屏幕上的元素之间导航时,请务必以适当的粒度对这些元素进行分组、分隔或隐藏。如果屏幕中的每个低级可组合项都单独突出显示,用户就必须进行大量互动才能在屏幕上移动。如果元素合并得过于激进,用户可能无法理解哪些元素在逻辑上属于同一组。如果屏幕上有纯装饰性元素,则可以对无障碍服务隐藏这些元素。在这些情况下,您可以使用 Compose API 来合并、清除和隐藏语义。
合并语义
当您将 clickable 修饰符应用于父级可组合项时,Compose 会自动合并其下的所有子元素。如需了解交互式 Compose Material 和 Foundation 组件默认如何使用合并策略,请参阅互动元素部分。
一个组件通常由多个可组合项组成。这些可组合项可能构成一个逻辑分组,并且每个可组合项都可能包含重要信息,但您可能仍希望无障碍服务将它们视为一个元素。
例如,假设有一个可组合项,它显示用户的头像、姓名和一些额外信息:
您可以在语义修饰符中使用 mergeDescendants 参数,让 Compose 合并这些元素。这样,无障碍服务会将该组件视为一个实体,并且后代的所有语义属性都会合并:
@Composable private fun PostMetadata(metadata: Metadata) { // Merge elements below for accessibility purposes Row(modifier = Modifier.semantics(mergeDescendants = true) {}) { Image( imageVector = Icons.Filled.AccountCircle, contentDescription = null // decorative ) Column { Text(metadata.author.name) Text("${metadata.date} • ${metadata.readTimeMinutes} min read") } } }
无障碍服务现在会一次性聚焦于整个容器,并合并其内容:
每个语义属性都有定义的合并策略。例如,ContentDescription 属性会将所有 ContentDescription 后代值添加到列表中。您可以通过检查语义属性在 SemanticsProperties.kt 中的 mergePolicy 实现来检查语义属性的合并策略。
属性可以采用父项或子项值,将值合并到列表或字符串中,完全不允许合并并抛出异常,也可以选择任何其他自定义合并策略。
在其他一些情况下,您希望将子语义合并到父语义中,但实际并未发生。在以下示例中,我们有一个包含子元素的 clickable 列表项父级,我们可能希望父级合并所有这些子元素:
@Composable private fun ArticleListItem( openArticle: () -> Unit, addToBookmarks: () -> Unit, ) { Row(modifier = Modifier.clickable { openArticle() }) { // Merges with parent clickable: Icon( painter = painterResource(R.drawable.ic_logo), contentDescription = "Article thumbnail" ) ArticleDetails() // Defies the merge due to its own clickable: BookmarkButton(onClick = addToBookmarks) } }
当用户按下 clickable 项 Row 时,系统会打开文章。其中嵌套了一个 BookmarkButton,用于为文章添加书签。此嵌套按钮显示为未合并,而行内的其余子内容已合并:
Row 节点内的列表中包含许多文字。未合并的树包含每个 Text 可组合项的单独节点。根据设计,某些可组合项不会自动合并到父级下。如果子级也在合并(通过明确设置 mergeDescendants = true 或作为自行合并的组件,例如按钮或可点击项),则父级无法合并其子级。了解某些 API 如何合并或拒绝合并,有助于您调试一些可能出现的意外行为。
当子元素在其父元素下构成一个合乎逻辑且合理的组时,请使用合并。但如果嵌套子级需要手动调整或移除其自己的语义,其他 API 可能更适合您的需求(例如 clearAndSetSemantics)。
清除并设置语义
如果需要完全清除或覆盖语义信息,可以使用强大的 API clearAndSetSemantics。
当组件需要清除自身及其后代的语义时,请将此 API 与空 lambda 搭配使用。如果必须覆盖其语义,请在 lambda 中添加新内容。
请注意,使用空 lambda 清除时,清除的语义不会发送到任何使用此信息的消费者,例如无障碍功能、自动填充或测试。使用 clearAndSetSemantics{/*semantic information*/} 覆盖内容时,新语义会替换元素及其后代的所有先前语义。
以下是自定义切换开关组件的示例,由包含图标和文本的可交互行表示:
// Developer might intend this to be a toggleable. // Using `clearAndSetSemantics`, on the Row, a clickable modifier is applied, // a custom description is set, and a Role is applied. @Composable fun FavoriteToggle() { val checked = remember { mutableStateOf(true) } Row( modifier = Modifier .toggleable( value = checked.value, onValueChange = { checked.value = it } ) .clearAndSetSemantics { stateDescription = if (checked.value) "Favorited" else "Not favorited" toggleableState = ToggleableState(checked.value) role = Role.Switch }, ) { Icon( imageVector = Icons.Default.Favorite, contentDescription = null // not needed here ) Text("Favorite?") } }
虽然图标和文本具有一些语义信息,但它们并不能表明此组件是可切换的。仅合并是不够的,因为您必须提供有关该组件的其他信息。
由于上面的代码段创建了一个自定义切换开关组件,因此您需要添加切换开关功能以及 stateDescription、toggleableState 和 role 语义。这样,组件状态和关联的操作便可用,例如,TalkBack 会播报“点按两次即可切换”,而不是“点按两次即可激活”。
通过清除原始语义并设置更具描述性的新语义,无障碍服务现在可以看到这是一个可切换状态的可切换组件。
使用 clearAndSetSemantics 时,请考虑以下事项:
- 由于服务在设置此 API 时不会收到任何信息,因此最好谨慎使用。
- AI 智能体和类似服务可能会使用语义信息来理解屏幕内容,因此只有在必要时才应清除这些信息。
- 自定义语义可以在 API lambda 中设置。
- 修饰符的顺序很重要,此 API 会清除应用位置之后的所有语义,而不管其他合并策略如何。
隐藏语义
在某些情况下,元素不需要发送到无障碍服务,这可能是因为它们的额外信息对于无障碍功能来说是多余的,或者它们纯粹是视觉装饰,不具有互动性。在这些情况下,您可以使用 hideFromAccessibility API 隐藏元素。
以下示例中包含可能需要隐藏的组件:跨越组件的冗余水印,以及用于装饰性分隔信息的字符。
@Composable fun WatermarkExample( watermarkText: String, content: @Composable () -> Unit, ) { Box { WatermarkedContent() // Mark the watermark as hidden to accessibility services. WatermarkText( text = watermarkText, color = Color.Gray.copy(alpha = 0.5f), modifier = Modifier .align(Alignment.BottomEnd) .semantics { hideFromAccessibility() } ) } } @Composable fun DecorativeExample() { Text( modifier = Modifier.semantics { hideFromAccessibility() }, text = "A dot character that is used to decoratively separate information, like •" ) }
在此处使用 hideFromAccessibility 可确保水印和装饰对无障碍服务隐藏,但仍保留其语义以用于其他用例(例如测试)。
应用场景细分
以下是应用场景摘要,可帮助您了解如何清楚地区分上述 API:
- 如果内容不适合无障碍服务使用:
- 当内容可能具有装饰性或冗余性,但仍必须进行测试时,请使用
hideFromAccessibility。 - 当需要为所有服务清除父级和子级语义时,请使用带有空 lambda 的
clearAndSetSemantics{}。 - 当需要手动设置组件的语义时,请使用
clearAndSetSemantics{/*content*/},并在 lambda 中添加内容。
- 当内容可能具有装饰性或冗余性,但仍必须进行测试时,请使用
- 当内容应被视为一个实体,并且需要其所有子级的信息才能完整时:
- 使用合并语义后代。
为你推荐
- 注意:当 JavaScript 处于关闭状态时,系统会显示链接文字
- Compose 中的无障碍功能
- [Compose 中的 Material Design 2][19]
- 测试 Compose 布局