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

Jetpack Compose Android:嵌套LazyRow失焦时如何记忆聚焦元素位置

实现嵌套LazyColumn+LazyRow的焦点位置记忆(类似YouTube TV)

完全可行,你之前遇到的问题核心是用单个remember变量存储索引会被所有LazyRow共享,导致切换行时状态混乱。解决思路是给每一行LazyRow分配独立的状态容器,单独存储各自的聚焦索引和滚动状态。

核心方案

  1. 独立状态存储:用映射结构(比如MutableStateMap)分别记录每一行的聚焦元素索引和滚动状态,键为行的索引,值为对应行的状态。
  2. 状态持久化:用rememberSaveable存储聚焦索引,避免配置变更(比如屏幕旋转)导致状态丢失。
  3. 自动恢复逻辑:当LazyRow重新获得焦点时,根据存储的索引自动滚动到目标位置并恢复焦点。

代码示例

@Composable
fun NestedLazyListsWithFocusRestore() {
    // 模拟数据源:外层LazyColumn的行数据,每行包含多个子项
    val rowDataList = remember {
        List(5) { rowIndex ->
            List(20) { itemIndex -> "Row $rowIndex - Item $itemIndex" }
        }
    }

    // 存储每行的聚焦元素索引:key=行索引,value=该行聚焦的元素索引
    val focusedIndices = rememberSaveable { mutableStateMapOf<Int, Int>() }
    // 存储每行LazyRow的滚动状态,确保每行独立控制
    val rowScrollStates = remember { mutableStateMapOf<Int, LazyListState>() }
    // 存储每个元素的焦点请求器,用于主动恢复焦点
    val focusRequesters = remember { mutableStateMapOf<Pair<Int, Int>, FocusRequester>() }

    LazyColumn(modifier = Modifier.fillMaxSize()) {
        itemsIndexed(rowDataList) { rowIndex, rowItems ->
            LazyRow(
                state = rowScrollStates.getOrPut(rowIndex) { rememberLazyListState() },
                modifier = Modifier
                    .fillMaxWidth()
                    .height(180.dp)
                    .padding(vertical = 8.dp)
                    .focusable()
            ) {
                itemsIndexed(rowItems, key = { itemIndex -> "$rowIndex-$itemIndex" }) { itemIndex, itemText ->
                    val focusRequester = focusRequesters.getOrPut(rowIndex to itemIndex) { FocusRequester() }
                    val isFocused = remember { mutableStateOf(false) }

                    Box(
                        modifier = Modifier
                            .size(150.dp, 160.dp)
                            .padding(horizontal = 4.dp)
                            .focusRequester(focusRequester)
                            .focusable()
                            .onFocusChanged {
                                isFocused.value = it.isFocused
                                if (it.isFocused) {
                                    // 记录当前行的聚焦元素索引
                                    focusedIndices[rowIndex] = itemIndex
                                }
                            }
                            .background(if (isFocused.value) Color.DarkGray else Color.LightGray),
                        contentAlignment = Alignment.Center
                    ) {
                        Text(text = itemText, color = Color.White)
                    }
                }
            }

            // 当行加载完成后,恢复之前的聚焦位置
            LaunchedEffect(rowIndex) {
                val savedIndex = focusedIndices[rowIndex] ?: 0
                // 滚动到保存的位置
                rowScrollStates[rowIndex]?.scrollToItem(savedIndex)
                // 主动请求焦点到目标元素
                focusRequesters[rowIndex to savedIndex]?.requestFocus()
            }
        }
    }
}

关键细节说明

  • 状态隔离:通过MutableStateMap给每一行分配独立的LazyListState和聚焦索引,彻底避免不同行之间的状态干扰。
  • 焦点主动恢复:使用FocusRequester在LazyRow加载完成后,主动将焦点定位到之前保存的元素,实现和YouTube TV一致的体验。
  • 配置变更兼容:rememberSaveable会自动序列化存储的索引,确保屏幕旋转、进程重启后状态不丢失。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.03 14:10:26