API 默认值

Material、Compose 界面和 Foundation API 默认实现并提供许多无障碍实践。它们包含遵循其特定角色和功能的内置语义。这意味着,您只需进行少量额外工作或无需进行额外工作,即可提供大多数无障碍支持。

针对适当的目的使用适当的 API 意味着组件通常带有涵盖标准用例的预定义无障碍行为。不过,请务必仔细检查这些默认设置是否符合您的无障碍需求。如果无法满足,Compose 还提供了其他方法来满足更具体的要求。

了解 Compose API 中的默认无障碍语义和模式有助于您在使用这些 API 时考虑到无障碍功能。它还可帮助您在更多自定义组件中支持无障碍功能。

触摸目标尺寸下限

屏幕上可供用户点击、触摸或互动的任何元素都必须足够大,让用户能够进行可靠的互动。调整这些元素的大小时,请务必将最小尺寸设置为 48dp,以正确遵循 Material Design 无障碍指南。

Material 组件(例如 Checkbox、RadioButton、Switch、Slider 和 Surface)会在内部设置此最小尺寸,但仅当组件可以接收用户操作时才会设置。例如,当 Checkbox 的 onCheckedChange 参数设置为非 null 值时,复选框会包含内边距,使其宽度和高度至少为 48 dp。

@Composable
private fun CheckableCheckbox() {
    Checkbox(checked = true, onCheckedChange = {})
}

一个具有默认内边距、宽度和高度均为 48 dp 的复选框。
图 1. 具有默认内边距的复选框。

当 onCheckedChange 参数设为 null 时,将不会添加内边距,因为用户无法直接与该组件互动。

@Composable
private fun NonClickableCheckbox() {
    Checkbox(checked = true, onCheckedChange = null)
}

没有内边距的复选框。
图 2. 不带内边距的复选框。

实现 Switch、RadioButton 或 Checkbox 等选择控件时,您通常需要将可点击行为向上传给父级容器,方法是将对可组合项的点击回调设置为 null,并为父级可组合项添加一个 toggleable 或 selectable 修饰符。

@Composable
private fun CheckableRow() {
    MaterialTheme {
        var checked by remember { mutableStateOf(false) }
        Row(
            Modifier
                .toggleable(
                    value = checked,
                    role = Role.Checkbox,
                    onValueChange = { checked = !checked }
                )
                .padding(16.dp)
                .fillMaxWidth()
        ) {
            Text("Option", Modifier.weight(1f))
            Checkbox(checked = checked, onCheckedChange = null)
        }
    }
}

“选项”文本旁边的复选框,正在被选中和取消选中。
图 3. 具有可点击行为的复选框。

当可点击可组合项的尺寸小于最小触摸目标尺寸时,Compose 仍会增加触摸目标尺寸。为此,它会将触摸目标大小扩展到可组合项的边界之外。

以下示例包含一个非常小的可点击 Box。触摸目标区域会自动扩展到 Box 的边界之外,因此点按 Box 旁边仍会触发点击事件。

@Composable
private fun SmallBox() {
    var clicked by remember { mutableStateOf(false) }
    Box(
        Modifier
            .size(100.dp)
            .background(if (clicked) Color.DarkGray else Color.LightGray)
    ) {
        Box(
            Modifier
                .align(Alignment.Center)
                .clickable { clicked = !clicked }
                .background(Color.Black)
                .size(1.dp)
        )
    }
}

一个非常小的可点击框,通过点按该框旁边,可将其展开为更大的触摸目标。
图 4. 一个非常小的可点击框,可展开为更大的触摸目标。

为防止不同可组合项的触摸区域之间可能发生重叠,请始终为可组合项使用足够大的最小尺寸。在示例中,这意味着使用 sizeIn 修饰符设置内部框的最小尺寸:

@Composable
private fun LargeBox() {
    var clicked by remember { mutableStateOf(false) }
    Box(
        Modifier
            .size(100.dp)
            .background(if (clicked) Color.DarkGray else Color.LightGray)
    ) {
        Box(
            Modifier
                .align(Alignment.Center)
                .clickable { clicked = !clicked }
                .background(Color.Black)
                .sizeIn(minWidth = 48.dp, minHeight = 48.dp)
        )
    }
}

上一个示例中的极小方框已增大,以创建更大的触摸目标。
图 5. 一个较大的方框触摸目标。

如果元素必须保持较小的可见尺寸,请改用 Material 3 中的 minimumInteractiveComponentSize 修饰符。它会在每个维度上预留至少 48 dp 的布局空间,并将元素置于该空间的中心,以便元素保持其绘制大小,并让周围的元素移动以腾出空间。该修饰符不会扩展触摸目标本身,而是在布局中为前面所述的扩展留出空间。Checkbox 等 Material 组件会在内部使用此修饰符。请将其放在任何大小修饰符之前,因为其前面的大小修饰符会限制它可以预留的空间。

@Composable
private fun MinimumInteractiveBox() {
    var clicked by remember { mutableStateOf(false) }
    Row(verticalAlignment = Alignment.CenterVertically) {
        Box(
            Modifier
                .clickable { clicked = !clicked }
                .minimumInteractiveComponentSize()
                .size(24.dp)
                .background(if (clicked) Color.DarkGray else Color.Black)
        )
        Text("Option")
    }
}

图形元素

当您定义 Image 或 Icon 可组合项时,Android 框架无法自动了解应用显示的内容。您需要传递图形元素的文本说明。

假设有一个屏幕,用户可以通过这个屏幕与朋友分享当前页面。此屏幕包含一个可点击的分享图标:

一排四个可点击的图标,其中“分享”图标突出显示。
图 6. 一行可点击的图标,其中“分享”图标处于选中状态。

仅根据图标,Android 框架无法向视力受损的用户描述该图标。Android 框架需要图标的其他文本说明。

contentDescription 参数用于描述图形元素。请使用本地化字符串,因为用户可以看到该字符串。

@Composable
private fun ShareButton(onClick: () -> Unit) {
    IconButton(onClick = onClick) {
        Icon(
            imageVector = Icons.Filled.Share,
            contentDescription = stringResource(R.string.label_share)
        )
    }
}

有些图形元素纯粹是装饰性的,您可能不想将其传达给用户。将 contentDescription 参数设置为 null 时,您是在向 Android 框架表明此元素没有关联的操作或状态。

@Composable
private fun PostImage(post: Post, modifier: Modifier = Modifier) {
    val image = post.imageThumb ?: painterResource(R.drawable.placeholder_1_1)

    Image(
        painter = image,
        // Specify that this image has no semantic meaning
        contentDescription = null,
        modifier = modifier
            .size(40.dp, 40.dp)
            .clip(MaterialTheme.shapes.small)
    )
}

contentDescription 主要用于图形元素,例如图片。Material 组件(例如 Button 或 Text)和可操作行为(例如 clickable 或 toggleable)附带其他预定义语义,这些语义描述了其固有行为,并且可以通过其他 Compose API 进行更改。

互动元素

Material 和 Foundation Compose API 可创建用户能够通过 clickable 和 toggleable 修饰符 API 进行互动的界面元素。由于可交互组件可能由多个元素组成,因此 clickable 和 toggleable 默认会合并其子项的语义,以便将该组件视为一个逻辑实体。

例如,Material Button 可能由子图标和一些文本组成。Material Button 默认会合并其子项语义,而不是将子项视为单独的个体,以便无障碍服务可以相应地对它们进行分组:

具有未合并与已合并子级语义的按钮。
图 7. 具有未合并与合并子语义的按钮。

同样,使用 clickable 修饰符也会导致可组合项将其后代的语义合并到单个实体中,该实体会连同相应的操作表示形式一起发送到无障碍服务:

Row(
    // Uses `mergeDescendants = true` under the hood
    modifier = Modifier.clickable { openArticle() }
) {
    Icon(
        painter = painterResource(R.drawable.ic_logo),
        contentDescription = "Open",
    )
    Text("Accessibility in Compose")
}

您还可以在父级可点击项上设置特定的 onClickLabel,以便向无障碍服务提供更多信息,并更精细地表示操作:

Row(
    modifier = Modifier
        .clickable(onClickLabel = "Open this article") {
            openArticle()
        }
) {
    Icon(
        painter = painterResource(R.drawable.ic_logo),
        contentDescription = "Open"
    )
    Text("Accessibility in Compose")
}

以 TalkBack 为例,此 clickable 修饰符及其点击标签可让 TalkBack 提供“点按两次即可打开此文章”的操作提示,而不是更通用的默认反馈“点按两次即可激活”。

此反馈会因操作类型而异。长按会提供 TalkBack 提示“点按两次并按住即可”,后跟一个标签:

Row(
    modifier = Modifier
        .combinedClickable(
            onLongClickLabel = "Bookmark this article",
            onLongClick = { addToBookmarks() },
            onClickLabel = "Open this article",
            onClick = { openArticle() },
        )
) {}

在某些情况下,您可能无法直接访问 clickable 修饰符(例如,当它设置在较低的嵌套层中时),但仍希望将朗读标签从默认值更改为其他值。为此,请使用 semantics 修饰符,将设置 clickable 与修改通知分开,并在其中设置点击标签,以修改操作表示:

@Composable
private fun ArticleList(openArticle: () -> Unit) {
    NestedArticleListItem(
        // Clickable is set separately, in a nested layer:
        onClickAction = openArticle,
        // Semantics are set here:
        modifier = Modifier.semantics {
            onClick(
                label = "Open this article",
                action = {
                    // Not needed here: openArticle()
                    true
                }
            )
        }
    )
}

您无需传递点击操作两次。现有的 Compose API(例如 clickable 或 Button)会为您处理此问题。合并逻辑会验证是否针对存在的信息采用了最外层的修饰符标签和操作。在前面的示例中,NestedArticleListItem 会自动将其 openArticle() 点击操作传递给其 clickable 语义。您可以在第二个语义修饰符操作中将点击操作留空。不过,点击标签取自第二个语义修饰符 onClick(label = "Open this document"),因为它在第一个语义修饰符中不存在。

您可能会遇到这样的情况:您希望将子级语义合并到父级语义中,但实际并未发生。如需了解更深入的信息,请参阅合并和清除。

自定义组件

构建自定义组件时,请查看 Material 库或其他 Compose 库中类似组件的实现。然后,根据需要模仿或修改其无障碍行为。例如,如果您将 Material Checkbox 替换为您自己的实现,则查看现有的 Checkbox 实现会提醒您添加 triStateToggleable 修饰符,该修饰符用于处理组件的无障碍属性。此外,要大量使用 Foundation 修饰符,因为这些修饰符包含内置的无障碍服务注意事项和本部分中介绍的现有 Compose 实践。

您还可以在清除和设置语义部分中找到自定义切换开关组件的示例,并在 API 指南中找到有关如何在自定义组件中支持无障碍功能的更详细信息。