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

如何在Compose中构建可配合Paging Library使用的StaggeredGridLayout?

在Jetpack Compose中实现配合Paging3的瀑布流布局(仿Pinterest UI)

一、添加必要依赖

在Module级别的build.gradle.kts中引入所需依赖:

// Compose核心UI依赖
implementation("androidx.compose.ui:ui:1.6.0")
implementation("androidx.compose.material3:material3:1.2.0")
// Paging3 Compose适配库
implementation("androidx.paging:paging-compose:3.2.1")
// 图片加载(示例用Coil,也可替换为Glide)
implementation("io.coil-kt:coil-compose:2.5.0")

二、用LazyVerticalStaggeredGrid实现瀑布流

Compose 1.4+提供了官方原生的瀑布流组件LazyVerticalStaggeredGrid,直接用来实现Pinterest的2列错落布局:

LazyVerticalStaggeredGrid(
    columns = StaggeredGridCells.Fixed(2), // 固定2列,和Pinterest一致
    contentPadding = PaddingValues(8.dp),
    horizontalArrangement = Arrangement.spacedBy(8.dp),
    verticalItemSpacing = 8.dp
) {
    // 填充分页数据项
}

三、结合Paging3实现分页加载

1. 定义分页数据源

实现PagingSource来处理分页数据的请求与返回:

class ImagePagingSource(private val api: ImageApi) : PagingSource<Int, ImageData>() {
    override suspend fun load(params: LoadParams<Int>): LoadResult<Int, ImageData> {
        return try {
            val currentPage = params.key ?: 1
            val response = api.fetchImages(page = currentPage, pageSize = params.loadSize)
            LoadResult.Page(
                data = response.images,
                prevKey = if (currentPage == 1) null else currentPage - 1,
                nextKey = if (response.images.isEmpty()) null else currentPage + 1
            )
        } catch (e: Exception) {
            LoadResult.Error(e)
        }
    }

    override fun getRefreshKey(state: PagingState<Int, ImageData>): Int? {
        return state.anchorPosition?.let { pos ->
            state.closestPageToPosition(pos)?.prevKey?.plus(1)
                ?: state.closestPageToPosition(pos)?.nextKey?.minus(1)
        }
    }
}

2. ViewModel中配置Pager

class PinterestViewModel : ViewModel() {
    val pagingImageFlow = Pager(
        config = PagingConfig(
            pageSize = 20,
            enablePlaceholders = false, // 关闭占位符,避免瀑布流高度跳动
            initialLoadSize = 40
        )
    ) {
        ImagePagingSource(ImageApi.create())
    }.flow.cachedIn(viewModelScope)
}

3. Compose UI整合分页与瀑布流

@Composable
fun PinterestStyleScreen(viewModel: PinterestViewModel = viewModel()) {
    val lazyPagingItems = viewModel.pagingImageFlow.collectAsLazyPagingItems()

    LazyVerticalStaggeredGrid(
        columns = StaggeredGridCells.Fixed(2),
        contentPadding = PaddingValues(8.dp),
        horizontalArrangement = Arrangement.spacedBy(8.dp),
        verticalItemSpacing = 8.dp
    ) {
        // 渲染分页数据项
        items(lazyPagingItems) { image ->
            image?.let { ImageCard(it) }
        }

        // 处理加载状态
        lazyPagingItems.apply {
            when {
                loadState.refresh is LoadState.Loading -> {
                    item { LoadingIndicator() }
                }
                loadState.append is LoadState.Loading -> {
                    item { LoadingIndicator() }
                }
                loadState.refresh is LoadState.Error -> {
                    val error = loadState.refresh as LoadState.Error
                    item { ErrorRetryItem(error.error.message ?: "加载失败", ::retry) }
                }
                loadState.append is LoadState.Error -> {
                    val error = loadState.append as LoadState.Error
                    item { ErrorRetryItem(error.error.message ?: "加载失败", ::retry) }
                }
            }
        }
    }
}

// 自适应高度的图片卡片(瀑布流核心)
@Composable
fun ImageCard(imageData: ImageData) {
    Card(
        modifier = Modifier.fillMaxWidth(),
        shape = RoundedCornerShape(8.dp),
        elevation = CardDefaults.cardElevation(4.dp)
    ) {
        AsyncImage(
            model = imageData.url,
            contentDescription = imageData.title,
            contentScale = ContentScale.Crop,
            modifier = Modifier
                .fillMaxWidth()
                .aspectRatio(imageData.aspectRatio) // 根据图片宽高比自动适配高度
        )
        Text(
            text = imageData.title,
            modifier = Modifier.padding(8.dp),
            style = MaterialTheme.typography.bodySmall
        )
    }
}

// 加载、错误重试组件
@Composable
fun LoadingIndicator() {
    Box(Modifier.fillMaxWidth().padding(16.dp), contentAlignment = Alignment.Center) {
        CircularProgressIndicator()
    }
}

@Composable
fun ErrorRetryItem(message: String, onRetry: () -> Unit) {
    Column(
        modifier = Modifier.fillMaxWidth().padding(16.dp),
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Text(message)
        Button(onClick = onRetry, modifier = Modifier.padding(top = 8.dp)) {
            Text("重试")
        }
    }
}

关键注意事项

  • 自适应高度:通过AsyncImage的aspectRatio绑定图片真实宽高比,让卡片自动调整高度,实现瀑布流错落效果。
  • 状态处理:必须覆盖分页的加载、错误状态,避免用户看到空白或异常界面。
  • 性能优化:关闭Paging的占位符选项,防止瀑布流高度跳动;用AsyncImage做图片加载,自带内存缓存避免OOM。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.20 17:45:45