Android Jetpack Compose TV 返回页面后如何恢复指定位置焦点?
解决方案
要解决返回页面时焦点和滚动状态不匹配的问题,无需为每个元素保存FocusRequester(避免OOM),只需保存滚动状态和最后聚焦的位置,返回后恢复滚动位置并自动聚焦到目标元素即可。
核心步骤
- 保存滚动状态:用
rememberSaveable配合LazyListState.Saver保存TvLazyColumn和每个TvLazyRow的滚动状态,确保导航返回时列表位置不丢失。 - 记录聚焦位置:用
rememberSaveable记录最后获得焦点的元素所在的行和列索引。 - 自动恢复焦点:返回列表页后,先滚动到目标行和列,再通过
FocusRequester让目标元素自动获取焦点(仅为可见元素创建FocusRequester,避免内存占用过高)。
修改后的完整代码
class MainActivity : ComponentActivity() { private val rowItems = (0..20).toList() private val rows = (0..20).toList() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { val navController = rememberNavController() MyAppNavHost(navController = navController) } } @Composable fun List(navController: NavController) { // 保存TvLazyColumn的滚动状态 val columnState = rememberSaveable(saver = LazyListState.Saver) { LazyListState() } // 保存每个TvLazyRow的滚动状态 val rowStates = rememberSaveable { mutableMapOf<Int, LazyListState>() } // 记录最后聚焦的元素位置(行、列) val lastFocused = rememberSaveable { mutableStateOf(Pair(-1, -1)) } TvLazyColumn( modifier = Modifier.fillMaxSize(), state = columnState, verticalArrangement = Arrangement.spacedBy(16.dp), pivotOffsets = PivotOffsets(parentFraction = 0.05f), ) { items(rows.size) { rowPos -> Column() { Text(text = "Row $rowPos") // 获取或创建当前行的滚动状态 val rowState = rowStates.getOrPut(rowPos) { rememberSaveable(saver = LazyListState.Saver) { LazyListState() } } TvLazyRow( modifier = Modifier.height(70.dp), state = rowState, horizontalArrangement = Arrangement.spacedBy(16.dp), pivotOffsets = PivotOffsets(parentFraction = 0.0f), ) { items(rowItems.size) { itemPos -> var color by remember { mutableStateOf(Color.Green) } val itemFocusRequester = remember { FocusRequester() } Box( Modifier .focusRequester(itemFocusRequester) .width(100.dp) .height(50.dp) .onFocusChanged { color = if (it.hasFocus) { // 更新最后聚焦的位置 lastFocused.value = Pair(rowPos, itemPos) Color.Red } else { Color.Green } } .background(color) .clickable { navController.navigate("details") } ) { Text(text = "Item ${itemPos.toString()}", Modifier.align(Alignment.Center)) } // 当返回列表页且当前元素是最后聚焦的目标时,请求焦点 LaunchedEffect(lastFocused.value, navController.currentDestination?.route) { if (lastFocused.value == Pair(rowPos, itemPos) && navController.currentDestination?.route == "list") { // 等待列表滚动稳定后请求焦点 awaitFrame() itemFocusRequester.requestFocus() } } } } } } } // 初始加载或返回时,滚动到最后聚焦的位置 LaunchedEffect(navController.currentBackStackEntry) { val (targetRow, targetItem) = lastFocused.value if (targetRow != -1 && targetItem != -1) { // 先滚动到目标行 columnState.scrollToItem(targetRow) awaitFrame() // 再滚动到目标列 rowStates[targetRow]?.scrollToItem(targetItem) } else { // 初始状态下聚焦到列表 columnState.layoutInfo.visibleItemsInfo.firstOrNull()?.let { columnState.scrollToItem(it.index) } } } } @Composable fun MyAppNavHost( navController: NavHostController = rememberNavController(), startDestination: String = "list" ) { NavHost( navController = navController, startDestination = startDestination ) { composable("details") { Details() } composable("list") { List(navController) } } } @Composable fun Details() { Box( Modifier .background(Color.Blue) .fillMaxSize()) { Text("Second Screen", Modifier.align(Alignment.Center), fontSize = 48.sp) } } }
关键说明
- 滚动状态保存:
rememberSaveable配合LazyListState.Saver确保状态在配置变更或导航时不丢失,且仅保存必要的滚动信息,内存占用低。 - 焦点请求优化:仅为当前可见的列表元素创建
FocusRequester(用remember而非rememberSaveable),不可见元素的FocusRequester会被自动回收,避免OOM。 - 延迟焦点请求:通过
awaitFrame()等待列表滚动完成后再请求焦点,确保目标元素已被渲染,焦点请求生效。
内容的提问来源于stack exchange,提问作者Plorial
相关产品推荐
相关产品推荐

