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

Jetpack Compose Paging实现中滚动卡顿问题求助

Compose分页列表滚动卡顿问题解决建议

我在使用Compose实现应用分页功能时,遇到一个问题:快速滚动尚未加载图片的区域时,界面会明显卡顿。已排查确认Coil图片加载无异常,希望得到解决建议。

相关实现代码如下:

分页主界面Composable

@Composable
fun <T : TMDbItem> PagingScreen(
    viewModel: BasePagingViewModel<T>,
    onClick: (TMDbItem) -> Unit,
) {
    val lazyTMDbItems = viewModel.pagingDataFlow.collectAsLazyPagingItems()

    when (lazyTMDbItems.loadState.refresh) {
        is LoadState.Loading -> {
            TMDbProgressBar()
        }
        is LoadState.Error -> {
            val message =
                (lazyTMDbItems.loadState.refresh as? LoadState.Error)?.error?.message ?: return

            lazyTMDbItems.apply {
                ErrorScreen(
                    message = message,
                    modifier = Modifier.fillMaxSize(),
                    refresh = { retry() }
                )
            }
        }
        else -> {
            LazyTMDbItemGrid(lazyTMDbItems, onClick)
        }
    }
}

网格列表实现

@Composable
private fun <T : TMDbItem> LazyTMDbItemGrid(
    lazyTMDbItems: LazyPagingItems<T>,
    onClick: (TMDbItem) -> Unit
) {
    LazyVerticalGrid(
        columns = GridCells.Fixed(COLUMN_COUNT),
        contentPadding = PaddingValues(
            start = Dimens.GridSpacing,
            end = Dimens.GridSpacing,
            bottom = WindowInsets.navigationBars.getBottom(LocalDensity.current)
                .toDp().dp.plus(
                    Dimens.GridSpacing
                )
        ),
        horizontalArrangement = Arrangement.spacedBy(
            Dimens.GridSpacing,
            Alignment.CenterHorizontally
        ),
        content = {

            items(lazyTMDbItems.itemCount) { index ->
                val tmdbItem = lazyTMDbItems[index]
                tmdbItem?.let {
                    TMDbItemContent(
                        it,
                        Modifier
                            .height(320.dp)
                            .padding(vertical = Dimens.GridSpacing),
                        onClick
                    )
                }
            }

            lazyTMDbItems.apply {
                when (loadState.append) {
                    is LoadState.Loading -> {
                        item(span = span) {
                            LoadingRow(modifier = Modifier.padding(vertical = Dimens.GridSpacing))
                        }
                    }
                    is LoadState.Error -> {
                        val message =
                            (loadState.append as? LoadState.Error)?.error?.message ?: return@apply

                        item(span = span) {
                            ErrorScreen(
                                message = message,
                                modifier = Modifier.padding(vertical = Dimens.GridSpacing),
                                refresh = { retry() })
                        }
                    }
                    else -> {}
                }
            }
        })
}

图片加载Composable

@Composable
private fun BoxScope.TMDbItemPoster(posterUrl: String?, tmdbItemName: String) {
    val painter = rememberAsyncImagePainter(
        model = posterUrl,
        error = rememberVectorPainter(Icons.Filled.BrokenImage),
        placeholder = rememberVectorPainter(Icons.Default.Movie)
    )
    val colorFilter = when (painter.state) {
        is AsyncImagePainter.State.Loading, is AsyncImagePainter.State.Error -> ColorFilter.tint(
            MaterialTheme.colors.imageTint
        )
        else -> null
    }
    val scale =
        if (painter.state !is AsyncImagePainter.State.Success) ContentScale.Fit else ContentScale.FillBounds

    Image(
        painter = painter,
        colorFilter = colorFilter,
        contentDescription = tmdbItemName,
        contentScale = scale,
        modifier = Modifier
            .fillMaxSize()
            .align(Alignment.Center)
    )
}

针对性解决建议

  1. 缓存占位符/错误图标Painter
    不要在每次TMDbItemPoster重组时创建rememberVectorPainter,提前缓存这两个Painter,减少不必要的对象创建与重组开销:

    @Composable
    private fun BoxScope.TMDbItemPoster(posterUrl: String?, tmdbItemName: String) {
        // 提前缓存Painter对象
        val placeholderPainter = remember { rememberVectorPainter(Icons.Default.Movie) }
        val errorPainter = remember { rememberVectorPainter(Icons.Filled.BrokenImage) }
        
        val painter = rememberAsyncImagePainter(
            model = posterUrl,
            error = errorPainter,
            placeholder = placeholderPainter
        )
        // ... 其余代码
    }
    
  2. 缓存计算状态,减少重复计算
    将colorFilter和scale的计算用remember包裹,仅在painter.state变化时重新计算,避免每次重组都执行判断逻辑:

    val colorFilter = remember(painter.state) {
        when (painter.state) {
            is AsyncImagePainter.State.Loading, is AsyncImagePainter.State.Error -> 
                ColorFilter.tint(MaterialTheme.colors.imageTint)
            else -> null
        }
    }
    val scale = remember(painter.state) {
        if (painter.state !is AsyncImagePainter.State.Success) 
            ContentScale.Fit else ContentScale.FillBounds
    }
    
  3. 优化图片请求尺寸
    从TMDb API请求图片时,指定适配Item尺寸的缩略图规格(比如w342),避免加载过大的原图,减少图片解码时间与内存占用。

  4. 减少Lazy布局的重组触发

    • 确保TMDbItem是数据类,自动实现正确的equals和hashCode,让Lazy布局能准确判断Item是否需要重组。
    • 检查TMDbItemContent内部是否有不稳定参数传递,尽量使用remember或稳定类型传递参数。
  5. 启用性能分析定位根源
    使用Android Studio的Compose Profiler查看卡顿期间的UI线程占用、重组次数:

    • 如果是重组次数过多,针对性减少不必要的重组触发;
    • 如果是图片解码耗时,可配置Coil使用硬件加速解码,或提前对图片进行压缩处理。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.05 09:35:33