如何在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
相关产品推荐
相关产品推荐

