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

Android TV上Jetpack Compose LazyColumns的D-Pad导航焦点跳变异常求助

Android TV Jetpack Compose LazyColumn D-Pad焦点跳变问题解决

问题描述

在Android TV上使用Jetpack Compose的LazyColumn时,D-Pad上下导航会出现焦点异常跳变的情况,哪怕换成自带焦点状态的普通按钮,问题依然存在。复现代码里左侧LazyColumn在焦点变化时手动处理滚动,导致按顺序导航时焦点随机跳转。

问题根源

你在onFocusChanged回调里直接调用scrollToItem,这个操作会触发LazyColumn的布局重排,而Compose的焦点系统在布局变动时会重新计算焦点目标,两者时序冲突,干扰了D-Pad正常的焦点遍历逻辑,最终导致焦点跳变。

解决方案

1. 调整滚动触发时机与方式

把滚动逻辑从onFocusChanged里移出来,用LaunchedEffect监听焦点状态变化,同时用平滑滚动替代立即滚动,避免打断焦点系统的正常流程。

2. 修复后的代码示例

@Composable
fun Greeting(listItems: List<Int>) {
    var currentItem by remember { mutableStateOf("None") }
    val scrollState = rememberLazyListState()

    Row {
        Text(
            text = "Current Focus = $currentItem",
            modifier = Modifier.weight(1f)
        )

        Column(Modifier.weight(1f)) {
            Text(text = "Fixed Focus & Scroll")
            LazyColumn(state = scrollState) {
                itemsIndexed(listItems) { index, item ->
                    val interactionSource = remember { MutableInteractionSource() }
                    val isFocused by interactionSource.collectIsFocusedAsState()

                    // 用LaunchedEffect监听焦点变化,触发平滑滚动
                    LaunchedEffect(isFocused) {
                        if (isFocused) {
                            scrollState.animateScrollToItem(
                                index = index,
                                // 让焦点项保持在可视区域偏上位置,避免频繁滚动
                                scrollOffset = scrollState.layoutInfo.viewportSize.height / 3
                            )
                        }
                    }

                    Item(
                        item = item,
                        onFocused = { currentItem = "Left $item" },
                        interactionSource = interactionSource
                    )
                }
            }
        }

        Column(Modifier.weight(1f)) {
            Text(text = "Without focus changed")
            LazyColumn {
                items(listItems) { item ->
                    Item(
                        item,
                        { currentItem = "Right $item" }
                    )
                }
            }
        }
    }
}

@Composable
fun Item(
    item: Int,
    onFocused: () -> Unit,
    modifier: Modifier = Modifier,
    interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }
) {
    val focused by interactionSource.collectIsFocusedAsState()

    Text(
        text = "$item",
        modifier = modifier
            .focusable(interactionSource = interactionSource)
            .onFocusChanged { state ->
                if (state.isFocused) {
                    onFocused()
                }
            }
            .padding(8.dp)
            .border(if (focused) 4.dp else 0.dp, MaterialTheme.colors.primary)
            .padding(8.dp)
    )
}

关键修改点

  • 用LaunchedEffect替代onFocusChanged中的直接滚动,确保滚动操作和焦点系统的处理时序不冲突
  • 替换scrollToItem为animateScrollToItem,滚动更平滑,不会强制打断布局
  • 添加滚动偏移量,让焦点项停在可视区域内的合理位置,减少不必要的滚动触发

额外提示

  • 尽量使用最新稳定版的Compose依赖,旧版本可能存在TV焦点系统的已知bug
  • 避免在焦点变化回调里执行会触发重组或布局的操作,这类逻辑优先放在LaunchedEffect中
  • TV应用建议使用官方的TvMaterial组件库,组件已经针对D-Pad导航做了适配优化

内容的提问来源于stack exchange,提问作者dst

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.18 11:01:01