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

Android Paging 3加载下一页后RecyclerView意外自动滚动至顶部

问题分析与解决方案

从你描述的现象(加载下一页后RecyclerView滚到顶部,适配器位置从0开始)来看,核心问题是加载下一页时意外触发了Paging的refresh操作,而非预期的append操作,导致适配器重新加载整个数据集,而非追加新数据。

可能的触发原因

  • PagingSource被意外Invalidate:你的SSSSearcherPagingSource中修改了共享的searcher.query,如果SearcherForHits会在query变化时自动触发PagingSource失效,那么每次加载下一页修改page参数时,都会导致PagingSource被invalidate,进而触发refresh。
  • refresh状态误判与不当操作:你在setLoadingStates中仅监听loadStates.refresh状态,并且在refresh完成后执行createCategoryTabs等操作——如果加载下一页时错误触发了refresh,这些操作可能间接导致RecyclerView滚动重置(比如重新配置LayoutManager)。
  • getRefreshKey逻辑过于简单:固定返回0的refreshKey会导致任何refresh操作都从第0页开始加载,即使原本是要加载下一页。
  • PagingData重复提交:如果paginator.liveData在加载下一页时重新发射了新的PagingData,会触发适配器的submitData重新执行,相当于重置整个数据集。

分步解决方案

1. 修复PagingSource的Invalidate问题

避免在load方法中修改共享的searcher.query,改为传递独立的查询对象,防止触发PagingSource失效:

override suspend fun load(params: LoadParams<Int>): LoadResult<Int, T> {
    return try {
        val pageNumber = params.key ?: 0
        // 创建独立查询副本,不修改共享的searcher.query
        val tempQuery = searcher.query.copy(
            page = pageNumber,
            hitsPerPage = 20
        )
        // 使用独立查询执行搜索
        val response = searchWithTempQuery(tempQuery) ?: return emptyPage()
        val data = response.hits.map(transformer)
        val nextKey = if (pageNumber < response.nbPages) pageNumber + 1 else null
        LoadResult.Page(
            data = data,
            prevKey = null,
            nextKey = nextKey
        )
    } catch (exception: Exception) {
        Log.w("Paging search operation failed", exception)
        LoadResult.Error(exception)
    }
}

如果无法创建独立查询,需检查SearcherForHits的文档,确认修改query是否会自动触发PagingSource invalidate,若有则需关闭该逻辑。

2. 区分refresh与append状态,避免不当操作

在setLoadingStates中,仅在初始加载或下拉刷新时执行初始化操作,加载下一页(append)时不执行重置逻辑:

private fun setLoadingStates() {
    viewLifecycleOwner.lifecycleScope.launchWhenResumed {
        plpAdapter?.loadStateFlow?.collectLatest { loadStates -> 
            // 仅处理初始加载/下拉刷新完成的情况
            when (loadStates.refresh) {
                is LoadState.NotLoading -> {
                    val productList = plpAdapter?.getProductList() ?: arrayListOf()
                    if (productList.isNotEmpty()) {
                        if (!isFromSearch) {
                            // 仅在新查询时创建分类标签
                            createCategoryTabs(productList, selectedCategoryId)
                        }
                        trackAlgoliaPLPView()
                        initMostUsedFilter()
                        trackMoengagePLPViewEvent(productList = productList)
                    } else {
                       noDataFound()
                    }

                    if (isFromSearch) {
                        val suggestion = algoliaViewModel.suggestions.value?.peekContent()?.second
                        val query = algoliaViewModel.suggestions.value?.peekContent()?.first
                        trackMoEngageSearchEvent(
                            selectedQuery = suggestion?.query,
                            searchText = query,
                            productList.isEmpty()
                        )
                    }
                    hideShimmer()
                }
                is LoadState.Error -> hideShimmer()
            }

            // 单独处理加载下一页的状态,不执行重置操作
            when (loadStates.append) {
                is LoadState.Error -> {
                    // 处理下一页加载失败逻辑,比如显示重试按钮
                }
            }
        }
    }
}

3. 优化getRefreshKey逻辑

让refresh操作回到当前可见的页面位置,而非固定从第0页开始:

override fun getRefreshKey(state: PagingState<Int, T>): Int {
    return state.anchorPosition?.let { anchorPosition ->
        val closestPage = state.closestPageToPosition(anchorPosition)
        closestPage?.prevKey ?: closestPage?.nextKey ?: 0
    } ?: 0
}

4. 确保submitData仅在必要时调用

检查paginator的创建逻辑,确保同一个查询生命周期内只创建一次paginator,避免重复提交PagingData:

// 确保观察者仅设置一次,paginator仅在新查询时重新创建
paginator?.liveData?.observe(viewLifecycleOwner) { pagingData ->
    plpAdapter?.submitData(lifecycle, pagingData)
}

验证要点

  • 观察loadStateFlow的状态,确认加载下一页时触发的是append而非refresh
  • 检查适配器的item数量,加载下一页后应该是累加而非重置为20

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.12 22:18:12