如何使用分页库在回收站视图中添加日期分隔符?

How to add date separators in recycler view using Paging Library?

经过大量搜索,我知道使用常规适配器是可行的,但我不知道如何使用 Paging Library 来实现。我不需要代码只是一个线索。

示例

要添加分隔符,您基本上有 2 个选项:

  1. 基于视图,您明确地将分隔符作为 'item' 包含在列表中,并为这些分隔符定义新的视图类型。允许列表重新使用分隔符视图,但这意味着您需要在定义数据时考虑分隔符。
  2. 基于数据,每个项目实际上都有一个分隔视图,但它只显示在特定项目上。根据某些标准,您可以在绑定视图持有者时显示或隐藏它。

对于分页库,只有选项 2 可行,因为它只加载部分数据并且插入分隔符变得更加复杂。您只需要找出一种方法来根据结果检查项目 x 是否与项目 x-1 和 show/hide 视图中的日期部分不同。

绑定上一项中的数据传递时也是如此

  override fun onBindViewHolder(holder: ViewHolder, position: Int) {
    val item = getItem(position)
    val previousItem = if (position == 0) null else getItem(position - 1)
    holder.bind(item, previousItem)
  }

然后每个视图都会设置一个 header,仅当前一个项目不具有相同的 header 时才可见。

    val previousHeader =  previousItem?.name?.capitalize().first()
    val header = item?.name?.capitalize()?.first()
    view.cachedContactHeader.text = header
    view.cachedContactHeader.isVisible  = previousHeader != header

非常好,对于您的情况,选项 2 可能效果很好。

在我的例子中,我想要一个不在数据库中的额外项目,如下所示:

  • 显示全部
  • 项目 1
  • 项目 2

它也需要可以点击。通常的方法是将 getItemCount 覆盖到 return +1 并偏移其他方法的位置。

但我偶然发现了另一种我还没有看到记录的方法,它可能对某些情况有用。您可以使用 union:

将其他元素合并到您的查询中
@Query("select '' as name, 0 as id " +
        "union " +
        "select name, id from user " +
        "order by 1 asc")
DataSource.Factory<Integer, User> getAllDataSource();

也就是说数据源实际上是return开头的另一个项目,不需要调整位置。在您的适配器中,您可以检查该项目并以不同方式处理它。

在您的情况下,查询必须有所不同,但我认为这是可能的。

我和你在同一个地方,我想出了这个解决方案。

不过有一个重要的注意事项,为了实现这一点,我必须将我的日期转换器更改为数据库,从 long 到 string 以存储时间戳

这些是我的转换器

class DateConverter {
    companion object {
        @JvmStatic
        val formatter = SimpleDateFormat("yyyyMMddHHmmss", Locale.ENGLISH)

        @TypeConverter
        @JvmStatic
        fun toDate(text: String): Date = formatter.parse(text)

        @TypeConverter
        @JvmStatic
        fun toText(date: Date): String = formatter.format(date)
    }
}

虽然有一些起始信息,但我有一个报告列表 headers 我想显示,翻页并能够过滤

他们表示为 object:

data class ReportHeaderEntity(
@ColumnInfo(name = "id") override val id: UUID
, @ColumnInfo(name = "name") override val name: String
, @ColumnInfo(name = "description") override val description: String
, @ColumnInfo(name = "created") override val date: Date)

我还想在列表中的项目之间添加分隔符以按日期显示它们

我通过执行以下操作实现了这一目标:

我在这样的房间里创建了一个新查询

 @Query(
    "SELECT id, name, description,created " +
            "FROM   (SELECT id, name, description, created, created AS sort " +
            "        FROM   reports " +
            "        WHERE  :filter = '' " +
            "                OR name LIKE '%' || :filter || '%' " +
            "                OR description LIKE '%' || :filter || '%' " +
            "        UNION " +
            "        SELECT '00000000-0000-0000-0000-000000000000' as id, Substr(created, 0, 9) as name, '' as description, Substr(created, 0, 9) || '000000' AS created, Substr(created, 0, 9) || '256060' AS sort " +
            "        FROM   reports " +
            "        WHERE  :filter = '' " +
            "                OR name LIKE '%' || :filter || '%' " +
            "                OR description LIKE '%' || :filter || '%' " +
            "        GROUP  BY Substr(created, 0, 9)) " +
            "ORDER  BY sort DESC ")

fun loadReportHeaders(filter: String = ""): DataSource.Factory<Int, ReportHeaderEntity>

这基本上为我过滤的所有项目创建了一个分隔线

它还创建了一个用于排序的虚拟日期(时间为 25:60:60,以便它始终出现在其他报告的前面)

然后我使用 union 将其与我的列表合并,并按虚拟日期对它们进行排序

我必须从 long 更改为字符串的原因是因为在 sql 中使用字符串创建虚拟日期并将日期部分与整个日期时间分开要容易得多

上面创建了一个这样的列表:

00000000-0000-0000-0000-000000000000    20190522        20190522000000
e3b8fbe5-b8ce-4353-b85d-8a1160f51bac    name 16769  description 93396   20190522141926
6779fbea-f840-4859-a9a1-b34b7e6520be    name 86082  description 21138   20190522141925
00000000-0000-0000-0000-000000000000    20190521        20190521000000
6efa201f-d618-4819-bae1-5a0e907ddcfb    name 9702   description 84139   20190521103247

在我的 PagedListAdapter 中,我将其更改为 PagedListAdapter<ReportHeader, RecyclerView.ViewHolder> 的实现(不是特定的查看器)

已添加到随播广告object:

companion object {
    private val EMPTY_ID = UUID(0L,0L)
    private const val LABEL = 0
    private const val HEADER = 1
}

并像这样覆盖获取视图类型:

override fun getItemViewType(position: Int): Int = if (getItem(position)?.id ?: EMPTY_ID == EMPTY_ID) LABEL else HEADER

然后我创建了两个单独的视图持有者:

class ReportHeaderViewHolder(val binding: ListItemReportBinding) : RecyclerView.ViewHolder(binding.root) 

class ReportLabelViewHolder(val binding: ListItemReportLabelBinding) : RecyclerView.ViewHolder(binding.root)

并像这样实现了其他重写方法:

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
    val inflater = LayoutInflater.from(parent.context)
    return when (viewType) {
        HEADER -> ReportHeaderViewHolder(DataBindingUtil.inflate(inflater, R.layout.list_item_report, parent, false))
        else -> ReportLabelViewHolder(DataBindingUtil.inflate(inflater, R.layout.list_item_report_label, parent, false))
    }
}

override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
    val reportItem = getItem(position)
    when (getItemViewType(position)) {
        HEADER -> {
            (holder as ReportHeaderViewHolder).binding.apply {
                report = reportItem
                executePendingBindings()
            }
        }
        LABEL -> {
            (holder as ReportLabelViewHolder).binding.apply {
                date = reportItem?.name
                executePendingBindings()
            }
        }
    }
}

我希望这能帮助和激励人们找到更好的解决方案

您可以使用 Paging 3 库中的 insertSeparators 获得相同的结果。 确保您的商品按日期 sorted

内部或 viewmodel 检索 Pager 类似的东西

private val communicationResult: Flow<PagingData<CommunicationHistoryItem>> = Pager(
    PagingConfig(
        pageSize = 50,
        enablePlaceholders = false,
        maxSize = 400,
        initialLoadSize = 50
    )
) {
    CommunicationPagingSource(repository)
}.flow.cachedIn(viewModelScope)

毕竟insert separators像一个header

val groupedCommunicationResult = communicationResult
        .map { pagingData -> pagingData.map { CommunicationHistoryModel.Body(it) } }
        .map {
            it.insertSeparators{ after, before ->
                if (before == null) {
                    //the end of the list
                    return@insertSeparators null
                }

                val afterDateStr = after?.createdDate
                val beforeDateStr = before.createdDate

                if (afterDateStr == null || beforeDateStr == null)
                    return@insertSeparators null

                val afterDate = DateUtil.parseAsCalendar(afterDateStr)?.cleanTime()?.time ?: 0
                val beforeDate = DateUtil.parseAsCalendar(beforeDateStr)?.cleanTime()?.time ?: 0

                if (afterDate > beforeDate) {
                    CommunicationHistoryModel.Header( DateUtil.format(Date(beforeDate))) // dd.MM.yyyy
                } else {
                    // no separator
                    null
                }
            }
        }

cleanTime 对于 grouping 是必需的 dd.MM.yyyy 忽略时间

fun Calendar.cleanTime(): Date {
    set(Calendar.HOUR_OF_DAY, 0)
    set(Calendar.MINUTE, 0)
    set(Calendar.SECOND, 0)
    set(Calendar.MILLISECOND, 0)
    return this.time
}