简体   繁体   English

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

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

After a lot of searching, I know its possible with regular adapter, but I have no idea how to do it using Paging Library.经过大量搜索,我知道使用常规适配器是可能的,但我不知道如何使用分页库来做到这一点。 I don`t need code just a clue.我不需要代码只是一个线索。

Example例子

To add separators, you essentially have 2 options:要添加分隔符,您基本上有两个选项:

  1. View-based, you explicitly include separators as an 'item' in the list and define a new viewtype for those separators.基于视图,您明确地将分隔符作为“项目”包含在列表中,并为这些分隔符定义新的视图类型。 Allows the list to re-use the separator views but means you need to take the separators into account when defining the data.允许列表重复使用分隔符视图,但这意味着您需要在定义数据时考虑分隔符。
  2. Data-based, each item actually has a separator view, but it only shows on specific items.基于数据,每个项目实际上都有一个分隔符视图,但它只显示在特定项目上。 Based on some criteria you show or hide it whilst binding the view-holder.根据某些标准,您在绑定视图持有者时显示或隐藏它。

For the paging library only option 2 is viable since it only partially loads the data and inserting the separators becomes much more complicated.对于分页库,只有选项 2 是可行的,因为它仅部分加载数据并且插入分隔符变得更加复杂。 You will simply need to figure out a way to check if item x is a different day than item x-1 and show/hide the date section in the view depending on the result.您只需要找出一种方法来检查项目x是否与项目x-1并根据结果在视图中显示/隐藏日期部分。

I was in the same spot as you and I came up with this solution.我和你在同一个地方,我想出了这个解决方案。

One important note though, in order to implement this I had to change my date converter to the database, from long to string to store a timestamp一个重要的注意事项,为了实现这一点,我不得不将我的日期转换器更改为数据库,从 long 到 string 以存储时间戳

these are my converters这些是我的转换器

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

Some starting info though, I have a list of report headers that I wish to show , and page through and be able to filter虽然有一些起始信息,但我有一个我希望显示的报告标题列表,可以翻页并能够过滤

They are represented by this 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)

I also wanted to add separators between the items in the list to show them by date我还想在列表中的项目之间添加分隔符以按日期显示它们

I achieved this by doing the following:我通过执行以下操作实现了这一点:

I created a new query in room like this我在这样的房间中创建了一个新查询

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

This basically creates a separator line for all the items I have filtered through这基本上为我过滤的所有项目创建了一个分隔线

it also creates a dummy date for sorting (with the time of 25:60:60 so that it will always appear in front of the other reports)它还创建了一个用于排序的虚拟日期(时间为 25:60:60,以便它始终出现在其他报告的前面)

I then combine this with my list using union and sort them by the dummy date然后我使用 union 将它与我的列表结合起来,并按虚拟日期对它们进行排序

The reason I had to change from long to string is because it is much easier to create dummy dates with string in sql and seperate the date part from the whole date time我必须从 long 更改为 string 的原因是因为在 sql 中使用 string 创建虚拟日期并将日期部分与整个日期时间分开要容易得多

The above creates a list like this:上面创建了一个这样的列表:

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

In my PagedListAdapter I changed it to be an implementation of PagedListAdapter<ReportHeader, RecyclerView.ViewHolder> (not a specific viewholder)在我的 PagedListAdapter 中,我将其更改为PagedListAdapter<ReportHeader, RecyclerView.ViewHolder> (不是特定的viewholder)的实现

Added to the companion object:添加到伴生对象:

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

and overrode get view type like so:并像这样覆盖 get 视图类型:

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

I then created two seperate view holders :然后我创建了两个单独的视图持有者:

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

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

and implemented the other overriden methods like so:并实现了其他覆盖方法,如下所示:

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()
            }
        }
    }
}

I hope this helps and inspires people to find even better solutions我希望这有助于并激励人们找到更好的解决方案

You can achieve the same result using insertSeparators in Paging 3 library.您可以使用Paging 3库中的insertSeparators获得相同的结果。 Make sure your items are sorted by date.确保您的物品sorted日期sorted

Inside or viewmodel retrieve a Pager something like that在内部或viewmodel检索类似这样的Pager

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

After all insert separators like a header毕竟像标题一样insert separators

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 is required for grouping by dd.MM.yyyy ignoring time 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
}

When binding the data pass in the previous item as well绑定数据时也传入前一项

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

Every view then sets a header, which is only made visible if the previous item doesn't have the same header.然后每个视图设置一个标题,只有在前一项没有相同标题时才可见。

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

As mentioned here , Paging Library works with PagedListAdapter . 如前所述这里 ,寻呼图书馆工作与PagedListAdapter And PagedListAdapter extends from RecyclerView.Adapter, so you can do this simply like in Recyclerview ( example ). PagedListAdapter从RecyclerView.Adapter扩展,因此您可以像在Recyclerview( 示例 )中那样完成此操作。 Just in two words - you need to use different view types for your date headers and content items. 只用两个字 - 您需要为日期标题和内容项使用不同的视图类型。

Kiskae's answer is excellent and for your case option 2 probably works well. Kiskae 的回答非常好,对于您的情况,选项 2 可能效果很好。

In my case I wanted to have one additional item that wasn't in the database, like this:在我的情况下,我想要一个不在数据库中的附加项目,如下所示:

  • Show all显示所有
  • Item 1第 1 项
  • Item 2第 2 项

It needed to be clickable as well.它也需要可点击。 There's the usual way of overriding getItemCount to return +1 and offsetting positions for the other methods.通常的方法是覆盖getItemCount以返回 +1 并偏移其他方法的位置。

But I stumbled on another way that I haven't seen documented yet that might be useful for some cases.但是我偶然发现了另一种我还没有看到记录的方法,它可能对某些情况有用。 You might be able to incorporate additional elements into your query using union :您可以使用union将其他元素合并到您的查询中:

@Query("select '' as name, 0 as id " +
        "union " +
        "select name, id from user " +
        "order by 1 asc")
DataSource.Factory<Integer, User> getAllDataSource();

That means the data source actually returns another item in the beginning, and there's no need to adjust positions.这意味着数据源实际上在开始时返回了另一个项目,无需调整位置。 In your adapter, you can check for that item and handle it differently.在您的适配器中,您可以检查该项目并以不同方式处理它。

In your case the query would have to be different but I think it would be possible.在您的情况下,查询必须有所不同,但我认为这是可能的。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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