【问题标题】:Paging 3 - How to scroll to top of RecyclerView after PagingDataAdapter has finished refreshing AND DiffUtil has finished diffing?Paging 3 - 如何在 PagingDataAdapter 完成刷新并且 DiffUtil 完成差异后滚动到 RecyclerView 的顶部?
【发布时间】:2021-04-29 03:26:15
【问题描述】:

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

【问题讨论】:

  • 你找到解决办法了吗?
  • @P1NG2WIN 不,我现在使用 300 毫秒的延迟,这有点糟糕
  • :(我会创建关于它的问题
  • @P1NG2WIN 是您造成的问题吗?如果是的话,你能把它链接在这里吗?

标签: android paging android-paging android-paging-library android-paging-3


【解决方案1】:

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

【讨论】:

    【解决方案2】:

    看看代码是否刷新了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()
            }
     
    

    【讨论】:

      【解决方案3】:

      我已经设法从主题问题中改进基本代码 sn-p。 关键是监听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 的地方

      fun Boolean?.isTrue() = this != null && this
      

      所以这里的想法是跟踪mediator.prepend:endOfPagination 标志状态。当中介完成当前页面的分页加载部分时,他的prepend 状态不会改变(如果您在向下滚动后加载页面)。 解决方案适用于离线和在线模式。

      如果您需要跟踪前置分页或双向分页,这是一个很好的起点,可以尝试使用另一个 CombinedLoadStates 属性 append,refresh,mediatorsource

      【讨论】:

        【解决方案4】:

        @Florian 我可以确认我们不需要 postDelayed 使用 2021 年 7 月 21 日发布的版本 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

        【讨论】:

          【解决方案5】:

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

          }

          【讨论】:

            【解决方案6】:
            adapter.refresh()
            lifecycleScope.launch {
                adapter.loadStateFlow
                .collect {                  
                    binding.recycleView.smoothScrollToPosition(0)                           
                }
            }
            

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 2018-07-22
              • 1970-01-01
              • 2012-01-08
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2018-12-15
              相关资源
              最近更新 更多