TV端LazyVerticalGrid焦点遍历如何保持列对齐
LazyVerticalGrid 焦点遍历列对齐异常解决方案

问题描述
使用LazyVerticalGrid默认焦点处理+平滑滚动动画时,长按遥控器向上按钮会出现异常:初始焦点会保持同一列对齐,但滚动加速后焦点会跳转到最后一列。
问题原因
系统在布局中找不到目标项时,会默认选择当前项所在目标行中"最近"的一维项索引;若当前项位于最右侧列,就会触发焦点跳转。此外谷歌已确认该问题,根源是LazyGrid与LazyList共用的BeyondBoundsLayout修饰符仅会预加载第一个屏幕外项,而非整行内容。
临时解决方案
方案1:修复自定义焦点遍历的异常问题
直接引用未加载项的FocusRequester会抛出IllegalStateException,可通过结合LazyGridState判断目标项是否已加载,动态处理焦点请求:
class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val numItems = 100 val cols = 4 val colors = arrayOf( Color.Red, Color(255, 165, 0), Color.Yellow, Color.Green ) setContent { val gridState = rememberLazyGridState() LazyVerticalGrid( state = gridState, columns = GridCells.Fixed(cols) ) { items(numItems) { i -> val targetUpIndex = i - cols val targetDownIndex = i + cols val focusModifier = Modifier.focusProperties { // 处理向上焦点 up = when { targetUpIndex !in 0 until numItems -> FocusRequester.Cancel else -> { val isUpItemVisible = gridState.layoutInfo.visibleItemsInfo.any { it.index == targetUpIndex } if (isUpItemVisible) FocusRequester.Default else FocusRequester.Cancel } } // 处理向下焦点 down = when { targetDownIndex !in 0 until numItems -> FocusRequester.Cancel else -> { val isDownItemVisible = gridState.layoutInfo.visibleItemsInfo.any { it.index == targetDownIndex } if (isDownItemVisible) FocusRequester.Default else FocusRequester.Cancel } } } // 焦点异常时手动滚动对齐列 LaunchedEffect(key1 = gridState.focusedItemIndex) { val currentIndex = gridState.focusedItemIndex ?: return@LaunchedEffect val targetIndex = currentIndex - cols if (targetIndex in 0 until numItems && currentIndex % cols == targetIndex % cols) { gridState.animateScrollToItem(targetIndex) } } ClassicCard( modifier = Modifier .padding(10.dp) .then(focusModifier), image = { Box(modifier = Modifier .fillMaxWidth() .aspectRatio(16/9f) .background(colors[i % 4])) }, title = { Text("Item ${i + 1}") }, onClick = {} ) } } } } }
方案2:扩展预加载范围确保整行加载
通过beyondBoundsLayout修饰符扩大预加载范围,让整行项都处于布局中,确保默认焦点遍历能找到目标项:
// 先计算单个项的高度(可通过onSizeChanged获取实际高度) val itemHeight = 200.dp // 示例值,替换为实际项高度 LazyVerticalGrid( state = rememberLazyGridState(), columns = GridCells.Fixed(4) ) { items(numItems) { i -> ClassicCard( modifier = Modifier.padding(10.dp), image = { Box(modifier = Modifier .fillMaxWidth() .aspectRatio(16/9f) .background(colors[i % 4])) }, title = { Text("Item ${i + 1}") }, onClick = {} ) } }.beyondBoundsLayout { layout( beyondBoundsTop = cols * itemHeight, beyondBoundsBottom = cols * itemHeight ) { placeChildren() } }
官方状态
谷歌已记录该问题,等待官方修复期间可使用上述临时方案绕过。
内容的提问来源于stack exchange,提问作者Eric
相关产品推荐
相关产品推荐

