Как восстановить фокус

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

Восстановление фокуса в прокручиваемых контейнерах

В Jetpack Compose прокручиваемые контейнеры, такие как LazyColumn, LazyRow и LazyVerticalGrid, автоматически действуют как группы фокуса. Чтобы включить восстановление фокуса в ленивом списке или сетке, прикрепите модификатор focusRestorer:

LazyColumn(
    modifier = Modifier.focusRestorer()
) {
    items(items) { item ->
        Button(onClick = { /* Handle click */ }) {
            Text(item)
        }
    }
}

Когда фокус покидает LazyColumn, контейнер записывает, какой элемент был активен. Когда пользователь возвращается к списку, фокус восстанавливается на этом элементе, а не возвращается в начало.

Восстановить фокус в строке и столбце

В отличие от контейнеров с отложенной загрузкой, стандартные контейнеры без прокрутки, такие как Row и Column, по умолчанию не устанавливают границу фокуса. Поскольку они не перехватывают события входа в фокус самостоятельно, одного применения модификатора focusRestorer недостаточно.

Чтобы включить восстановление фокуса в Row или Column, объедините focusGroup с focusRestorer:

Row(
    modifier = Modifier
        .focusGroup()
        .focusRestorer()
) {
    Button(onClick = { /* Action 1 */ }) {
        Text("First item")
    }
    Button(onClick = { /* Action 2 */ }) {
        Text("Second item")
    }
    Button(onClick = { /* Action 3 */ }) {
        Text("Third item")
    }
}

Как указать резервную цель фокусировки

Когда фокус впервые попадает в контейнер, дочерний элемент, который ранее был в фокусе, отсутствует. Compose follows the default focus order to determine which child to focuses.

Вы можете настроить, какой дочерний элемент будет получать фокус при первом входе, передав резервный FocusRequester в focusRestorer (или лямбда-выражение, возвращающее FocusRequester в более ранних версиях Compose):

val firstItemRequester = remember { FocusRequester() }

LazyColumn(
    modifier = Modifier.focusRestorer(firstItemRequester)
) {
    itemsIndexed(items) { index, item ->
        val itemModifier = if (index == 0) {
            Modifier.focusRequester(firstItemRequester)
        } else {
            Modifier
        }
        Button(
            onClick = { /* Handle click */ },
            modifier = itemModifier
        ) {
            Text(item)
        }
    }
}

Если дочерний элемент не был выбран ранее (или если восстановить фокус не удалось), Compose перемещает фокус на связанный резервный целевой объект.

В компонентах навигации, таких как NavigationRail, NavigationBar, NavigationDrawer или TabRow, пользователи ожидают, что фокус будет установлен на выбранном в данный момент пункте назначения при входе в компонент из области контента.

Вы можете направить фокус на активный пункт назначения, объединив Modifier.focusGroup() с focusProperties и задав свойство onEnter (или enter в более ранних версиях Compose):

val (homeRequester, searchRequester, settingsRequester) = remember { FocusRequester.createRefs() }

val selectedRequester = when (currentDestination) {
    "home" -> homeRequester
    "search" -> searchRequester
    "settings" -> settingsRequester
    else -> homeRequester
}

NavigationRail(
    modifier = modifier
        .focusProperties {
            // Redirect focus to the selected item when focus enters the navigation rail
            onEnter = { selectedRequester.requestFocus() }
        }
        .focusGroup()
) {
    NavigationRailItem(
        selected = currentDestination == "home",
        onClick = { onNavigate("home") },
        icon = { Icon(Icons.Default.Home, contentDescription = "Home") },
        label = { Text("Home") },
        modifier = Modifier.focusRequester(homeRequester)
    )
    NavigationRailItem(
        selected = currentDestination == "search",
        onClick = { onNavigate("search") },
        icon = { Icon(Icons.Default.Search, contentDescription = "Search") },
        label = { Text("Search") },
        modifier = Modifier.focusRequester(searchRequester)
    )
    NavigationRailItem(
        selected = currentDestination == "settings",
        onClick = { onNavigate("settings") },
        icon = { Icon(Icons.Default.Settings, contentDescription = "Settings") },
        label = { Text("Settings") },
        modifier = Modifier.focusRequester(settingsRequester)
    )
}

Восстанавливать фокус при переходе между экранами

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

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

  1. Свяжите контейнер с FocusRequester.
  2. Прежде чем активировать событие навигации, вызовите saveFocusedChild() для FocusRequester контейнера.
  3. Когда пользователь возвращается на экран, запросите начальный фокус на контейнере или вызовите restoreFocusedChild().