[英]Paging 3 - How to scroll to top of RecyclerView after PagingDataAdapter has finished refreshing AND DiffUtil has finished diffing?
我正在使用带有 RemoteMediator 的 Paging 3,它在从网络获取新数据时显示缓存数据。 当我刷新我的PagingDataAdapter
(通过调用refresh()
时,我希望我的 RecyclerView 在刷新完成后滚动到顶部。 在代码实验室loadStateFlow
,他们尝试通过以下方式通过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
现在是空的并且没有什么可刷新的)但我更愿意保留我的旧数据可见,直到我知道刷新实际上成功了。
在某些情况下,例如搜索 static 内容,我们可以在areItemsTheSame
的DiffUtil.ItemCallback
内返回false
作为解决方法。 我也用它来改变排序属性。
看看代码是否刷新了 loadtype 的条件。
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 是刷新清除所有表,您应该删除条件。
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
是 Boolean 扩展乐趣
fun Boolean?.isTrue() = this != null && this
所以这里的想法是跟踪mediator.prepend:endOfPagination
标志状态。 当中介完成当前页面的分页加载部分时,他的prepend
不会改变(如果您在向下滚动后加载页面)。 解决方案适用于离线和在线模式。
如果您需要跟踪前置分页或双向分页,这是一个很好的起点,可以使用另一个CombinedLoadStates
属性append
、 refresh
、 mediator
和source
@Florian 我可以确认我们不需要使用 2021 年 7 月 21 日发布的3.1.0-alpha03
版本将postDelayed
滚动到顶部。 此外,我设法进一步过滤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
如果您对上述更改有编译错误,请确保您使用的是至少 1.2.0-alpha02 版本的 RecyclerView。 上面的任何版本也很好:
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
关注https://developer.android.com/reference/kotlin/androidx/paging/PagingDataAdapter
val USER_COMPARATOR = object : DiffUtil.ItemCallback<User>() {
override fun areItemsTheSame(oldItem: User, newItem: User): Boolean =
// User ID serves as unique ID
oldItem.userId == newItem.userId
override fun areContentsTheSame(oldItem: User, newItem: User): Boolean =
// Compare full contents (note: Java users should call .equals())
oldItem == newItem
}
class UserAdapter : PagingDataAdapter<User, UserViewHolder>(USER_COMPARATOR) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): UserViewHolder {
return UserViewHolder.create(parent)
}
override fun onBindViewHolder(holder: UserViewHolder, position: Int) {
val repoItem = getItem(position)
// Note that item may be null, ViewHolder must support binding null item as placeholder
holder.bind(repoItem)
}
}
adapter.refresh()
lifecycleScope.launch {
adapter.loadStateFlow
.collect {
binding.recycleView.smoothScrollToPosition(0)
}
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.