如何让Android TV的LazyVerticalGrid支持DPad逐项滚动?
解决Compose LazyVerticalGrid电视端DPad逐项滚动问题
问题背景
TVLazyVerticalGrid已被废弃,改用LazyVerticalGrid后,无法通过电视遥控器DPad实现网格列表的逐项滚动,期望实现网格项按上下左右方向逐个导航的效果。
问题分析
原代码存在以下问题导致DPad导航异常:
- 所有网格项共用同一个
isItemInFocus状态,焦点状态混乱,无法正确识别当前聚焦项 - 仅设置
focusable()但未适配电视端焦点导航逻辑,LazyVerticalGrid默认滚动行为不支持逐项导航 - 未在焦点切换时触发滚动,导致聚焦项可能不在可视区域内
解决方案
- 为每个网格项创建独立的焦点状态和
FocusRequester,避免状态冲突 - 通过
focusProperties自定义DPad方向的焦点导航规则,确保焦点按网格行列顺序切换 - 在项获得焦点时,自动滚动到对应项的可视位置,实现逐项滚动的视觉效果
修改后的代码
@Composable fun AnimalsGrid( animalUiModelList: ImmutableList<AnimalUiModel>, isLoading: Boolean, onLastAnimalVisible: () -> Unit, modifier: Modifier = Modifier, ) { val lazyGridState = rememberLazyGridState() val coroutineScope = rememberCoroutineScope() val itemWidth = 468.dp / 2.5f val itemHeight = 225.dp / 2.5f val itemSpacing = 10.dp LazyVerticalGrid( columns = GridCells.Fixed(3), state = lazyGridState, verticalArrangement = Arrangement.spacedBy(itemSpacing), horizontalArrangement = Arrangement.spacedBy(itemSpacing), userScrollEnabled = true, modifier = modifier .clip(RoundedCornerShape(12.dp)) .background(color = MaterialTheme.colorScheme.surface) .padding(16.dp) .fillMaxSize() ) { items(animalUiModelList.size) { index -> val animal = animalUiModelList[index] val focusRequester = remember { FocusRequester() } var isFocused by remember { mutableStateOf(false) } // 计算当前项的行列位置 val row = index / 3 val column = index % 3 Animal( animalModel = animal, modifier = Modifier .width(itemWidth) .height(itemHeight) .focusRequester(focusRequester) .focusable() .focusProperties { // 定义上下左右方向的焦点导航规则 up = if (row > 0) FocusRequester.Default else FocusRequester.Cancel down = if (row < (animalUiModelList.size - 1) / 3) FocusRequester.Default else FocusRequester.Cancel left = if (column > 0) FocusRequester.Default else FocusRequester.Cancel right = if (column < 2) FocusRequester.Default else FocusRequester.Cancel } .onFocusChanged { focusState -> isFocused = focusState.isFocused if (isFocused) { // 聚焦时自动滚动到当前项的可视区域 coroutineScope.launch { lazyGridState.animateScrollToItem(index) } } } .background(if (isFocused) Color.Green else Color.Transparent) ) } if (isLoading) { item { CircularProgressIndicator( modifier = Modifier .padding(20.dp) .size(24.dp) .align(Alignment.Center), color = MaterialTheme.colorScheme.onSurface, ) } } } }
关键说明
- 每个网格项通过
remember初始化独立的FocusRequester和焦点状态,确保焦点状态互不干扰 focusProperties定义了DPad各方向的焦点导航逻辑,让焦点在网格中按行列顺序切换- 利用
rememberCoroutineScope和animateScrollToItem,在项获得焦点时平滑滚动到目标位置,保证聚焦项始终可见 - 确保
Animal组件内部没有抢占焦点的元素,避免焦点被内部组件拦截
内容的提问来源于stack exchange,提问作者Katarina Zivkovic
相关产品推荐
相关产品推荐

