Цели для групп

Вы можете объединить несколько целей фокусировки с помощью модификатора focusGroup. Группировка позволяет управлять иерархиями фокуса, изолировать логические области пользовательского интерфейса (например, панели инструментов, диалоговые действия или списки карточек) и перехватывать, когда фокус входит в область или выходит из нее.

Концепции фокус-групп: вход, выход и обход по умолчанию

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

  • Вход. Событие, которое происходит, когда фокус переходит с любого элемента за пределами группы фокуса на элемент внутри группы.
  • Выход. Событие, которое происходит, когда фокус покидает последний внутренний элемент группы и переходит на внешний целевой объект.
Диаграмма, на которой показаны переходы Enter и Exit через границу фокус-группы.
Рисунок 1. Фокус-группа с событиями входа и выхода.

Поведение по умолчанию при переходе между элементами

По умолчанию, когда фокус переходит на группу извне:

  1. Вход в группу. Compose автоматически выбирает внутренний дочерний элемент для фокуса. При одномерной навигации (с помощью клавиши Tab) выбирается первый дочерний элемент в порядке визуального отображения. При двумерной навигации (с помощью клавиш со стрелками или d-pad) выбирается дочерний элемент, который геометрически ближе всего к входящему направлению.
  2. Навигация внутри группы. Повторные нажатия клавиши навигации перемещают фокус между элементами, входящими в группу.
  3. Выход из группы. Когда пользователь переходит за пределы последнего целевого объекта фокусировки внутри группы, следующее нажатие клавиши выводит его из группы и перемещает фокус на соседний целевой объект фокусировки за пределами группы.

Настройка перенаправления при входе в фокус

Во многих интерфейсах целевой элемент по умолчанию (например, первый дочерний элемент в порядке появления) не является наиболее логичным или удобным выбором для пользователя.

Назначение и варианты использования

Рассмотрим диалоговое окно подтверждения с кнопкой "Отмена" слева и основной кнопкой "Подтвердить" справа. Когда пользователь переходит вниз к панели кнопок из поля формы:

  • Режим работы по умолчанию. Фокус будет установлен на кнопку "Отмена", поскольку она является первой кнопкой в порядке визуального отображения.
  • Ожидаемое поведение. Поскольку "Подтвердить" – это основное рекомендуемое действие, вы можете захотеть, чтобы фокус попадал непосредственно на "Подтвердить" при входе в группу кнопок из более раннего порядка фокуса, но при этом позволял пользователю нажать Влево, чтобы при необходимости выбрать "Отмена".
По умолчанию фокус устанавливается на группу кнопок.
Рисунок 2. Кнопки Отмена и Подтвердить с целевым объектом по умолчанию.

Другие распространенные варианты использования:

  • При входе в TabRow фокус перемещается непосредственно на выбранную вкладку.
  • Перемещение фокуса непосредственно на элемент управления "Воспроизвести / Приостановить" при входе на панель воспроизведения медиаконтента.

Реализация с помощью onEnter и FocusRequester

Чтобы перенаправить фокус при входе в фокус-группу:

  1. Примените модификатор focusGroup к родительскому контейнеру, содержащему связанные цели фокуса.
  2. Примените модификатор focusProperties к контейнеру и настройте обратный вызов onEnter.
  3. В обратном вызове onEnter (в области действия FocusEnterExitScope) вызовите requestFocus() для FocusRequester, связанного с предпочтительным дочерним элементом по умолчанию, используя focusRequester.

val defaultChildRequester = remember { FocusRequester() }

Row(
    modifier = Modifier
        .focusGroup()
        .focusProperties {
            // Intercept entry into this group and redirect focus to the primary action
            onEnter = {
                defaultChildRequester.requestFocus()
            }
        }
) {
    Button(onClick = { /* Cancel action */ }) {
        Text("Cancel")
    }
    Button(
        onClick = { /* Confirm action */ },
        modifier = Modifier.focusRequester(defaultChildRequester)
    ) {
        Text("Confirm (Default)")
    }
}

Внутри onEnter и onExit вы также можете вызвать cancelFocusChange(), чтобы полностью заблокировать переходы фокуса через границу, или проверить requestedFocusDirection, чтобы настроить маршрутизацию в зависимости от того, использовал ли пользователь Tab, Down или Left.

Контейнеры с прокруткой по умолчанию являются группами фокуса

В Jetpack Compose прокручиваемые контейнеры, такие как LazyRow, LazyColumn и LazyVerticalGrid, автоматически действуют как группы фокуса. Вам не нужно явно применять к ним модификатор focusGroup, чтобы задать обратные вызовы onEnter или onExit.