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

Android TV Compose中如何用Dpad实现LazyRow快速滚动

问题背景
  • 开发场景:使用Jetpack Compose实现Android TV端轮播组件
  • 核心约束:聚焦项必须始终为屏幕显示的首个条目
  • 故障现象:Dpad快速滚动时性能差、响应延迟高

现象说明

轮播效果录屏
录屏前半段是逐次点按右键的滚动效果,后半段是长按右键连续滚动到列表末尾的效果:

  • 逐次点按时,按下右键后列表先执行滚动动画,动画结束后焦点才移动到下一个条目,操作有明显滞后感
  • 长按快速滚动时,整体滚动速度远慢于按键触发速度

现有实现代码

自定义横向列表组件

@Composable
private fun CustomLazyRow() {
    val scrollState = rememberLazyListState()

    LazyRow(
        state = scrollState,
        horizontalArrangement = Arrangement.spacedBy(16.dp)
    ) {
        itemsIndexed(
            items = (1..20).toList()
        ) { index, item ->
            var isFocused by remember { mutableStateOf(false) }
            Text(
                text = "Item $item",
                modifier = Modifier
                    .dpadNavigation(scrollState, index)
                    .width(156.dp)
                    .aspectRatio(4 / 3F)
                    .onFocusChanged { isFocused = it.isFocused }
                    .focusable()
                    .border(if (isFocused) 4.dp else Dp.Hairline, Color.Black)
            )
        }
    }
}

Dpad导航Modifier实现

fun Modifier.dpadNavigation(
    scrollState: LazyListState,
    index: Int
) = composed {
    val focusManager = LocalFocusManager.current
    var focusDirectionToMove by remember { mutableStateOf<FocusDirection?>(null) }
    val scope = rememberCoroutineScope()

    onKeyEvent {
        if (it.type == KeyEventType.KeyDown) {
            when (it.nativeKeyEvent.keyCode) {
                KeyEvent.KEYCODE_DPAD_LEFT -> focusDirectionToMove = FocusDirection.Left
                KeyEvent.KEYCODE_DPAD_RIGHT -> focusDirectionToMove = FocusDirection.Right
            }
            if (focusDirectionToMove != null) {
                scope.launch {
                    if (focusDirectionToMove == FocusDirection.Left && index > 0) {
                        // 该写法测试无效:
                        // scope.launch { scrollState.animateScrollToItem(index - 1) }
                        scrollState.animateScrollToItem(index - 1)
                        focusManager.moveFocus(FocusDirection.Left)
                    }
                    if (focusDirectionToMove == FocusDirection.Right) {
                        // 该写法测试无效:
                        // scope.launch { scrollState.animateScrollToItem(index + 1) }
                        scrollState.animateScrollToItem(index + 1)
                        focusManager.moveFocus(FocusDirection.Right)
                    }
                }
            }
        }
        true
    }
}

已排查尝试

最初判断卡顿根因是animateScrollToItem为挂起函数,必须等滚动动画执行完成才会执行后续的moveFocus逻辑,因此尝试将滚动逻辑放到独立协程中与焦点移动并行执行,但测试发现该方案完全失效,列表不会触发滚动。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.03 05:12:36