繁体   English   中英

Paging 3 - 如何在 PagingDataAdapter 完成刷新并且 DiffUtil 完成差异后滚动到 RecyclerView 的顶部?

[英]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 完成差异时收到通知。 但这会导致适配器的PREPENDAPPEND加载状态的各种竞争条件,这也会导致 DiffUtil 运行(但这里我们不想滚动到顶部)。

一种可行的解决方案是将PagingData.empty()传递给PagingDataAdapter并重新运行相同的查询(只是调用refresh不起作用,因为PagingData现在是空的并且没有什么可刷新的)但我更愿意保留我的旧数据可见,直到我知道刷新实际上成功了。

在某些情况下,例如搜索 static 内容,我们可以在areItemsTheSameDiffUtil.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属性appendrefreshmediatorsource

@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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM