Как добавить разделители дат в режиме ресайклера с помощью библиотеки подкачки?

После долгих поисков я знаю, что это возможно с обычным адаптером, но я понятия не имею, как это сделать с помощью библиотеки подкачки. Мне не нужен код, просто подсказка.

Пример
Как добавить разделители дат в режиме ресайклера с помощью библиотеки подкачки?

21
0
6 338
5
Перейти к ответу Данный вопрос помечен как решенный

Ответы 5

Ответ принят как подходящий

Чтобы добавить разделители, у вас есть 2 варианта:

  1. На основе представления вы явно включаете разделители в качестве «элемента» в список и определяете новый тип представления для этих разделителей. Позволяет списку повторно использовать представления разделителей, но означает, что вам необходимо учитывать разделители при определении данных.
  2. На основе данных каждый элемент фактически имеет вид разделителя, но он отображается только на определенных элементах. По некоторым критериям вы показываете или скрываете его при привязке держателя представления.

Для библиотеки подкачки возможен только вариант 2, поскольку он загружает данные лишь частично, и вставка разделителей становится намного сложнее. Вам просто нужно будет найти способ проверить, является ли элемент x другим днем, чем элемент x-1, и показать / скрыть раздел даты в представлении в зависимости от результата.

Если мне нужно использовать вариант 1, есть идеи?

Stony 03.06.2019 08:27

Отличный ответ, и если мы примем во внимание разделение проблем, вариант 2 - единственный выход. DataSource не должен знать, что View хочет отображать заголовки, он должен только получать данные.

rewgoes 13.02.2020 06:15

По варианту 2 есть несколько вопросов: - Что делать, если элемент, имеющий «заголовок», удален из БД? - Что делать, если новый элемент добавлен в середину списка? - Что делать, если в элементе, содержащем «заголовок», изменилось поле даты?

Sirojiddin Komolov 13.02.2020 14:54

При связывании данных передается также и предыдущий элемент.

  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, вероятно, работает хорошо.

В моем случае я хотел иметь еще один элемент, которого не было в базе данных, например:

  • Показать все
  • Пункт 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();

Это означает, что источник данных фактически возвращает другой элемент в начале, и нет необходимости корректировать позиции. В своем адаптере вы можете проверить этот элемент и обработать его по-другому.

В вашем случае запрос должен быть другим, но я думаю, что это возможно.

Решение должно быть таким же, как сказал @Cruces.

Stony 03.06.2019 08:26

Я был в том же месте, что и вы, и я придумал это решение.

Однако одно важное замечание: чтобы реализовать это, мне пришлось изменить преобразователь даты в базу данных, с длинной строки на строку, чтобы сохранить метку времени.

это мои конвертеры

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>

Это в основном создает разделительную линию для всех элементов, которые я отфильтровал.

он также создает фиктивную дату для сортировки (со временем 25:60:60, чтобы она всегда отображалась перед другими отчетами)

Затем я объединяю это со своим списком, используя объединение, и сортирую их по фиктивной дате

Причина, по которой мне пришлось перейти с 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> (а не на конкретного зрителя)

Добавлено в сопутствующий объект:

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

Надеюсь, это поможет и вдохновит людей на поиск еще лучших решений.

Спасибо за отличную работу! Но ведь в GROUP BY должно быть какое-нибудь имя столбца, не так ли?

LIFED 30.05.2019 11:12

group by - это в основном тип разделителя, который вы хотите добавить, у меня была дата, поэтому я сгруппировал по дате, если вы хотите сделать что-то вроде телефонного каталога, вы можете группировать по substr (upper (name) 0,1)

Cruces 30.05.2019 13:35

Я не понимаю. Согласно документации, это работает следующим образом: GROUP BY [column_name]. Substr возвращает только строку, не связанную с именами столбцов. Или я что-то упустил?

LIFED 30.05.2019 13:47

о, извините, я не понял ваш вопрос, да, группа by, похоже, тоже работает с substr и другими функциями, в основном она оценивает функцию для каждой строки, а затем группирует их вместе в соответствии с этим значением, если вы просто добавили имя столбца, это будет выполнять группу в соответствии со значением этого столбца

Cruces 30.05.2019 23:32

Я думаю, что Group By должна избегать дублирования HEADER.

Stony 03.06.2019 08:23

да, конечно, если вы не группируете по, тогда вы получаете один заголовок для каждой строки в вашей базе данных, нам нужен один заголовок для каждой группы строк с одинаковой датой

Cruces 03.06.2019 11:34

Спасибо за ответ, очень полезно! Я застрял с той же проблемой, и теперь я рассматриваю возможность использования опубликованного вами подхода или использования версии v3 (альфа) библиотеки подкачки, где они добавили API insertSeparators ().

XZen 27.01.2021 19:46

библиотека подкачки в настоящее время лучше, я бы порекомендовал (если у вас есть время изучить ее) использовать ее, это то, что я использую с этого момента

Cruces 28.01.2021 09:11

Вы можете добиться того же результата, используя insertSeparators в библиотеке Пейджинг 3. Убедитесь, что ваши товары имеют 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 вроде заголовок

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
}

он будет работать только для разделителей в середине элементов списка, если вы хотите, чтобы разделитель тоже был вверху, добавьте его if (after == null) {CommunicationHistoryModel.Header (DateUtil.format (Date (beforeDate)))}

Daniyal Javaid 22.06.2021 12:20

Другие вопросы по теме