恢复焦点

当用户使用硬件键盘或 D-pad 导航复杂的界面时,经常会在屏幕的不同部分之间移动,例如从侧边导航栏跳转到内容列表,与某个项目互动,然后再跳回来。借助焦点恢复功能,容器可以记住之前获得焦点的子元素,并在焦点重新进入容器时将焦点恢复到该子元素。

恢复可滚动容器中的焦点

在 Jetpack Compose 中,LazyColumn、LazyRow 和 LazyVerticalGrid 等可滚动容器会自动充当焦点组。如需在延迟列表或网格上启用焦点恢复,请附加 focusRestorer 修饰符:

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

当焦点退出 LazyColumn 时,容器会记录哪个项处于活动状态。 当用户返回到列表时,焦点会恢复到该项,而不是从顶部重新开始。

恢复 Row 和 Column 中的焦点

与延迟布局容器不同,标准非滚动布局容器(例如 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 会遵循默认焦点顺序来确定要聚焦哪个子项。

您可以传递回退 FocusRequester 给 focusRestorer(或在较早的 Compose 版本中传递返回 FocusRequester 的 lambda),以自定义首次进入时哪个子项获得焦点:

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 属性(或在较早的 Compose 版本中设置 enter),将焦点直接指向活动目的地:

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. 在触发导航事件之前,请在容器的 FocusRequester 上调用 saveFocusedChild()。
  3. 当用户返回到相应界面时,请求将初始焦点放在容器上或调用 restoreFocusedChild()。