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

Jetpack Compose使用LazyColumn实现垂直列表滑动卡顿不流畅问题求助

Jetpack Compose LazyColumn滑动卡顿问题排查与修复

核心问题点

  • 1. 图片加载逻辑存在严重性能缺陷

    自定义的loadPicture函数有多处问题:
    1. Glide请求未包裹在LaunchedEffect中,每次Composable重组都会重复发起图片请求,滑动时列表项频繁触发重组,会导致大量重复的图片加载任务抢占主线程资源
    2. remember未绑定url作为key,列表项复用时不会重置图片状态,会出现图片错位、重复请求的问题
    3. 没有管理Glide请求的生命周期,滑出屏幕的列表项的图片请求不会被取消,冗余请求占用系统资源
    4. 未对Bitmap做尺寸适配,直接加载原图,大图片解码、渲染会占用大量主线程算力
      修复后的loadPicture参考实现:
    @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
    }
    
    更推荐直接使用官方维护的图片加载库的Compose扩展,比如Coil Compose、Glide Compose,无需自定义图片加载逻辑,性能更稳定。
  • 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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.27 15:54:04