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

Android 如何使用分页库在回收器视图中添加日期分隔符?,android,android-recyclerview,kotlin,android-paging,Android,Android Recyclerview,Kotlin,Android Paging,经过大量搜索,我知道使用常规适配器是可能的,但我不知道如何使用分页库来实现。我不需要密码,只需要一个线索 示例 要添加分隔符,您基本上有两个选项: 基于视图,可以将分隔符显式地作为“项”包含在列表中,并为这些分隔符定义新的viewtype。允许列表重新使用分隔符视图,但这意味着您在定义数据时需要考虑分隔符 基于数据,每个项目实际上都有一个分隔符视图,但它仅显示在特定项目上。根据某些条件,您可以在绑定视图持有者时显示或隐藏它 对于分页库,只有选项2是可行的,因为它仅部分加载数据,插入分隔符变得更加

经过大量搜索,我知道使用常规适配器是可能的,但我不知道如何使用分页库来实现。我不需要密码,只需要一个线索

示例

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

  • 基于视图,可以将分隔符显式地作为“项”包含在列表中,并为这些分隔符定义新的viewtype。允许列表重新使用分隔符视图,但这意味着您在定义数据时需要考虑分隔符
  • 基于数据,每个项目实际上都有一个分隔符视图,但它仅显示在特定项目上。根据某些条件,您可以在绑定视图持有者时显示或隐藏它

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

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

      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)
      }
    
    然后,每个视图都设置一个标题,该标题仅在上一个项目没有相同标题时才可见

        val previousHeader =  previousItem?.name?.capitalize().first()
        val header = item?.name?.capitalize()?.first()
        view.cachedContactHeader.text = header
        view.cachedContactHeader.isVisible  = previousHeader != header
    
    非常好,对于您的情况,选项2可能运行良好

    在我的例子中,我希望有一个数据库中没有的附加项,如下所示:

    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
    
    • 全部展示
    • 项目1
    • 项目2
    它也需要是可点击的。通常的方法是覆盖
    getItemCount
    以返回+1,并对其他方法的位置进行偏移

    但我无意中发现了另一种我还没有看到文档记录的方法,这种方法可能对某些情况有用。您可以使用
    union
    将其他元素合并到查询中:

    @Query("select '' as name, 0 as id " +
            "union " +
            "select name, id from user " +
            "order by 1 asc")
    DataSource.Factory<Integer, User> getAllDataSource();
    
    @Query(“选择“”作为名称,选择0作为id”+
    “工会”+
    从用户选择名称、id+
    “由1 asc订购”)
    工厂getAllDataSource();
    
    这意味着数据源实际上在开始时返回另一项,并且不需要调整位置。在适配器中,您可以检查该项并以不同方式处理它


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

    我与您处于同一位置,我提出了这个解决方案

    但有一个重要的注意事项是,为了实现这一点,我必须将日期转换器更改为数据库,从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)
        }
    }
    
    虽然有一些起始信息,但我有一个我希望显示的报告标题列表,可以翻页并进行筛选

    它们由以下对象表示:

    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>
    
    在我的PagedListAdapter中,我将其更改为
    PagedListAdapter
    (不是特定的视图持有者)的实现

    添加到伴生对象:

    companion object {
        private val EMPTY_ID = UUID(0L,0L)
        private const val LABEL = 0
        private const val HEADER = 1
    }
    
    并重写get view type,如下所示:

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

    我希望这有助于并激励人们找到更好的解决方案

    使用第3页库中的
    插入分隔符
    ,可以达到同样的效果。 确保您的物品按日期排序

    内部或
    viewmodel
    检索
    Pager
    类似的内容

    private val communicationResult: Flow<PagingData<CommunicationHistoryItem>> = Pager(
        PagingConfig(
            pageSize = 50,
            enablePlaceholders = false,
            maxSize = 400,
            initialLoadSize = 50
        )
    ) {
        CommunicationPagingSource(repository)
    }.flow.cachedIn(viewModelScope)
    
    根据
    dd.MM.yyyy
    忽略时间对
    分组
    需要
    cleanTime

    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
    }
    

    谢谢你的出色工作!但是GroupBy中应该有任何列名,不是吗?GroupBy基本上就是你要添加的分隔符的类型,我的是日期,所以我按日期分组,如果你想做电话目录之类的事情,你可以按substr分组(上限(名称)0,1),我不明白。根据文档,它是如何工作的:按[column_name]分组。Substr只返回与列名无关的字符串。或者我遗漏了什么?哦,对不起,我不明白你的问题,是的,group by似乎也使用substr和其他函数,基本上它计算每行的函数,然后根据该值将它们分组,如果您只是添加了列名,它将根据该列的值执行分组。我认为分组方式是为了避免重复的标题。解决方案应该与@Cruces所说的相同。如果我必须使用选项1,有什么想法吗?很好的答案,如果我们考虑到关注点的分离,选项2是唯一的方法。数据源不应该知道视图想要显示标题,它应该只获取数据。在2选项上有几个问题:-如果从数据库中删除了具有“标题”的项怎么办?-如果将新项目添加到列表的中间,该怎么办如果包含“标题”的项目中的日期字段发生更改,该怎么办?
    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
    }