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

