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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 15:29:50