Jetpack Compose使用LazyColumn实现垂直列表滑动卡顿不流畅问题求助
Jetpack Compose LazyColumn滑动卡顿问题排查与修复
核心问题点
1. 图片加载逻辑存在严重性能缺陷
自定义的loadPicture函数有多处问题:- Glide请求未包裹在
LaunchedEffect中,每次Composable重组都会重复发起图片请求,滑动时列表项频繁触发重组,会导致大量重复的图片加载任务抢占主线程资源 remember未绑定url作为key,列表项复用时不会重置图片状态,会出现图片错位、重复请求的问题- 没有管理Glide请求的生命周期,滑出屏幕的列表项的图片请求不会被取消,冗余请求占用系统资源
- 未对Bitmap做尺寸适配,直接加载原图,大图片解码、渲染会占用大量主线程算力
修复后的loadPicture参考实现:
更推荐直接使用官方维护的图片加载库的Compose扩展,比如Coil Compose、Glide Compose,无需自定义图片加载逻辑,性能更稳定。@Composable fun loadPicture(url: String): MutableState<Bitmap?> { val bitmapState: MutableState<Bitmap?> = remember(url) { mutableStateOf(null) } val context = LocalContext.current LaunchedEffect(url) { val target = object : CustomTarget<Bitmap>() { override fun onLoadCleared(placeholder: Drawable?) { bitmapState.value = null } override fun onResourceReady( resource: Bitmap, transition: Transition<in Bitmap>? ) { bitmapState.value = resource } } Glide.with(context) .asBitmap() .load(url) // 可替换为目标控件实际尺寸,减小解码开销 .override(Target.SIZE_ORIGINAL) .into(target) // Composable离开组合时取消请求 awaitDispose { Glide.with(context).clear(target) } } return bitmapState }- Glide请求未包裹在
2. LazyColumn未设置唯一item key
调用items时没有传入key参数,默认使用索引作为key,当列表数据发生变更时,会触发所有列表项的不必要重组,滑动过程中如果有数据更新,会大幅提升重组开销。
修复方案:给Poster类添加唯一标识字段(比如id),修改items调用:LazyColumn { items( items = posters, key = { it.id }, // 传入唯一key itemContent = { item -> VerticalListItem(item = item, callback) ListItemDivider() } ) }3. 点击回调每次重组都会重新创建
在Content函数中直接创建OnClickListener实例,每次Content重组都会生成新的回调对象,导致所有列表项的入参发生变化,触发不必要的全量重组,滑动时的性能开销大幅提升。
修复方案:使用remember稳定回调实例:is ViewState.Success -> { val navController = findNavController() // 把回调用remember缓存,仅当navController变化时才重新创建 val clickCallback = remember(navController) { OnClickListener { poster -> val destination = MainFragmentDirections.actionMainFragmentToDetailFragment(poster) navController.navigate(destination) } } VerticalListView(viewState.data, clickCallback) }4. StateFlow收集逻辑存在冗余风险
refresh方法每次调用都会启动新的收集器收集repository.result,如果多次调用refresh会存在多个收集器同时工作,重复发射数据导致上层频繁重组的问题。
修复方案:仅在ViewModel初始化时收集一次数据流,refresh方法单独触发数据刷新逻辑:init { viewModelScope.launch { repository.result.collect { _stateFlow.tryEmit(it) } } } fun refresh() { viewModelScope.launch { // 单独调用仓库的刷新接口,不要重复收集流 repository.refreshData() } }
额外优化建议
- 给
VerticalListItem、ImageView等常用Composable添加@Stable或@Immutable注解,提升Compose编译器的重组优化效率 - 开启Compose编译器的性能优化开关,在build.gradle中开启编译时优化配置
- 图片加载时添加固定尺寸的占位图,避免图片加载成功时控件大小突变触发父布局重组
内容的提问来源于stack exchange,提问作者Ali
相关产品推荐
相关产品推荐

