在Jetpack Compose中实现带分页的懒加载Vertical Pager
问题:如何同时实现VerticalPager的分页交互与Paging3的懒加载分页
我们已实现多数功能,分别尝试了LazyColumn和VerticalPager,但各自存在问题:
LazyColumn:可配合Paging3正常实现动态分页(每次加载5条,距离末尾2条时触发下一批加载),但无法提供类似Instagram Reels/Youtube Shorts的分页滚动交互与动画效果。VerticalPager:具备理想的全屏分页滚动体验,但会一次性加载数据库中全部30条数据,无法适配现有分页逻辑——推测是因为其初始化时依赖itemCount,会触发Paging3一次性加载所有数据。
目标是实现竖屏视频流(类似Reels):每个页面全屏显示视频,页面可见时播放、隐藏时暂停,同时保留Paging3的懒加载分页能力。
核心原因
VerticalPager的页面总数由PagerState的pageCount决定,当直接传入pagingVideo.itemCount时,Paging3会判定需要加载所有数据来填充这个总数,从而触发一次性全量加载,而非懒加载。
解决方案
1. 自定义分页触发逻辑
放弃依赖LazyPagingItems的自动加载,改为监听PagerState的滚动位置手动触发分页:
- 当当前页面距离已加载数据末尾≤2时,调用
pagingVideo.append()触发下一批加载。 - 避免直接用
pagingVideo.itemCount作为PagerState的pageCount,改用已加载的有效数据数量(或自定义计数)。
2. 优化VerticalPager懒加载行为
- 设置
beyondBoundsPageCount = 1,仅预加载当前页面前后各1页,减少不必要的资源消耗。 - 使用
LazyPagingItems的peek()方法替代[]操作,避免触发不必要的数据加载。
3. 配合视频播放状态管理
利用PagerState的currentPage和isScrollInProgress状态,控制视频的播放与暂停,确保仅当前可见的视频处于播放状态。
修改后的完整代码
val pagingVideo = state.videos.collectAsLazyPagingItems() val refresh = pagingVideo.loadState.refresh val append = pagingVideo.loadState.append // 初始化数据刷新 LaunchedEffect(Unit) { pagingVideo.refresh() } // 监听滚动位置,手动触发分页加载 LaunchedEffect(pagerState.currentPage) { val loadedItemCount = pagingVideo.itemCount // 距离已加载末尾还有2条时,触发下一批加载 if (pagerState.currentPage >= loadedItemCount - 2 && !pagingVideo.appendState.isLoading) { pagingVideo.append() } // 更新滑块位置 interactionListener.updateSliderPosition(pagerState.currentPage) } // 动态设置Pager总页数:有更多数据时,在已加载数量基础上加1作为加载占位页 val pagerState = rememberPagerState( initialPage = 0, initialPageOffsetFraction = 0f ) { if (pagingVideo.loadState.append !is LoadState.NotLoading || pagingVideo.hasNextPage) { pagingVideo.itemCount + 1 } else { pagingVideo.itemCount } } val fling = PagerDefaults.flingBehavior( state = pagerState, lowVelocityAnimationSpec = tween( easing = LinearEasing, durationMillis = 300 ) ) VerticalPager( state = pagerState, modifier = Modifier .background(black) .fillMaxSize() .padding(innerPadding), horizontalAlignment = Alignment.CenterHorizontally, flingBehavior = fling, beyondBoundsPageCount = 1, // 限制预加载范围 key = { index -> if (index < pagingVideo.itemCount) { pagingVideo.peek(index)?.blinkID?.takeIf { it.isNotEmpty() } ?: "$index" } else { "loading-$index" } } ) { pagerIndex -> // 处理加载占位页 if (pagerIndex >= pagingVideo.itemCount) { LoadingProgressBar() } else { val blinks = pagingVideo.peek(pagerIndex) ?: return@VerticalPager Box(modifier = Modifier.fillMaxSize()) { VideoPlayer( blinks, interactionListener, pagerState, pagerIndex, editPlayer = { exoPlayer = it } ) AnimatedVisibility( visible = state.pauseButton && pagerState.currentPage == pagerIndex && !pagerState.isScrollInProgress, enter = scaleIn( spring(Spring.DampingRatioMediumBouncy), initialScale = 1.3f ), exit = scaleOut(tween(150)), modifier = Modifier.align(Alignment.Center) ) { Icon( painter = painterResource(R.drawable.ic_blink_play), contentDescription = null, tint = Color.Unspecified, modifier = Modifier.size(36.dp) ) } } } } // 处理加载状态 pagingVideo.loadState.apply { when { refresh is LoadState.Loading -> LoadingProgressBar() refresh is LoadState.Error -> Log.e(TAG, "Refresh error: ${refresh.error}") append is LoadState.Error -> Log.e(TAG, "Append error: ${append.error}") } }
关键说明
- 手动分页触发:通过监听
pagerState.currentPage判断是否接近已加载数据末尾,手动调用append(),避免Paging3一次性加载所有数据。 - 动态PageCount:根据是否还有未加载数据,动态设置
PagerState的总页数,添加占位页显示加载状态。 - 懒加载优化:用
peek()获取数据(不会触发加载),配合beyondBoundsPageCount限制预加载范围,降低内存占用。 - 视频状态控制:仅当页面为当前页且未滚动时显示播放按钮,确保视频播放状态与页面可见性同步。
内容的提问来源于stack exchange,提问作者Rebin A. Dev
相关产品推荐
相关产品推荐

