API 默认值

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

为相应用途使用适当的 API 意味着,组件通常会附带预定义的无障碍功能行为,涵盖标准使用情形。不过,请务必仔细检查这些默认设置是否符合您的无障碍需求。如果不是,Compose 提供了多种方法来满足更具体的要求。

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

最小触摸目标尺寸

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

CheckboxRadioButtonSwitchSliderSurface 等 Material 组件在内部设置此最小尺寸,但仅在相应组件可接收用户操作时设置。例如,当 CheckboxonCheckedChange 参数设为非 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. 不含内边距的复选框。

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

@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. 更大的方框触摸目标。

图形元素

在定义 ImageIcon 可组合项时,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 组件(如 ButtonText)和可操作的行为(如 clickabletoggleable)附带了其他预定义的语义,用于描述其内在行为,并且可以通过其他 Compose API 进行更改。

互动元素

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

例如,一个 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(例如 clickableButton)会为您处理此问题。合并逻辑会验证是否针对存在的信息采取了最外层的修饰符标签和操作。在前面的示例中,NestedArticleListItem 会自动将 openArticle() 点击操作传递给其 clickable 语义。您可以在第二个语义修饰符操作中将点击操作留空。不过,点击标签是从第二个语义修饰符 onClick(label = "Open this document") 中获取的,因为它未出现在第一个语义修饰符中。

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

自定义组件

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

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