Помимо основной информации, которую содержит composable-функция, например текстовой строки composable-функции Text, может быть полезно иметь больше дополнительной информации об элементах пользовательского интерфейса.
Информация о значении и роли компонента в Compose называется семантикой. Она позволяет предоставлять дополнительный контекст о компонентах таким сервисам, как специальные возможности, автозаполнение и тестирование. Например, значок камеры может визуально быть просто изображением, но семантическое значение может быть "Сделать фото".
Объединив соответствующую семантику с соответствующими API Compose, вы предоставляете службам доступности как можно больше информации о своем компоненте, которые затем решают, как представить его пользователю.
API Material, Compose UI и Foundation поставляются со встроенной семантикой, которая соответствует их конкретной роли и функции, но вы также можете изменить эту семантику для существующих API или установить новые для пользовательских компонентов в соответствии с вашими конкретными требованиями.
Семантические свойства
Семантические свойства передают значение соответствующей composable-функции. Например, composable-функция Text содержит семантическое свойство text, потому что это значение этой функции. Объект Icon содержит свойство contentDescription (если оно задано разработчиком), которое передает в текстовом виде значение значка.
Рассмотрите, как семантические свойства передают значение composable-функции. Рассмотрим Switch. Вот как это выглядит для пользователя:
Switch во включенном и выключенном состоянии.Чтобы описать значение этого элемента, можно сказать следующее: "Это переключатель, который находится в положении "Включено". Нажмите на него, чтобы взаимодействовать с ним.
Именно для этого и используются семантические свойства. Узел семантики этого элемента Switch содержит следующие свойства, которые можно увидеть в инспекторе макетов:
Switch.Role указывает на тип элемента. StateDescription описывает, как следует ссылаться на состояние "Включено". По умолчанию это локализованная версия слова "Включено", но в зависимости от контекста можно использовать более точное слово (например, "Активировано"). ToggleableState – текущее состояние переключателя. Свойство OnClick указывает на метод, используемый для взаимодействия с этим элементом.
Отслеживание семантических свойств каждой composable-функции в приложении открывает множество мощных возможностей:
- Сервисы специальных возможностей используют свойства, чтобы представлять интерфейс на экране и позволять пользователям взаимодействовать с ним. Для composable-функции Switch TalkBack может озвучить: "Включено; Переключатель; Нажмите дважды, чтобы переключить". Пользователь может дважды нажать на экран, чтобы отключить переключатель.
-
Фреймворк тестирования использует свойства, чтобы находить узлы, взаимодействовать с ними и делать утверждения:
val mySwitch = SemanticsMatcher.expectValue( SemanticsProperties.Role, Role.Switch ) composeTestRule.onNode(mySwitch) .performClick() .assertIsOff()
Composables и модификаторы, созданные на основе базовой библиотеки Compose, по умолчанию уже задают нужные свойства. При необходимости вы можете изменить эти свойства вручную, чтобы улучшить поддержку специальных возможностей для определенных сценариев использования, или изменить стратегию объединения или очистки composable-функций.
Чтобы сообщить службам специальных возможностей о типе контента в компоненте, можно использовать разные семантические свойства. Эти дополнения будут поддерживать основную семантическую информацию и помогут службам специальных возможностей точно настроить то, как ваш компонент представлен, объявлен или взаимодействует.
Полный список семантических свойств приведен в описании объекта SemanticsProperties. Полный список возможных действий со специальными возможностями приведен в объекте SemanticsActions.
Заголовки
В приложениях часто встречаются экраны с большим количеством текста, например длинные статьи или новостные страницы, которые обычно делятся на подразделы с заголовками.
Пользователям с ограниченными возможностями может быть трудно ориентироваться на таком экране. Для удобства навигации некоторые специальные возможности позволяют перемещаться непосредственно между разделами или заголовками. Чтобы включить эту функцию, укажите, что ваш компонент является heading, определив его семантическое свойство:
@Composable private fun Subsection(text: String) { Text( text = text, style = MaterialTheme.typography.headlineSmall, modifier = Modifier.semantics { heading() } ) }
Оповещения и всплывающие окна
Если ваш компонент является оповещением или всплывающим окном, например Snackbar, вы можете сообщить службам специальных возможностей, что пользователям можно передать новую структуру или обновления контента.
Компоненты, похожие на оповещения, можно пометить семантическим свойством liveRegion. Это позволяет специальным возможностям автоматически уведомлять пользователя об изменениях в этом компоненте или его дочерних элементах:
PopupAlert( message = "You have a new message", modifier = Modifier.semantics { liveRegion = LiveRegionMode.Polite } )
В большинстве случаев, когда нужно ненадолго привлечь внимание пользователя к оповещениям или важным изменениям контента на экране, следует использовать liveRegionMode.Polite.
Используйте liveRegion.Assertive только в крайних случаях, чтобы не мешать пользователям.
Используйте его, когда пользователям важно знать, что контент ограничен по времени.
PopupAlert( message = "Emergency alert incoming", modifier = Modifier.semantics { liveRegion = LiveRegionMode.Assertive } )
Динамические области не следует использовать для часто обновляемого контента, например таймеров обратного отсчета, чтобы не перегружать пользователей постоянными уведомлениями.
Компоненты, похожие на окна
Для специальных компонентов, похожих на окна, например ModalBottomSheet, требуются дополнительные сигналы, чтобы отличать их от окружающего контента. Для этого можно использовать семантику paneTitle, чтобы любые изменения в окне или панели были представлены сервисами специальных возможностей вместе с основной семантической информацией.
ShareSheet( message = "Choose how to share this photo", modifier = Modifier .fillMaxWidth() .align(Alignment.TopCenter) .semantics { paneTitle = "New bottom sheet" } )
Посмотрите, как в Material 3 используется paneTitle для компонентов.
Компоненты ошибок
Для других типов контента, таких как компоненты, похожие на ошибки, вы можете расширить основную семантическую информацию для пользователей с особыми потребностями.
При определении состояний ошибки вы можете сообщить сервисам специальных возможностей о семантике error и предоставить расширенные сообщения об ошибках.
В этом примере TalkBack читает основной текст ошибки, а затем дополнительное сообщение:
Error( errorText = "Fields cannot be empty", modifier = Modifier .semantics { error("Please add both email and password") } )
Компоненты для отслеживания прогресса
Если у вас есть специальные компоненты, которые отслеживают прогресс, вы можете уведомлять пользователей об изменениях, включая текущее значение, диапазон и размер шага. Для этого можно использовать семантику progressBarRangeInfo. Так специальные возможности будут знать об изменениях и смогут сообщать о них пользователям. Различные вспомогательные технологии также могут иметь уникальные способы намека на увеличение и уменьшение прогрессии.
ProgressInfoBar( modifier = Modifier .semantics { progressBarRangeInfo = ProgressBarRangeInfo( current = progress, range = 0F..1F ) } )
Информация о списке и товарах
В специальных списках и сетках с большим количеством элементов сервисам специальных возможностей может быть полезно получать более подробную информацию, например общее количество элементов и индексов.
Используя семантику collectionInfo и collectionItemInfo для списка и элементов соответственно, сервисы специальных возможностей могут сообщать пользователям, на каком элементе они находятся, в дополнение к текстовой семантической информации.
MilkyWayList( modifier = Modifier .semantics { collectionInfo = CollectionInfo( rowCount = milkyWay.count(), columnCount = 1 ) } ) { milkyWay.forEachIndexed { index, text -> Text( text = text, modifier = Modifier.semantics { collectionItemInfo = CollectionItemInfo(index, 0, 0, 0) } ) } }
Описание состояния
Composable-функция может определять stateDescription для семантики, которую фреймворк Android использует для считывания состояния composable-функции. Например, переключаемая composable-функция может находиться в состоянии "выбрано" или "не выбрано". В некоторых случаях может потребоваться переопределить метки описания состояния по умолчанию, которые использует Compose. Для этого нужно явно указать ярлыки описания состояния перед определением composable-функции как переключаемой:
@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() } ) ) { /* ... */ } }
Специальные действия
Настраиваемые действия можно использовать для более сложных жестов на сенсорном экране, таких как смахивание для закрытия или перетаскивание, поскольку они могут быть сложными для пользователей с нарушениями моторики или другими ограниченными возможностями.
Чтобы сделать жест смахивания для закрытия более доступным, вы можете связать его с настраиваемым действием, передав туда действие закрытия и ярлык:
SwipeToDismissBox( modifier = Modifier.semantics { // Represents the swipe to dismiss for accessibility customActions = listOf( CustomAccessibilityAction( label = "Remove article from list", action = { removeArticle() true } ) ) }, state = rememberSwipeToDismissBoxState(), backgroundContent = {} ) { ArticleListItem() }
Сервис специальных возможностей, например TalkBack, выделяет компонент и сообщает, что в его меню доступны дополнительные действия, в том числе смахивание для удаления:
Ещё один вариант использования настраиваемых действий – длинные списки с элементами, для которых доступно больше действий. Пользователям может быть неудобно перебирать каждое действие для каждого отдельного элемента.
Чтобы улучшить навигацию, что особенно полезно для вспомогательных технологий, основанных на взаимодействии, таких как Switch Access или Voice Access, вы можете использовать настраиваемые действия в контейнере, чтобы переместить действия из индивидуального обхода в отдельное меню действий:
ArticleListItemRow( modifier = Modifier .semantics { customActions = listOf( CustomAccessibilityAction( label = "Open article", action = { openArticle() true } ), CustomAccessibilityAction( label = "Add to bookmarks", action = { addToBookmarks() true } ), ) } ) { Article( modifier = Modifier.clearAndSetSemantics { }, onClick = openArticle, ) BookmarkButton( modifier = Modifier.clearAndSetSemantics { }, onClick = addToBookmarks, ) }
В таких случаях не забудьте вручную очистить семантику исходных дочерних элементов с помощью модификатора clearAndSetSemantics, поскольку вы перемещаете их в настраиваемые действия.
Например, при выборе контейнера в меню Switch Access открывается список доступных вложенных действий:
Дерево семантики
Композиция описывает пользовательский интерфейс приложения и создается при запуске composable-функций. Композиция – это древовидная структура, состоящая из composable-функций, которые описывают пользовательский интерфейс.
Параллельно с деревом Composition существует дерево семантики. Это дерево описывает ваш пользовательский интерфейс альтернативным способом, понятным для сервисов специальных возможностей и фреймворка тестирования. Сервисы специальных возможностей используют это дерево, чтобы описывать приложение пользователям с особыми потребностями. Фреймворк тестирования использует дерево для взаимодействия с приложением и утверждений о нем. Дерево семантики не содержит информации о том, как рисовать composable-функции, но содержит информацию об их семантическом значении.
Если ваше приложение состоит из composable-функций и модификаторов из базовой библиотеки Compose и библиотеки Material, дерево семантики будет заполнено и сгенерировано автоматически. Однако при добавлении собственных низкоуровневых composable-функций семантику нужно указывать вручную. Также могут быть ситуации, когда ваше дерево неверно или не полностью представляет значение элементов на экране, и в этом случае вы можете адаптировать дерево.
Рассмотрим пример composable-функции для календаря:
В этом примере весь календарь реализован как одна низкоуровневая composable-функция с использованием функции Layout и рисованием непосредственно в Canvas.
Если вы не предпримете никаких других действий, сервисы специальных возможностей не получат достаточно информации о содержимом composable-функции и выборе пользователя в календаре. Например, если пользователь нажимает на день, содержащий 17, фреймворк специальных возможностей получает только информацию с описанием для всего элемента управления календарем. В этом случае TalkBack озвучит "Календарь" или "Календарь на апрель", и пользователю придется гадать, какой день выбран. Чтобы сделать эту composable-функцию более доступной, вам нужно будет добавить семантическую информацию вручную.
Объединенное и необъединенное дерево
Как упоминалось ранее, каждая composable-функция в дереве пользовательского интерфейса может иметь ноль или более заданных семантических свойств. Если у composable-функции не заданы семантические свойства, она не включается в семантическое дерево. Таким образом, дерево семантики содержит только узлы, которые действительно имеют семантическое значение. Однако иногда для передачи правильного значения того, что показано на экране, также полезно объединить определенные поддеревья узлов и рассматривать их как одно целое. Таким образом, вы можете рассуждать о наборе узлов в целом, а не иметь дело с каждым узлом-потомком по отдельности. Как правило, каждый узел в этом дереве представляет собой элемент, на котором можно сфокусироваться при использовании специальных возможностей.
Пример такой функции – Button. Вы можете рассуждать о кнопке
как об одном элементе, даже если она может содержать несколько дочерних узлов:
Button(onClick = { /*TODO*/ }) { Icon( imageVector = Icons.Filled.Favorite, contentDescription = null ) Spacer(Modifier.size(ButtonDefaults.IconSpacing)) Text("Like") }
В дереве семантики свойства дочерних элементов кнопки объединяются, и кнопка представляется как один конечный узел в дереве:
Composables и модификаторы могут указывать, что они хотят объединить семантические свойства своих потомков, вызывая Modifier.semantics
(mergeDescendants = true) {}. Если задать для этого свойства значение true, семантические свойства будут объединены. В примере Button composable-функция Button использует модификатор clickable, который включает модификатор semantics. Поэтому дочерние узлы кнопки объединяются.
Ознакомьтесь с документацией по специальным возможностям, чтобы узнать, когда следует изменить поведение объединения в composable-функции.
Это свойство задано для нескольких модификаторов и composable-функций в библиотеках Foundation и Material Compose. Например, модификаторы clickable и toggleable автоматически объединяют свои дочерние элементы. Кроме того, composable-функция ListItem объединит свои дочерние элементы.
Осмотреть дерево
Дерево семантики состоит из двух разных деревьев. Объединенное дерево семантики, в котором дочерние узлы объединяются, если для свойства mergeDescendants задано значение true.
Также существует необъединенное дерево семантики, в котором объединение не применяется, а каждый узел остается нетронутым. Сервисы специальных возможностей используют необъединенное дерево и применяют собственные алгоритмы объединения с учетом свойства mergeDescendants. По умолчанию фреймворк тестирования использует объединенное дерево.
Оба дерева можно проверить с помощью метода printToLog(). По умолчанию, как и в предыдущих примерах, регистрируется объединенное дерево. Чтобы напечатать дерево без объединения, задайте для параметра useUnmergedTree сопоставителя onRoot() значение true:
composeTestRule.onRoot(useUnmergedTree = true).printToLog("MY TAG")
Инспектор макетов позволяет отображать как объединенное, так и необъединенное дерево семантики. Для этого выберите нужный вариант в фильтре просмотра:
Для каждого узла в дереве инспектор макетов показывает на панели свойств как объединенную семантику, так и семантику, заданную для этого узла:
По умолчанию сопоставители в Testing Framework используют объединенное семантическое дерево.
Поэтому вы можете взаимодействовать с элементом Button, сопоставляя текст, показанный внутри него:
composeTestRule.onNodeWithText("Like").performClick()
Чтобы переопределить это поведение, задайте для параметра useUnmergedTree сопоставителей значение true, как в случае с сопоставителем onRoot.
Адаптируйте дерево
Как упоминалось ранее, вы можете переопределить или очистить определенные семантические свойства или изменить поведение слияния дерева. Это особенно важно, когда вы создаете собственные компоненты. Если не задать правильные свойства и поведение слияния, приложение может быть недоступно, а тесты могут вести себя не так, как вы ожидаете. Подробнее о тестировании…
Рекомендуем
- Примечание. Текст ссылки показывается, когда JavaScript отключен.
- Специальные возможности в Compose
- Material Design 2 в Compose
- Тестирование макета Compose