По умолчанию поведение средства чтения с экрана специальных возможностей в приложении Compose реализуется в ожидаемом порядке чтения, который обычно происходит слева направо, а затем сверху вниз. Однако существуют некоторые типы макетов приложений, в которых алгоритм не может определить фактический порядок чтения без дополнительных подсказок. В приложениях на основе представлений такие проблемы можно исправить с помощью свойств traversalBefore
и traversalAfter
. Начиная с Compose 1.5 , Compose предоставляет столь же гибкий API, но с новой концептуальной моделью.
isTraversalGroup
и traversalIndex
— это семантические свойства, которые позволяют управлять доступностью и порядком фокуса TalkBack в сценариях, где алгоритм сортировки по умолчанию не подходит. isTraversalGroup
идентифицирует семантически важные группы, а traversalIndex
регулирует порядок отдельных элементов внутри этих групп. Вы можете использовать isTraversalGroup
отдельно или с traversalIndex
для дальнейшей настройки.
Используйте isTraversalGroup
и traversalIndex
в своем приложении, чтобы контролировать порядок обхода программы чтения с экрана.
Группировать элементы с помощью isTraversalGroup
isTraversalGroup
— это логическое свойство, которое определяет, является ли узел семантики группой обхода. Этот тип узла является узлом, функция которого состоит в том, чтобы служить границей или границей при организации дочерних элементов узла.
Установка isTraversalGroup = true
для узла означает, что все дочерние элементы этого узла посещаются перед переходом к другим элементам. Вы можете установить isTraversalGroup
на узлах, не доступных для чтения с экрана, таких как столбцы, строки или поля.
В следующем примере используется isTraversalGroup
. Он излучает четыре текстовых элемента. Два левых элемента принадлежат одному элементу CardBox
, а два правых — другому элементу CardBox
:
// CardBox() function takes in top and bottom sample text. @Composable fun CardBox( topSampleText: String, bottomSampleText: String, modifier: Modifier = Modifier ) { Box(modifier) { Column { Text(topSampleText) Text(bottomSampleText) } } } @Composable fun TraversalGroupDemo() { val topSampleText1 = "This sentence is in " val bottomSampleText1 = "the left column." val topSampleText2 = "This sentence is " val bottomSampleText2 = "on the right." Row { CardBox( topSampleText1, bottomSampleText1 ) CardBox( topSampleText2, bottomSampleText2 ) } }
Код выдает вывод, аналогичный следующему:
![Макет с двумя столбцами текста: в левом столбце написано «Это предложение находится в левом столбце», а в правом столбце — «Это предложение находится справа».](https://developer.android.com/static/develop/ui/compose/images/a11y-istraversalgroup-example.png?authuser=0000&hl=ru)
Поскольку семантика не задана, по умолчанию программа чтения с экрана перемещает элементы слева направо и сверху вниз. Из-за этого по умолчанию TalkBack зачитывает фрагменты предложений в неправильном порядке:
«Это предложение находится в» → «Это предложение» → «левый столбец». → «справа».
Чтобы правильно упорядочить фрагменты, измените исходный фрагмент, установив isTraversalGroup
значение true
:
@Composable fun TraversalGroupDemo2() { val topSampleText1 = "This sentence is in " val bottomSampleText1 = "the left column." val topSampleText2 = "This sentence is" val bottomSampleText2 = "on the right." Row { CardBox( // 1, topSampleText1, bottomSampleText1, Modifier.semantics { isTraversalGroup = true } ) CardBox( // 2, topSampleText2, bottomSampleText2, Modifier.semantics { isTraversalGroup = true } ) } }
Поскольку isTraversalGroup
устанавливается отдельно для каждого CardBox
, границы CardBox
применяются при сортировке их элементов. В этом случае сначала считывается левый CardBox
, а затем правый CardBox
.
Теперь TalkBack зачитывает фрагменты предложения в правильном порядке:
«Это предложение находится в» → «левом столбце». → «Это предложение» → «справа».
Дальнейшая настройка порядка обхода
traversalIndex
— это свойство с плавающей запятой, которое позволяет настроить порядок обхода TalkBack. Если группировки элементов недостаточно для правильной работы TalkBack, используйте traversalIndex
в сочетании с isTraversalGroup
для дальнейшей настройки порядка чтения с экрана.
Свойство traversalIndex
имеет следующие характеристики:
- Элементы с более низкими значениями
traversalIndex
имеют приоритет в первую очередь. - Может быть положительным или отрицательным.
- Значение по умолчанию —
0f
. - Влияет только на узлы, доступные для чтения с экрана, такие как экранные элементы, такие как текст или кнопки. Например, установка только
traversalIndex
для столбца не будет иметь никакого эффекта, если только в столбце также не установленisTraversalGroup
.
В следующем примере показано, как можно использовать traversalIndex
и isTraversalGroup
вместе.
Пример: перемещение циферблата
Циферблат — это распространенный сценарий, в котором стандартный порядок обхода не работает. Примером в этом разделе является средство выбора времени, где пользователь может просматривать цифры на циферблате и выбирать цифры для часовых и минутных интервалов.
![Циферблат с указателем времени над ним.](https://developer.android.com/static/develop/ui/compose/images/a11y-clock-face.png?authuser=0000&hl=ru)
В следующем упрощенном фрагменте есть CircularLayout
, в котором нарисованы 12 чисел, начиная с 12 и двигаясь по кругу по часовой стрелке:
@Composable fun ClockFaceDemo() { CircularLayout { repeat(12) { hour -> ClockText(hour) } } } @Composable private fun ClockText(value: Int) { Box(modifier = Modifier) { Text((if (value == 0) 12 else value).toString()) } }
Поскольку циферблат не читается логически при стандартном порядке слева направо и сверху вниз, TalkBack считывает цифры не по порядку. Чтобы исправить это, используйте увеличивающееся значение счетчика, как показано в следующем фрагменте:
@Composable fun ClockFaceDemo() { CircularLayout(Modifier.semantics { isTraversalGroup = true }) { repeat(12) { hour -> ClockText(hour) } } } @Composable private fun ClockText(value: Int) { Box(modifier = Modifier.semantics { this.traversalIndex = value.toFloat() }) { Text((if (value == 0) 12 else value).toString()) } }
Чтобы правильно установить порядок обхода, сначала сделайте CircularLayout
группой обхода и установите isTraversalGroup = true
. Затем, когда каждый текст часов рисуется на макете, установите для соответствующего traversalIndex
значение счетчика.
Поскольку значение счетчика постоянно увеличивается, traversalIndex
каждого значения часов увеличивается по мере добавления чисел на экран: значение часов 0 имеет traversalIndex
, равный 0, а значение часов 1 имеет traversalIndex
, равный 1. Таким образом, порядок, в котором TalkBack читает их в наборе. Теперь числа внутри CircularLayout
читаются в ожидаемом порядке.
Поскольку заданные traversalIndexes
относятся только к другим индексам в той же группе, остальная часть порядка экрана сохраняется. Другими словами, семантические изменения, показанные в предыдущем фрагменте кода, изменяют только порядок внутри циферблата, для которого установлено значение isTraversalGroup = true
.
Обратите внимание, что без установки для семантики CircularLayout's
значения isTraversalGroup = true
изменения traversalIndex
по-прежнему применяются. Однако без связывающего их CircularLayout
двенадцать цифр циферблата считываются последними, после посещения всех остальных элементов на экране. Это происходит потому, что все остальные элементы имеют traversalIndex
по умолчанию, равный 0f
, а текстовые элементы часов считываются после всех остальных элементов 0f
.
Пример: настройка порядка обхода для кнопки плавающего действия
В этом примере traversalIndex
и isTraversalGroup
управляют порядком обхода кнопки плавающего действия Material Design (FAB). Основой этого примера является следующая компоновка:
![Макет с верхней панелью приложения, образцом текста, плавающей кнопкой действия и нижней панелью приложения.](https://developer.android.com/static/develop/ui/compose/images/a11y-fab-example.png?authuser=0000&hl=ru)
По умолчанию макет в этом примере имеет следующий порядок TalkBack:
Верхняя панель приложений → Примеры текстов от 0 до 6 → кнопка плавающего действия (FAB) → Нижняя панель приложений.
Возможно, вы захотите, чтобы программа чтения с экрана сначала сосредоточилась на FAB. Чтобы установить traversalIndex
для элемента Material, такого как FAB, выполните следующие действия:
@Composable fun FloatingBox() { Box(modifier = Modifier.semantics { isTraversalGroup = true; traversalIndex = -1f }) { FloatingActionButton(onClick = {}) { Icon(imageVector = Icons.Default.Add, contentDescription = "fab icon") } } }
В этом фрагменте создание поля с isTraversalGroup
, для которого установлено значение true
, и установка traversalIndex
для того же поля ( -1f
ниже значения по умолчанию, равного 0f
), означает, что плавающее поле появляется перед всеми другими элементами на экране.
Затем вы можете поместить плавающий блок и другие элементы в каркас, реализующий макет Material Design:
@OptIn(ExperimentalMaterial3Api::class) @Composable fun ColumnWithFABFirstDemo() { Scaffold( topBar = { TopAppBar(title = { Text("Top App Bar") }) }, floatingActionButtonPosition = FabPosition.End, floatingActionButton = { FloatingBox() }, content = { padding -> ContentColumn(padding = padding) }, bottomBar = { BottomAppBar { Text("Bottom App Bar") } } ) }
TalkBack взаимодействует с элементами в следующем порядке:
FAB → Верхняя панель приложений → Примеры текстов от 0 до 6 → Нижняя панель приложений.
Дополнительные ресурсы
- Доступность : основные концепции и методы, общие для всей разработки приложений для Android.
- Создание доступных приложений : основные шаги, которые вы можете предпринять, чтобы сделать ваше приложение более доступным.
- Принципы улучшения доступности приложения : ключевые принципы, которые следует учитывать при работе над тем, чтобы сделать ваше приложение более доступным.
- Тестирование доступности : принципы и инструменты тестирования доступности Android.