1、简介

您将学习怎样运用Jetpack Compose改善运用的无障碍功用。我们将具体介绍几个常见的用例,并逐渐改善一个示例运用。我们将介绍触摸政策大小、内容描绘、点击标签等等。

视力受损、色盲、听力收缩、精细动作失能的人、以及有认知障碍和许多其他残疾的人可以运用Android设备来处理他们日常生活中的各种事物、假设您可以在开发运用时考虑到无障碍功用,那么您便可以改善用户体会,对具有这少许需求以及其他无障碍功用的用户来说特别如此。

我们将运用TalkBack手动检验代码更改。TalkBack 是一项首要供视力受损的人运用的无障碍服务。此外,请确保运用其他无障碍服务(例如开关操控)检验对代码所做的任何更改。

Jetpack Compose(第十一趴)——运用Jetpack Compose改善运用的无障碍功用

1.1、学习内容:

  • 怎样通过增大触摸政策大小来辅助惊喜动作失能的用户。
  • 什么是语义特色以及怎样更改语义特色。
  • 怎样向可组合项供应信息,让其运用起来更快捷。

2、准备工作

在此过程中,您将下载此 Codelab 的代码,其间包含一个简略的新闻阅览器运用。

所需条件

获取代码

此 Codelab 的代码可以在android-compose-codelabs GitHub 代码库中找到。如需克隆该代码库,请运行以下指令:

$ git clone https://github.com/android/codelab-android-compose

3、触摸政策大小

屏幕上可供用户点击、触摸或以其他方法互动的全部元素都应足够大,让用户可以进行牢靠的互动。您应确保这些元素的宽度和高度至少为48dp

某些Material组件会为您设置这些大小。例如,Button可组合项的MinHeight设置为36dp,并运用8dp的垂直内边距。这加起来就是要求的48dp高度。

当我们翻开示例运用并运行TalkBack时,我们会留意到,文章卡片中叉号图标的触摸政策十分小。我们希望此触摸政策至少为48dp。

鄙人面的屏幕截图中,左面是原始运用,而右侧是改善的处理计划。

Jetpack Compose(第十一趴)——运用Jetpack Compose改善运用的无障碍功用

让我们来看看相应的结束,看一下此可组合项的大小。翻开PostCards.kt并查找PostCardHistory可组合项。如您所见,该结束将溢出菜单图标的大小设置为24dp:

@Composable
fun PostCardHistory(post: Post, navigateToArticle: (String) -> Unit) {
    // ...
    Row(
        // ...
    ) {
        // ...
        CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.medium) {
            Icon(
                imageVector = Icons.Default.Close,
                contentDescription = stringResource(R.string.cd_show_fewer),
                modifier = Modifier
                    .clickable { openDialog = true }
                    .size(24.dp)
            )
        }
    }
}

如需增大此Icon的触摸政策大小我么可以加上内边距:

@Composable
fun PostCardHistory(post: Post, navigateToArticle: (String) -> Unit) {
    // ...
    Row(
        // ...
    ) {
        // ...
        CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.medium) {
            Icon(
                imageVector = Icon.Default.Close,
                contentDescription = stringResource(R.string.cd_show_fewer),
                modifier = Modifier
                    .clickable { openDialog = true }
                    .padding(12.dp)
                    .size(24.dp)
            )
        }
    }
}
留意:修饰符函数的次第十分重要。由于每个函数都会对上一个函数回来的Modifier进行更改,因此次第会影响毕竟结果。在本例中,我们在设置大小之前但在运用clickable修饰符之后运用padding。这样,大小会加上内边距,并且整个元素是可点击的。

在我们的用例中,有一种更简略的办法确保触摸政策至少为48dp。我们可以运用Material组件IconButton,该组件将为我们处理此问题:

@Composable
fun PostCardHistory(post: Post, navigateToArticle: (String) -> Unit) {
    // ...
    Row(
        // ...
    ) {
        // ...
        CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.medium) {
            IconButton(onClick = { openDialog = true }) {
                Icon(
                    imageVector = Icons.Default.Close,
                    contentDescription = stringResource(R.string.cd_show_fewer)
                )
            }
        }
    }
}

运用TalkBack阅览屏幕时,现在会正确闪现48dp的触摸政策区域。此外,IconButton还添加了涟漪作用,向用户标明该元素是可点击的。

4、点击标签

默许情况下,运用中的可点击元素不会供应任何关于该元素会在点击时做什么的信息。因此,TalkBack等无障碍服务将会运用十分广泛的默许描绘。

为了向具有无障碍功用需求的用户供应最佳的体会,我们可以供应具体的描绘,解说当用户点击此元素时会发生什么情况。

在Jetnews运用中,用户可点击各个文章卡片以阅览正片文章。默许情况下,这样讲独处可点击元素的内容,紧接着是文字“Double tap to activate”(点按两次即可激活)。我们希望更具体,运用“Double tap to read article”(点按两次即可阅览文章)。下面是原始版别与志向处理发问的比较:

Jetpack Compose(第十一趴)——运用Jetpack Compose改善运用的无障碍功用

更改可组合项的点击标签。之前(左面)与之后(右侧)的比较。

clickable修饰符包含一个参数,可让您直接设置此点击标签。

让我们再来看看PostCardHistory结束:

@Composable
fun PostCardHistory(
    // ...
) {
    Row(
        Modifier.clickable { navigateToArticle(post.id) }
    ) {
        // ...
    }
}

如您所见,此结束运用了clickable修饰符。如需设置点击标签,我们可以设置onClickLabel参数:

@Composable
fun PostCardHistory(
    // ...
) {
    Row(
        Modifier.clickable(
            // R.string.action_read_article = "read article"
            onClickLable = stringResource(R.string.action_read_article)
        ) {
            navigateToArticle(post.id)
        }
    ) {
        // ...
    }
}

TalkBack现在会正确读出“Double tap to read article”.

主屏幕上的其他文章卡片具有相同的点击标签。让我们来看看PostCardPopular可组合项的结束并更新其点击标签:

@Composable
fun PostCardPopular(
    // ...
) {
    Card(
        shape = MaterialTheme.shapes.medium,
        modifier = modifier.size(280.dp, 240.dp),
        onClick = { navigateToArticle(post.id) }
    ) {
        // ...
    }
}

此可组合项在内部运用Card可组合项,后者不允许您直接设置点击标签。您可以改用semantics修饰符设置点击标签:

@OptIn(ExperimentalMaterialApi::class)
@Composable
fun PostCardPopular(
    post: Post,
    navigateToArticle: (String) -> Unit,
    modifier: Modifier = Modifier
) {
    val readArticleLabel = stringResource(id = R.string.action_read_article)
    Card(
        shape = MaterialTheme.shapes.medium,
        modifier = modifier
            .size(280.dp, 240.dp)
            .semantics { onCLick(label = readArticleLabel, action = null) },
        onClick = { navigateToArticle(post.id) }
    ) {
        // ...
    }
}

5、自定义操作

许多运用都会闪现某种列表,列表中的每一项都包含一项或多项操作。运用屏幕阅览器时,阅览此类列表或许会变得单调乏味,由于相同的操作会被回来集合。

我们可以向可组合项添加自定义无障碍操作。这样,就可以将与赞同列表项相关的操作归为一组。

在Jetnews运用中,我们闪现用户可以阅览的文章列表。每个列表项都包含一项操作,指明用户希望少看到此主题。在本部分中,我们会将此操作移植一项自定义无障碍操作,这样阅览列表就变得更简单。

左面闪现的是默许情况,其间每个叉号图标都可集合。右侧闪现的处理计划,其间该操作包含在TalkBack的自定义操作中:

Jetpack Compose(第十一趴)——运用Jetpack Compose改善运用的无障碍功用

向文章项添加自定义操作。之前(左面)与之后(右侧)的比较。

让我们翻开PostCard.kt并查看PostCardHistory可组合项的结束。请留意运用Mdoifier.clickableonClickRowIconButton的可点击特色:

@Composable
fun PostCardHistory(post: Post, navigateToArticle: (String) -> Unit) {
    // ...
    Row(
        Modifier.clickable(
            onClickLabel = stringResource(R.string.action_read_article)
        ) {
            navigateToArticle(post.id)
        }
    ) {
        // ...
        CompositionLocalProvider(LocaalContentAlpha provides ContentAlpha.medium) {
            IconButton(onClick = { openDialog = true }) {
                Icon(
                    imageVector = Icons.Default.Close,
                    contentDescription = stringResource(R.string.cd_show_fewer)
                )
            }
        }
    }
}

默许情况下,RowIconButton可组合项都可点击,因此都会由TalkBack集合。列表中的每一项都是如此,这意味着,在阅览列表时需求进行许多的滑动。我们希望与IconButton相关的操作作为一项自定义操作包含在列表项中。我们可以运用clearAndSetSemantics修饰符来奉告无障碍服务不要与此Icon进行互动。

@Composable
fun PostCardHistory(post: Post, navigateToArticle: (String) -> Unit) {
    // ...
    Row(
        Modifier.clickable(
            onClickLable = stringResource(R.string.action_read_article) 
        ) {
                navigateToArticle(post.id)
            }
    ) {
        // ...
        CompositionLocalProvider(LocalContentAlpha provides ContentAlpah.medium) {
            IconButton(
                modifier = Modifier.clearAndSetSemantics { },
                onClick = { openDialog = true }
            ) {
                Icon(
                    imageVector = Icons.Default.Close,
                    contentDescription = stringResource(R.string.cd_show_fewer)
                )
            }
        }
    }
    // ...
}

不过,通过移除IconButton的涵义,现在无法再实行该操作了。我或许可以将该操作添加到列表项中,办法是在semantics修饰符中添加自定义操作:

@Composable
fun PostCardHistory(post: Post, navigateToArticle: (String) -> Unit) {
    // ...
    val showFewerLabel = stringResource(R.string.cd_show_fewer)
    Row(
        Modifier
            .clickable(
                onClickLabel = strignResource(R.string.action_read_article)
            ) {
                navigateToArticle(post.id)
            }
            .semantics {
                customActions = listOf(
                    CustomAccessibilityAction(
                        label = showFewerLabel,
                        // action returns boolean to indicate success
                        action = { openDialog = true; true }
                    )
                )
            }
    ) {
        // ...
        CompositionLabelProvider(LocalContentAlpha provides ContentAlpha.medium) {
            IconButton(
                modifier = Modifier.clearAndSetSemantics { },
                onClick = { openDialog = true }
            ) {
                Icon(
                    imageVector = Icons.Default.Close,
                    contentDescription = showFewerLabel
                )
            }
        }
    }
    // ...
}

现在,我们可以运用TalkBack中自定义操作弹出菜单来运用该操作。随着列表项中的操作数不断添加,浙江变得越来越有意义。

6、视觉元素描绘

并不是运用的每个用户都可以看到或解说运用中闪现的视觉元素,如图标和插入。无障碍服务也无法仅仅依据视觉元素的像历来弄清楚这些元素的意思。这使得开发者有必要将有关运用中的视觉元素的更多信息传递给无障碍服务。

ImageIcon等视觉可组合项包含一个contentDescription参数。您可以在其间传递该视觉元素的本地化描绘,假设该元素是纯装饰性的,则传递null

在我们的运用中,文章屏幕短少一些内容描绘。让我们运行运用并挑选顶部的文章以导航到文章屏幕。

Jetpack Compose(第十一趴)——运用Jetpack Compose改善运用的无障碍功用

添加视觉内容描绘。之前(左面)与之后(右侧)的比较。

假设我们不供应任何信息,当用户点击左上角的导航图标时,TalkBack将简略地独处“Button bdoule tap to activate”。这样并没有奉告用户有关当他们激活该按钮时将会实行什么操作的任何信息。让我们翻开ArticleScreen.kt:

@Composable
fun ArticleScreen(
    // ...
) {
    // ...
    Scaffold(
        topBar = {
            InsetAwareTopAppBar(
                title = {
                    // ...
                },
                navigationIcon = {
                    IconButton(onClick = onBack) {
                        Icon(
                            imageVector = Icons.Filled.ArrowBack,
                            contentDescription = null
                        )
                    }
                }
            )
        }
    )
}

向Icon添加有意义的内容描绘:

@Composable
fun ArticleScreen(
    // ...
) {
    // ...
    Scaffold(
        topBar = {
            InsetAwareTopAppBar(
                title = {
                    // ...
                },
                navigationIcon = {
                    IconButton(onClick = onBack) {
                        Icon(
                            imageVector = Icons.Filled.ArrowBack,
                            contentDescription = stringResource(
                                R.string.cd_navigate_up
                            )
                        )
                    }
                }
            )
        }
    )
}

这篇文章中的另一个视觉元素是标题图片。在本例中,此图片是纯装饰性的,它没有闪现我们需求传达给用户的任何信息。因此,将内容描绘设置为null,当我们运用无障碍服务时,会跳过该元素。

品名㕜的最后有一个视觉元素是个人资料相片。在本例中,我们运用的是通用头衔,因此没有必要在此处添加内容小看。当我们运用此作者的实践个人相片时,我们可以让他们为其供应合适的内容描绘。

7、标题

当屏幕包含许多的文字时,就像我们的文章屏幕相同,有视觉障碍的用户很难快速找到他们想要查找的板块。为了协助处理此问题,我们可以指明文字的哪些部分是标题。然后,用户就可以通过向上或向下滑动来快速阅览这些不同的标题。

默许情况下,没有可组合项被标记为标题,因此不可以进行导航。我们希望文章屏幕供应按标题导航:

Jetpack Compose(第十一趴)——运用Jetpack Compose改善运用的无障碍功用

添加标题。之前(左面)与之后(右侧)的比较。

文章中的标题是在PostContent.kt中定义的。让我们翻开该文件并滚动到Paragraph可组合项:

@Composable
private fun Paragraph(paragraph: Paragraph) {
    // ...
    Box(modifier = Modifier.padding(bottom = trailingPadding)) {
        // ...
        ParagraphType.Header -> {
            Text(
                modifier = Modifier.padding(4.dp),
                text = annotatedString,
                style = textStyle.merge(paragraphStyle)
            )
        }
        // ...
    }
}

此处, Header被定义为一个简略的Text可组合项。我们可以设置heading语义特色,以指明此可组合项时标题。

@Composable
private fun Paragraph(paragraph: Paragraph) {
    // ...
    Box(modifier = Modifier.padding(bottom = trailingPadding)) {
        when (paragraph.type) {
            // ...
            ParagraphType.Header -> {
                Text(
                    modifier = Modifier.padding(4.dp)
                        .semantics { heading() },
                        text = annotatedString,
                        style = textStyle.merge(paragraphStyle)
                )
            }
            // ...
        }
    }
}

8、自定义吞并

正如我们在前面的过程中所看到的,TalkBack等无障碍服务按元素在屏幕中导航。默许情况下,Jetpack Compose中至少设置了一个语义特色的每个初级可组合项会取得焦点。例如,text语义特色,因此会取得焦点。

不过,假设屏幕上有太多可集合的元素,当用户逐一阅览这些元素时,会导致混乱。我们可以运用semantics修饰符及其mergeDescendants特色将可组合项吞并在一起。

让我们看一下文章屏幕。大多数元素都取得了正确等级的焦点。但是,文章的元数据现在是作为几个独自的项目朗诵的。可以通过将其吞并为一个可集合实体来加以改善:

Jetpack Compose(第十一趴)——运用Jetpack Compose改善运用的无障碍功用

吞并可组合项。之前(左面)与之后(右侧)的比较:

让我们翻开PostContent.kt并查看PostMetadata可组合项:

@Composable
private fun PostMetadata(metadata: Metadata) {
    // ...
    Row {
        Image(
            // ...
        )
        Spacer(Modifier.width(8.dp))
        Column {
            Text(
                // ...
            )
            CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.medium) {
                Text(
                    // ...
                )
            }
        }
    }
}

我们可以让顶级行吞并它的子孙,这样就会发生我们想要的行为:

@Composable
private fun PostMetadata(medatada: Metadata) {
    // ...
    Row(Modifier.semantics(mergeDescendants = true) {}) {
        Image(
            // ...
        )
        Spacer(Modifier.width(8.dp))
        Column {
            Text(
                // ...
            )
            CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.medium) {
                Text(
                    // ...
                )
            }
        }
    }
}

9、切换开关和复选框

当TalkBack选定SwitchCheckbox等可切换元素时,会大声读出其选装情况。不过,假设没有上下文,就很难了解这些课切换元素指的是什么。我们可以通过提高可切换情况来包含可切换元素的上下文,这样用户就可以通过按可组合项本身或描绘它的标签来切换SwitchCheckbox

我们可以在”Interests”屏幕中看到一个这样的比方。您可以从主屏幕中翻开抽屉式导航栏来导航到该屏幕。在“Interests”屏幕上,有用户可以订阅的主题列表。默许你情况下,次屏幕上的复选框与其标签是分开集合的,这使得很难了解其上下文。我们希望整个Row可切换:

Jetpack Compose(第十一趴)——运用Jetpack Compose改善运用的无障碍功用

运用复选框。之前(左面)与之后(右侧)的比较。

让我们翻开InterestsScreen.kt并查看TopicItem可组合项的结束:

@Composable
private fun TopicItem(itemTitle: String, selected: Boolean, onToggle: () -> Unit) {
    // ...
    Row(
        modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp)
    ) {
        // ...
        Checkbox(
            checked = selected,
            onCheckedChange = { onToggle() },
            modifier = Modifier.align(Alignment.CenterVertically)
        )
    }
}

如您所见,Checkbox有一个onCheckedChange回调,用于处理元素的开关切换。我们可以将此回调提高到整个Row的等级:

@Composable
private fun TopicItem(itemTitle: String, selected: Boolean, onToggle: () -> Unit) {
    // ...
    Row(
        modifier = Modifier
            .toggleable(
                value = selected,
                onValueChange = { _ -> onToggle() },
            )
            .padding(horizontal = 16.dp, vertical = 8.dp)
    ) {
        // Checkbox(
            checked = selected,
            onCheckedChange = null,
            modifier = Modifeir.align(Alignment.CenterVertically)
        )
    }
}

10、情况描绘

在上一步中,我们将开关切换行为从Checkbox提高到父级Row。我们可以通过可组合项的情况添加自定义描绘来进一步改善此元素的无障碍功用。

默许情况下,Checkbox情况读作“Ticked”或“Not ticked”。我们可以将此描绘替换为我们自己的自定义描绘:

Jetpack Compose(第十一趴)——运用Jetpack Compose改善运用的无障碍功用

添加情况描绘。之前(左面)与之后(右侧)的比较。 我们可以继续运用在最后一步中改写的TopicItem可组合项:

@Composable
private fun TopicItem(itemTitle: String, selected: Boolean, onToggle: () -> Unit) {
    // ...
    Row(
        modifier = Modifier
            .toggleable(
                value = selected,
                onValueChange = { _ -> onToggle() },
                role = Role.Checkbox
            )
            .padding(horizontal = 16.dp, vertical = 8.dp)
    ) {
        // ...
        Checkbox(
            checked = selected,
            onCheckedChange = null,
            modifier = Modifier.align(Aligement.CenterVertically)
        )
    }
}

我们可以运用semantics修饰符中的stateDescription特色来添加自定义情况小看:

@Composable
private fun TopicItem(itemTitle: String, selected: Boolean, onToggle: () -> Unit) {
    // ...
    val stateNotSubscribed = stringResource(R.string.state_not_subscribed)
    val stateSubscribed = stringResource(R.string.state_subscribed)
    Row(
        modifier = Modifier
            .semantics {
                stateDescription = if (selected) {
                    stateSubscribed
                } else {
                    stateNotSubscribed
                }
            }
            .toggleable(
                vale = selected,
                onValueChange = { _ -> onToggle() },
                role = Role.Checkbox
            )
            .padding(horizontal = 16.dp, vertical = 8.dp)
    ) {
        // ...
        Checkbox(
            checked = selected,
            onCheckedChange = null,
            modifier = Modifier.align(Alignment.CenterVertically)
        )
    }
}

11、恭喜

恭喜结束Jetpack Compose十一趴,接下来就需求你在项目上实践所学的常识!