Изменить порядок обхода

Порядок обхода — это порядок, в котором службы специальных возможностей перемещаются по элементам пользовательского интерфейса. В приложении Compose элементы располагаются в ожидаемом порядке чтения: обычно сначала слева направо, затем сверху вниз. Однако в некоторых сценариях Compose могут потребоваться дополнительные подсказки для определения правильного порядка чтения.

isTraversalGroup и traversalIndex — это семантические свойства, которые позволяют влиять на порядок обхода служб доступности в сценариях, где алгоритма сортировки Compose по умолчанию недостаточно. isTraversalGroup определяет семантически важные группы, которые нуждаются в настройке, а traversalIndex регулирует порядок отдельных элементов внутри этих групп. Вы можете использовать только isTraversalGroup , чтобы указать, что все элементы внутри группы должны быть выбраны вместе, или с помощью traversalIndex для дальнейшей настройки.

Используйте isTraversalGroup и traversalIndex в своем приложении, чтобы контролировать порядок обхода программы чтения с экрана.

Группировать элементы для обхода

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
        )
    }
}

Код выдает вывод, аналогичный следующему:

Макет с двумя столбцами текста, в левом столбце написано «Это   предложение находится в левом столбце», а правый столбец гласит: «Это предложение находится справа».
Рисунок 1. Макет с двумя предложениями (одно в левом столбце и одно в правом столбце).

Поскольку семантика не задана, по умолчанию программа чтения с экрана перемещает элементы слева направо и сверху вниз. Из-за этого по умолчанию 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 для Column не будет иметь никакого эффекта, если только для столбца также не установлен isTraversalGroup .

В следующем примере показано, как можно использовать traversalIndex и isTraversalGroup вместе.

Циферблат — это распространенный сценарий, в котором стандартный порядок обхода не работает. Примером в этом разделе является средство выбора времени, где пользователь может просматривать цифры на циферблате и выбирать цифры для часовых и минутных интервалов.

Циферблат с указателем времени над ним.
Рисунок 2. Изображение циферблата.

В следующем упрощенном фрагменте есть 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 .

Рекомендации по API

При использовании API обхода учитывайте следующее:

  • isTraversalGroup = true должно быть установлено для родительского элемента, содержащего сгруппированные элементы.
  • traversalIndex должен быть установлен для дочернего компонента, который содержит семантику и будет выбран службами доступности.
  • Убедитесь, что все элементы, которые вы исследуете, находятся на одном уровне zIndex , поскольку это также влияет на семантику и порядок обхода.
  • Убедитесь, что никакая семантика не объединена без необходимости, так как это может повлиять на то, к каким компонентам будут применяться индексы обхода.
{% дословно %} {% дословно %}