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
相关产品推荐
相关产品推荐

