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

在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}")
    }
}

关键说明

  1. 手动分页触发:通过监听pagerState.currentPage判断是否接近已加载数据末尾,手动调用append(),避免Paging3一次性加载所有数据。
  2. 动态PageCount:根据是否还有未加载数据,动态设置PagerState的总页数,添加占位页显示加载状态。
  3. 懒加载优化:用peek()获取数据(不会触发加载),配合beyondBoundsPageCount限制预加载范围,降低内存占用。
  4. 视频状态控制:仅当页面为当前页且未滚动时显示播放按钮,确保视频播放状态与页面可见性同步。

内容的提问来源于stack exchange,提问作者Rebin A. Dev

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.26 06:05:22