分页库 3.0:如何将项目总数传递给列表标题?

Posted

技术标签:

【中文标题】分页库 3.0:如何将项目总数传递给列表标题?【英文标题】:Paging library 3.0 : How to pass total count of items to the list header? 【发布时间】:2021-01-06 17:07:20 【问题描述】:

请帮帮我。 该应用程序仅用于接收来自https://trefle.io 的植物列表并将其显示在 RecyclerView 中。 我在这里使用的是 Paging library 3.0。任务:我想添加一个标题,其中将显示植物的总量。问题:我只是找不到将总项目的值传递给标题的方法。

    Data model:  
    data class PlantsResponseObject(
    @SerializedName("data")
    val data: List<PlantModel>?,
    @SerializedName("meta")
    val meta: Meta?
) 
    data class Meta(
        @SerializedName("total")
        val total: Int? // 415648
    )

   data class PlantModel(
    @SerializedName("author")
    val author: String?,
    @SerializedName("genus_id")
    val genusId: Int?, 
    @SerializedName("id")
    val id: Int?)

数据源类:

class PlantsDataSource(
    private val plantsApi: PlantsAPI,
    private var filters: String? = null,
    private var isVegetable: Boolean? = false

) : RxPagingSource<Int, PlantView>() 

    override fun loadSingle(params: LoadParams<Int>): Single<LoadResult<Int, PlantView>> 
        val nextPageNumber = params.key ?: 1
           return plantsApi.getPlants(  //API call for plants
               nextPageNumber, //different filters, does not matter
               filters,
               isVegetable)
               .subscribeOn(Schedulers.io())
               .map<LoadResult<Int, PlantView>> 
                   val total = it.meta?.total ?: 0 // Here I have an access to the total count
 //of items, but where to pass it?
                    LoadResult.Page(
                       data = it.data!! //Here I can pass only plant items data
                           .map  PlantView.PlantItemView(it) ,
                       prevKey = null,
                       nextKey = nextPageNumber.plus(1)
                   )
               
               .onErrorReturn
                   LoadResult.Error(it)
               
    

    override fun invalidate() 
        super.invalidate()
    

LoadResult.Page 只接受植物本身的列表。并且 DataSource(Repo, ViewModel, Activity) 上面的所有类都无法访问响应对象。问题:如何将项目总数传递给列表标题? 我将不胜感激。

【问题讨论】:

【参考方案1】:

第一次尝试使用 Paging 时面临同样的困境,尽管它出于分页的目的进行计数,但它没有提供获取计数的方法(即 Paging 库首先使用 @987654322 检查@在进行其余查询之前查看是否有比规定的PagingConfig值更多或更少的项目,它可以完美地返回它找到的结果总数。

目前实现此目的的唯一方法是并行运行两个查询:一个用于您的项目(正如您已经拥有的那样),另一个只是使用与前一个相同的查询参数计算找到多少结果,但仅限COUNT(*)

没有必要将后者返回为PagingDataSource&lt;LivedData&lt;Integer&gt;&gt;,因为它会不必要地添加大量样板文件。只需将其作为正常的LivedData&lt;Integer&gt; 返回,以便在列表结果更改时始终更新自身,否则它可能会遇到列表大小更改的问题,并且如果您返回纯Integer.

在您设置好它们之后,然后使用 ConcatAdapter 将它们添加到您的 RecyclerView 适配器,前面提到的适配器的顺序与您希望它们显示在列表中的顺序相同。

例如:如果您希望计数显示在列表的开头/顶部,则首先使用计数适配器设置 ConcatAdapter,然后设置列表项适配器。

【讨论】:

【参考方案2】:

一种方法是使用 MutableLiveData 然后观察它。例如

val countPlants = MutableLiveData<Int>(0)

override fun loadSingle(..... 

    countPlants.postValue(it.meta?.total ?: 0)

 

然后是你的 recyclerview 所在的地方。

pagingDataSource.countPlants.observe(viewLifecycleOwner)  count ->
    //update your view with the count value

【讨论】:

【参考方案3】:

Paging 中的 withHeader 函数只返回一个 ConcatAdapter 给定一个 LoadStateHeader,它有一些代码可以根据适配器的 LoadState 进行侦听和更新。

您应该能够通过实现自己的ItemCountAdapter 来做一些非常相似的事情,只是它不是监听LoadState 的变化,而是监听adapter.itemCount。您需要构建一个流/侦听器来决定何时发送更新,但您可以简单地将 loadState 更改映射到 itemCount。

LoadStateAdapter代码见这里,基本可以复制,把loadState改成itemCount:https://cs.android.com/androidx/platform/frameworks/support/+/androidx-master-dev:paging/runtime/src/main/java/androidx/paging/LoadStateAdapter.kt?q=loadstateadapter

例如,

abstract class ItemCountAdapter<VH : RecyclerView.ViewHolder> : RecyclerView.Adapter<VH>()  

var itemCount: Int = 0

set(itemCount  ... 

open fun displayItemCountAsItem(itemCount: Int): Boolean  
    return true 

...

然后要实际创建ConcatAdapter,您需要类似于:https://cs.android.com/androidx/platform/frameworks/support/+/androidx-master-dev:paging/runtime/src/main/java/androidx/paging/PagingDataAdapter.kt;l=236?q=withLoadStateHeader&sq=

fun PagingDataAdapter.withItemCountHeader(itemCountAdapter): ConcatAdapter 
    addLoadStateListener 
        itemCountAdapter.itemCount = itemCount
    
    return ConcatAdapter(itemCountAdapter, this)

【讨论】:

adapter.itemCount 与我的问题无关。植物总数约为 300,000,我想在加载 30 株植物的第一部分后立即在适配器中显示此值。我在服务器响应中从 Meta 对象中提取植物总量。 我明白了,如果我误读了你的问题,我很抱歉。无论您如何填充/查询植物总数,总体思路都应该是相似的。即,ConcatAdapter 使用 PagingDataAdapter + 与 LoadStateAdapter 非常相似的东西生成,但经过定制以显示初始加载何时完成且不为空,而不是简单地监听 Prepend / append 加载状态更新。如果有什么我可以澄清的,请告诉我。【参考方案4】:

您可以将PagingData 类型更改为Pair&lt;PlantView,Int&gt;(或任何其他结构)以添加您需要的任何信息。 然后,您将能够发送总计页面,这些页面执行类似的操作:

LoadResult.Page(
   data = it.data.map  Pair(PlantView.PlantItemView(it), total) ,
   prevKey = null,
   nextKey = nextPageNumber.plus(1)
)

在您的ModelView 中执行任何操作,例如再次将其映射到PlantItemView,但使用second 字段更新您的标题。

确实,它不是很优雅,因为您要在所有项目中发送它,但它比其他建议的解决方案要好。

【讨论】:

【参考方案5】:

另一种解决方案,虽然也不是很优雅,但是将总量添加到您的数据模型 PlantView。

PlantView(…val totalAmount: Int…)

然后在您的视图模型中,您可以添加包含一项信息的标题。下面是摘自官方paging documenation的一点修改代码

pager.flow.map  pagingData: PagingData<PlantView> ->
  // Map outer stream, so you can perform transformations on
  // each paging generation.
  pagingData
  .map  plantView ->
    // Convert items in stream to UiModel.PlantView.
    UiModel.PlantView(plantView)
  
  .insertSeparators<UiModel.PlantView, UiModel>  before, after ->
    when 
      //total amount is used from the next PlantView
      before == null -> UiModel.SeparatorModel("HEADER", after?.totalAmount)
      // Return null to avoid adding a separator between two items.
      else -> null
    
  

一个缺点是总量在每个 PlantView 中,而且总是相同的,因此是多余的。

【讨论】:

【参考方案6】:

目前,我发现这条评论很有用:https://issuetracker.google.com/issues/175338415#comment5

有人讨论向 Pager 提供元数据状态的方法

【讨论】:

以上是关于分页库 3.0:如何将项目总数传递给列表标题?的主要内容,如果未能解决你的问题,请参考以下文章

在分页库 3 中恢复滚动位置

使用 Google 的分页库更新 PagedList 中的单个项目

在使用 Paging library 3.0 时,我们如何将整个对象列表传递给 PagingDataAdapter?

具有自定义数据源偏移量的 Android 分页库 RecyclerView 中的项目

如何在 pageInfo 中将总数传递给客户端

使用分页库时,观察者显示列表大小为零