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

Android Compose中LazyVerticalGrid滑动卡顿问题排查

问题描述

开发的列表滑动卡顿明显,该问题在真机、模拟器环境下均存在,需要定位代码实现中的错误。

已排查结论
  • 移除LazyGridScope作用域内的onEvent代码行后,列表滑动流畅度出现明显提升。
关联实现代码

LevelsScreen 实现

@Composable
fun LevelsScreen(
    navController: NavController,
    levelsViewModel: LevelsViewModel = hiltViewModel()
){

    val gameInfo by levelsViewModel.getGameInfo.observeAsState()

    val uiState = levelsViewModel.uiState
    val listScrollState = rememberLazyGridState()
    val coroutineScope = rememberCoroutineScope()

    val levelsList = getLevelsList()

    LazyVerticalGrid(
        contentPadding = PaddingValues(
            start = 12.dp,
            top = 16.dp,
            end = 12.dp,
            bottom = 16.dp
        ),
        columns = GridCells.Fixed(2),
        state = listScrollState

    ){
        items(levelsList.size) {item ->
            CustomListItem(
                levelsListIndex = levelsList[item],
                onCardClick = {
                    levelsViewModel.updateCurrentLevel(item+1)
                    navController.navigate("question")
                },
                lastLevel = gameInfo?.lastLevel ?: 0
            )
        }

        levelsViewModel.onEvent(LevelsEvents.OnScrollStateChange(listScrollState.firstVisibleItemIndex))

        coroutineScope.launch {
            listScrollState.scrollToItem(uiState.value.scrollState)
        }
    }
}

对应ViewModel实现

private val _uiState = mutableStateOf(LevelsState())
val uiState: State<LevelsState> get() = _uiState

fun onEvent(event: LevelsEvents){
    when (event){
        is LevelsEvents.OnScrollStateChange -> {
            _uiState.value = _uiState.value.copy(scrollState = event.scrollPosition)
        }
    }
}
卡顿根因

你把状态更新、协程滚动逻辑直接写在了LazyVerticalGrid的内容lambda作用域内,触发了滑动过程中的无限重组循环:

  1. LazyVerticalGrid的content lambda会在列表每一帧滑动、每次重组时反复执行,你在这个作用域内直接调用onEvent更新uiState的滚动位置,状态更新会立刻触发当前页面重组,重组又会再次执行lambda调用onEvent更新状态,形成无节流的无限重组循环,主线程被持续占满导致卡顿。
  2. 你没有使用Compose的副作用API,直接在组合过程中启动协程调用scrollToItem,每次重组都会启动新的协程执行滚动操作,和上述状态更新逻辑形成死循环:滑动→更新状态触发重组→执行滚动→再次触发状态更新,进一步占用主线程资源。
修复方案
  1. 所有涉及状态监听、副作用执行的逻辑不要直接塞在组合函数的执行路径里,也不要放在LazyVerticalGrid的content lambda内,要使用Compose提供的副作用API,放在和LazyVerticalGrid平级的位置。
  2. 滚动位置恢复逻辑仅需要在首次进入页面、绑定的滚动状态变化时执行,不要每次重组都触发。
  3. 滚动位置保存逻辑增加去重判断,仅当可见项索引真的发生变化时再更新状态,避免每帧重复写状态。

参考修正代码:

@Composable
fun LevelsScreen(
    navController: NavController,
    levelsViewModel: LevelsViewModel = hiltViewModel()
){

    val gameInfo by levelsViewModel.getGameInfo.observeAsState()

    val uiState = levelsViewModel.uiState
    val listScrollState = rememberLazyGridState()
    val levelsList = getLevelsList()

    // 恢复滚动位置:仅当初始保存的滚动位置变化时执行
    LaunchedEffect(uiState.value.scrollState) {
        listScrollState.scrollToItem(uiState.value.scrollState)
    }

    // 监听滚动位置:去重后再更新状态保存
    LaunchedEffect(listScrollState) {
        snapshotFlow { listScrollState.firstVisibleItemIndex }
            .distinctUntilChanged()
            .collect { targetIndex ->
                levelsViewModel.onEvent(LevelsEvents.OnScrollStateChange(targetIndex))
            }
    }

    LazyVerticalGrid(
        contentPadding = PaddingValues(
            start = 12.dp,
            top = 16.dp,
            end = 12.dp,
            bottom = 16.dp
        ),
        columns = GridCells.Fixed(2),
        state = listScrollState

    ){
        items(levelsList.size) {item ->
            CustomListItem(
                levelsListIndex = levelsList[item],
                onCardClick = {
                    levelsViewModel.updateCurrentLevel(item+1)
                    navController.navigate("question")
                },
                lastLevel = gameInfo?.lastLevel ?: 0
            )
        }
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.02 21:57:23