如何解决Paging 3 - 如何在 PagingDataAdapter 完成刷新和 DiffUtil 完成差异后滚动到 RecyclerView 的顶部?
我将 Paging 3 与 RemoteMediator 一起使用,它在从网络获取新数据时显示缓存数据。
当我刷新我的 PagingDataAdapter
(通过调用 refresh()
)时,我希望我的 RecyclerView 在刷新完成后滚动到顶部。在 https://helm.sh/docs/howto/charts_tips_and_tricks/#automatically-roll-deployments 中,他们尝试通过 loadStateFlow
以下列方式处理此问题:
lifecycleScope.launch {
adapter.loadStateFlow
// Only emit when REFRESH LoadState for RemoteMediator changes.
.distinctUntilChangedBy { it.refresh }
// Only react to cases where Remote REFRESH completes i.e.,NotLoading.
.filter { it.refresh is LoadState.NotLoading }
.collect { binding.list.scrollToPosition(0) }
}
这确实向上滚动,但在 DiffUtil 完成之前。这意味着如果真的有新数据插入到顶部,RecyclerView 不会一直向上滚动。
我知道 RecyclerView 适配器有一个 AdapterDataObserver
回调,我们可以在 DiffUtil 完成差异时收到通知。但这会导致适配器的 PREPEND
和 APPEND
加载状态出现各种竞争条件,这也会导致 DiffUtil 运行(但在这里我们不想滚动到顶部)。
一种可行的解决方案是将 PagingData.empty()
传递给 PagingDataAdapter
并重新运行相同的查询(仅调用 refresh
不起作用,因为 PagingData
现在是空,没有任何东西可以刷新)但我更愿意让我的旧数据保持可见,直到我知道刷新确实成功了。
解决方法
查看代码是否刷新了加载类型。
repoDatabase.withTransaction {
// clear all tables in the database
if (loadType == LoadType.REFRESH) {
repoDatabase.remoteKeysDao().clearRemoteKeys()
repoDatabase.reposDao().clearRepos()
}
val prevKey = if (page == GITHUB_STARTING_PAGE_INDEX) null else page - 1
val nextKey = if (endOfPaginationReached) null else page + 1
val keys = repos.map {
Log.e("RemoteKeys","repoId: ${it.id} prevKey: $prevKey nextKey: $nextKey")
RemoteKeys(repoId = it.id,prevKey = prevKey,nextKey = nextKey)
}
repoDatabase.remoteKeysDao().insertAll(keys)
repoDatabase.reposDao().insertAll(repos)
}
如果 LoadType 为 refresh 清除所有表,则应删除该条件。
if (loadType == LoadType.REFRESH) {
repoDatabase.remoteKeysDao().clearRemoteKeys()
repoDatabase.reposDao().clearRepos()
}
,
我已经设法改进主题问题中的基本代码片段。
关键是在 CombinedLoadStates
viewLifecycleOwner.lifecycleScope.launchWhenCreated {
adapter?.loadStateFlow
?.distinctUntilChanged { old,new ->
old.mediator?.prepend?.endOfPaginationReached.isTrue() ==
new.mediator?.prepend?.endOfPaginationReached.isTrue()
}
?.filter { it.refresh is LoadState.NotLoading }
..
// next flow pipeline operators
}
其中 isTrue
是布尔扩展乐趣
fun Boolean?.isTrue() = this != null && this
所以这里的想法是跟踪 mediator.prepend:endOfPagination
旗国。当中介完成当前页面的分页加载部分时,他的 prepend
状态不会改变(以防您在向下滚动后加载页面)。
解决方案适用于离线和在线模式。
如果您需要在两个方向上跟踪前置分页或分页,那么使用另一个 CombinedLoadStates
属性 append
,refresh
,mediator
是一个很好的起点和source
在搜索静态内容等情况下,我们可以在 false
的 areItemsTheSame
内返回 DiffUtil.ItemCallback
作为解决方法。我也用它来改变排序属性。
@Florian 我可以确认,使用 2021 年 7 月 21 日发布的 postDelayed
版,我们不需要使用 3.1.0-alpha03
滚动到顶部。
此外,我设法进一步过滤了 loadStateFlow
集合,因此它不会阻止 StateRestorationPolicy.PREVENT_WHEN_EMPTY
根据 @Alexandr 答案工作。我的解决方案是:
到我写这篇文章的时候,Paging3 的最新版本是 3.1.0-alpha03
所以导入:
androidx.paging:paging-runtime-ktx:3.1.0-alpha03
然后将您的适配器的恢复策略设置如下:
adapter.stateRestorationPolicy = RecyclerView.Adapter.StateRestorationPolicy.PREVENT_WHEN_EMPTY
如果您对上述更改有编译错误,请确保您使用的 RecyclerView 版本至少为 1.2.0-alpha02。上面的任何版本也不错:
androidx.recyclerview:recyclerview:1.2.0-alpha02
然后使用过滤后的 loadStateFlow
将列表滚动到顶部,仅当您刷新页面并且项目被添加到列表中时:
viewLifecycleOwner.lifecycleScope.launch {
challengesAdapter.loadStateFlow
.distinctUntilChanged { old,new ->
old.mediator?.prepend?.endOfPaginationReached.isTrue() ==
new.mediator?.prepend?.endOfPaginationReached.isTrue() }
.filter { it.refresh is LoadState.NotLoading && it.prepend.endOfPaginationReached && !it.append.endOfPaginationReached}
.collect {
mBinding.fragmentChallengesByLocationList.scrollToPosition(0)
}
}
可以在此处找到 GitHub 讨论:https://github.com/googlecodelabs/android-paging/issues/149
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。