You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android TV Compose如何恢复LazyRow的上次焦点位置?

解决方案:保存LazyRow焦点位置并恢复

要实现焦点在不同LazyRow间切换时保存并恢复各自的焦点位置,需按以下步骤处理:

1. 在ViewModel中添加焦点位置存储

修改CatalogViewModel,新增状态用于持久化每个分类对应的最后焦点索引:

class CatalogViewModel : ViewModel() {
    private val _categoriesMap = MutableStateFlow<Map<String, List<App>>>(emptyMap())
    val categoriesMap: StateFlow<Map<String, List<App>>> = _categoriesMap
    
    // 存储每个分类的最后焦点位置:分类名 -> App索引
    private val _lastFocusedIndices = MutableStateFlow<Map<String, Int>>(emptyMap())
    val lastFocusedIndices: StateFlow<Map<String, Int>> = _lastFocusedIndices

    // 更新指定分类的焦点索引
    fun updateLastFocusedIndex(category: String, index: Int) {
        _lastFocusedIndices.value = _lastFocusedIndices.value.toMutableMap().apply {
            put(category, index)
        }
    }

    // 获取指定分类的最后焦点索引,默认返回0
    fun getLastFocusedIndex(category: String): Int {
        return _lastFocusedIndices.value[category] ?: 0
    }
    ...
}

2. 为每个LazyRow添加焦点监听与恢复逻辑

在CatalogBrowser中,为每行创建独立的列表状态与焦点请求器,监听焦点变化并触发恢复逻辑:

@Composable
fun CatalogBrowser(
    viewModel: CatalogViewModel,
    modifier: Modifier = Modifier,
    onCardSelected: (App) -> Unit
) {
    val categoriesMap by viewModel.categoriesMap.collectAsState(emptyMap())
    val lastFocusedIndices by viewModel.lastFocusedIndices.collectAsState(emptyMap())

    LazyColumn(
        modifier = modifier,
        verticalArrangement = Arrangement.spacedBy(16.dp),
        contentPadding = PaddingValues(horizontal = 48.dp, vertical = 32.dp)
    ) {
        itemsIndexed(categoriesMap.entries.toList()) { categoryIndex, entry ->
            val (category, appList) = entry
            // 当前行的列表状态,用于滚动到指定位置
            val rowListState = rememberLazyListState()
            // 行级焦点请求器,监听行的焦点进入/离开
            val rowFocusRequester = remember { FocusRequester() }
            // 当前行保存的最后焦点索引
            val savedIndex = viewModel.getLastFocusedIndex(category)

            Column {
                Text(text = category, modifier = Modifier.padding(start = 12.dp))
                Spacer(modifier = Modifier.height(6.dp))

                LazyRow(
                    modifier = Modifier
                        .height(67.5.dp)
                        .focusRequester(rowFocusRequester)
                        .focusable(), // 让行本身可聚焦,触发焦点切换逻辑
                    horizontalArrangement = Arrangement.spacedBy(10.dp),
                    contentPadding = PaddingValues(horizontal = 10.dp),
                    state = rowListState
                ) {
                    itemsIndexed(appList) { appIndex, app ->
                        val cardRequester = remember { FocusRequester() }

                        AppCard(
                            modifier = Modifier
                                .focusRequester(cardRequester)
                                .padding(vertical = 6.dp)
                                .onFocusChanged { state ->
                                    // 当卡片获得焦点时,更新当前分类的焦点索引
                                    if (state.isFocused) {
                                        viewModel.updateLastFocusedIndex(category, appIndex)
                                    }
                                },
                            app = app,
                            onClick = { onCardSelected(app) }
                        )

                        // 当行获得焦点时,滚动到保存位置并请求对应卡片焦点
                        LaunchedEffect(rowFocusRequester, savedIndex) {
                            val rowGotFocus = rowFocusRequester.requestFocus()
                            if (rowGotFocus && savedIndex < appList.size) {
                                // 滚动到目标位置
                                rowListState.scrollToItem(savedIndex)
                                // 延迟请求焦点,确保滚动完成
                                delay(50)
                                cardRequester.requestFocus()
                            }
                        }
                    }
                }
            }
        }
    }
}

关键逻辑说明

  • 状态持久化:通过ViewModel中的lastFocusedIndices保存每个分类的焦点位置,避免Compose重组导致状态丢失。
  • 焦点监听:利用onFocusChanged监听每个AppCard的焦点状态,一旦聚焦就更新对应分类的索引。
  • 焦点恢复:当LazyRow获得焦点时,先通过LazyListState滚动到保存的索引位置,再延迟请求对应卡片的焦点,确保滚动完成后焦点定位准确。
  • 行级焦点处理:给LazyRow添加focusable()和focusRequester,确保垂直切换焦点时能触发行的焦点事件,进而启动恢复逻辑。

内容的提问来源于stack exchange,提问作者Joseph Hsiao

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 14:13:11