8

我正在使用带有 RemoteMediator 的 Paging 3,它在从网络获取新数据时显示缓存数据。当我刷新我的PagingDataAdapter(通过调用refresh()它)时,我希望我的 RecyclerView 在刷新完成后滚动到顶部。在代码实验室中,他们尝试通过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现在是空的并且没有什么可刷新的)但我更愿意保持我的旧数据可见,直到我知道刷新实际上成功了。

4

6 回答 6

2

在某些情况下,例如搜索静态内容,我们可以返回falseinside areItemsTheSameofDiffUtil.ItemCallback作为一种解决方法。我也用它来改变排序属性。

于 2021-06-18T17:01:11.563 回答
0

关注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)
}

}

于 2021-12-21T23:24:33.727 回答
0

@Florian 我可以确认我们不需要使用21/07/2021 发布的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

如果您对上述更改有编译错误,请确保您使用的是至少 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

于 2021-08-06T16:15:20.250 回答
0

看看代码是否刷新了 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()
        }
 
于 2021-05-05T15:56:18.623 回答
0

我已经设法从主题问题中改进基本代码片段。关键是监听里面属性的非组合变体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标志状态。当 mediator 完成了当前页面的分页加载部分时,他的prepend状态不会改变(如果您在向下滚动后加载页面)。解决方案适用于离线和在线模式。

如果您需要跟踪前置分页或双向分页,这是一个很好的起点,可以使用其他CombinedLoadStates属性append,和refreshmediatorsource

于 2021-05-21T10:49:57.833 回答
0
adapter.refresh()
lifecycleScope.launch {
    adapter.loadStateFlow
    .collect {                  
        binding.recycleView.smoothScrollToPosition(0)                           
    }
}
于 2022-01-19T20:42:20.183 回答