Чтобы помочь людям с потребностями в специальных возможностях успешно использовать ваше приложение, спроектируйте его так, чтобы оно поддерживало ключевые требования к специальным возможностям.
Учитывайте минимальные размеры сенсорных целей.
Любой элемент на экране, на который кто-то может нажать, коснуться или взаимодействовать с ним, должен быть достаточно большим для надежного взаимодействия. При определении размера этих элементов обязательно установите минимальный размер 48 dp, чтобы правильно следовать рекомендациям по доступности Material Design .
Компоненты материала, такие как Checkbox
, RadioButton
, Switch
, Slider
и Surface
, устанавливают этот минимальный размер внутри себя, но только тогда, когда компонент может получать действия пользователя. Например, если для параметра onCheckedChange
Checkbox
установлено значение, отличное от NULL, флажок включает в себя отступы, чтобы его ширина и высота составляли не менее 48 dp.
@Composable private fun CheckableCheckbox() { Checkbox(checked = true, onCheckedChange = {}) }
Если для параметра onCheckedChange
установлено значение null, заполнение не включается, поскольку с компонентом нельзя взаимодействовать напрямую.
@Composable private fun NonClickableCheckbox() { Checkbox(checked = true, onCheckedChange = null) }
При реализации элементов управления выбором, таких как 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) } } }
Если размер интерактивного составного объекта меньше минимального размера сенсорного объекта, 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) ) } }
Чтобы предотвратить возможное перекрытие сенсорных областей разных составных элементов, всегда используйте достаточно большой минимальный размер для составного объекта. В данном примере это будет означать использование модификатора 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) ) } }
Добавить метки кликов
Вы можете использовать метку щелчка, чтобы добавить семантическое значение поведению щелчка составного объекта. Метки кликов описывают, что происходит, когда пользователь взаимодействует с составным объектом. Службы специальных возможностей используют метки кликов, чтобы помочь описать приложение пользователям с особыми потребностями.
Установите метку клика, передав параметр в модификаторе clickable
:
@Composable private fun ArticleListItem(openArticle: () -> Unit) { Row( Modifier.clickable( // R.string.action_read_article = "read article" onClickLabel = stringResource(R.string.action_read_article), onClick = openArticle ) ) { // .. } }
В качестве альтернативы, если у вас нет доступа к модификатору кликабельности, установите метку клика в модификаторе семантики :
@Composable private fun LowLevelClickLabel(openArticle: () -> Boolean) { // R.string.action_read_article = "read article" val readArticleLabel = stringResource(R.string.action_read_article) Canvas( Modifier.semantics { onClick(label = readArticleLabel, action = openArticle) } ) { // .. } }
Опишите визуальные элементы
Когда вы определяете компонуемое Image
или Icon
, платформа Android не может автоматически понять, что отображает приложение. Вам необходимо передать текстовое описание визуального элемента.
Представьте себе экран, на котором пользователь может поделиться текущей страницей с друзьями. Этот экран содержит кликабельный значок «Поделиться»:
Основываясь только на значке, платформа 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
. Спросите себя, передает ли элемент информацию, которая понадобится пользователю для выполнения своей задачи. Если нет, то лучше оставить описание.
Объединить элементы
Службы специальных возможностей, такие как Talkback и Switch Access, позволяют пользователям перемещать фокус между элементами на экране. Важно, чтобы элементы были сфокусированы на правильной детализации. Когда каждый низкоуровневый компонент на экране фокусируется независимо, пользователям приходится много взаимодействовать, чтобы перемещаться по экрану. Если элементы сливаются слишком агрессивно, пользователи могут не понять, какие элементы принадлежат друг другу.
Когда вы применяете clickable
модификатор к составному объекту, Compose автоматически объединяет все элементы, содержащиеся в составном объекте. Это также справедливо для ListItem
; элементы внутри элемента списка сливаются вместе, и службы доступности рассматривают их как один элемент.
Можно иметь набор составных элементов, образующих логическую группу, но эта группа не является кликабельной и не является частью элемента списка. Вам по-прежнему хотелось бы, чтобы службы специальных возможностей рассматривали их как один элемент. Например, представьте себе составной объект, который показывает аватар пользователя, его имя и некоторую дополнительную информацию:
Вы можете включить Compose для объединения этих элементов, используя параметр mergeDescendants
в модификаторе semantics
. Таким образом, службы доступности выбирают только объединенный элемент, и все семантические свойства потомков объединяются.
@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") } } }
Службы доступности теперь фокусируются сразу на всем контейнере, объединяя их содержимое:
Добавить специальные действия
Взгляните на следующий элемент списка:
Когда вы используете программу чтения с экрана, например Talkback, чтобы прослушать то, что отображается на экране, она сначала выбирает весь элемент, а затем значок закладки.
В длинном списке это может стать очень повторяющимся. Лучшим подходом является определение настраиваемого действия, которое позволит пользователю добавить элемент в закладки. Имейте в виду, что вам также необходимо явно удалить поведение самого значка закладки, чтобы убедиться, что он не выбран службой специальных возможностей. Это делается с помощью clearAndSetSemantics
:
@Composable private fun PostCardSimple( /* ... */ isFavorite: Boolean, onToggleFavorite: () -> Boolean ) { val actionLabel = stringResource( if (isFavorite) R.string.unfavorite else R.string.favorite ) Row( modifier = Modifier .clickable(onClick = { /* ... */ }) .semantics { // Set any explicit semantic properties customActions = listOf( CustomAccessibilityAction(actionLabel, onToggleFavorite) ) } ) { /* ... */ BookmarkButton( isBookmarked = isFavorite, onClick = onToggleFavorite, // Clear any semantics properties set on this node modifier = Modifier.clearAndSetSemantics { } ) } }
Опишите состояние элемента
Составной объект может определить stateDescription
для семантики, которую платформа Android использует для считывания состояния, в котором находится составной объект. Например, переключаемый составной объект может находиться либо в «проверенном», либо в «непроверенном» состоянии. В некоторых случаях вам может потребоваться переопределить метки описания состояния по умолчанию, которые использует Compose. Вы можете сделать это, явно указав метки описания состояния перед определением составного объекта как переключаемого:
@Composable private fun TopicItem(itemTitle: String, selected: Boolean, onToggle: () -> Unit) { val stateSubscribed = stringResource(R.string.subscribed) val stateNotSubscribed = stringResource(R.string.not_subscribed) Row( modifier = Modifier .semantics { // Set any explicit semantic properties stateDescription = if (selected) stateSubscribed else stateNotSubscribed } .toggleable( value = selected, onValueChange = { onToggle() } ) ) { /* ... */ } }
Определите заголовки
Приложения иногда отображают много контента на одном экране в прокручиваемом контейнере. Например, на экране может отображаться полное содержание статьи, которую читает пользователь:
Пользователи с ограниченными возможностями испытывают трудности с навигацией по такому экрану. Для облегчения навигации укажите, какие элементы являются заголовками. В предыдущем примере заголовок каждого подраздела можно определить как заголовок для обеспечения доступности. Некоторые службы специальных возможностей, такие как Talkback, позволяют пользователям переходить напрямую от заголовка к заголовку.
В Compose вы указываете, что составной элемент является заголовком , определяя его свойство semantics
:
@Composable private fun Subsection(text: String) { Text( text = text, style = MaterialTheme.typography.headlineSmall, modifier = Modifier.semantics { heading() } ) }
Обработка пользовательских составных элементов
Всякий раз, когда вы заменяете определенные компоненты Material в своем приложении пользовательскими версиями, вы должны учитывать соображения доступности.
Предположим, вы заменяете Checkbox
«Материал» своей собственной реализацией. Вы можете забыть добавить модификатор triStateToggleable
, который обрабатывает свойства доступности для этого компонента.
Как правило, посмотрите на реализацию компонента в библиотеке материалов и сымитируйте любое поведение доступности, которое сможете найти. Кроме того, активно используйте модификаторы Foundation, а не модификаторы уровня пользовательского интерфейса, поскольку они включают в себя соображения доступности по умолчанию.
Протестируйте реализацию пользовательского компонента с помощью нескольких служб доступности, чтобы проверить ее поведение.
Дополнительные ресурсы
- Доступность : основные концепции и методы, общие для всей разработки приложений для Android.
- Создание доступных приложений : основные шаги, которые вы можете предпринять, чтобы сделать ваше приложение более доступным.
- Принципы улучшения доступности приложения : ключевые принципы, которые следует учитывать при работе над тем, чтобы сделать ваше приложение более доступным.
- Тестирование доступности : принципы и инструменты тестирования доступности Android.