Лучшая практика использования RecyclerView, ListAdapter и ViewModel для сохранения проверенного статуса

Я использую RecyclerView с ListAdapter, который использует DiffUtil для внутреннего отображения данных. Конечно, все данные были предоставлены ViewModel через LiveData.

Теперь в каждом представлении элемента есть флажок, а я реализовал функцию выбора перетаскиванием, добавив RecyclerView.SimpleOnItemTouchListener. Статус выбора теперь поддерживается адаптером, это не лучшая идея, так как статус будет утерян после поворота экрана.

Я хочу, чтобы ViewModel сохранял статус выбора, и это моя идея:

  1. Фрагмент вызовет ViewModel для изменения статуса при получении соответствующего обратного вызова.
  2. ViewModel изменит список данных (или создаст новый, если он неизменяемый) и опубликует в LiveData.
  3. Фрагмент получает изменение данных и отправляет его ListAdapter.

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

Основная идея MVVM - это представление, управляемое данными, но в этом случае мы потеряли дополнительную информацию (например, позицию), что привело к дополнительным вычислениям. Итак, какова лучшая практика?

Часть кода адаптера (на основе GroupListAdapter, который расширяет ListAdapter):

    private val selectedGroups = mutableSetOf<Int>()
    private val selectedImages = mutableSetOf<Image>()

    override fun onBindGroupViewHolder(holder: StubViewHolder, groupIndex: Int, flattedPosition: Int) {
        holder.setText(R.id.itemImageTitleTextView, getGroupItem(groupIndex).title)
    }

    override fun onBindChildViewHolder(holder: StubViewHolder, groupIndex: Int, childIndex: Int, flattedPosition: Int) {
        val data = getChildItem(groupIndex, childIndex)
        GlideApp.with(fragment).load(data.uri).into(holder.getView(R.id.itemImageView))
    }

    override fun onBindGroupViewHolder(holder: StubViewHolder, groupIndex: Int, flattedPosition: Int,
                                       payloads: MutableList<Any>) {
        holder.setGroupSelectionMode(isInSelectMode())
        holder.setGroupChecked(selectedGroups.contains(flattedPosition))
    }

    override fun onBindChildViewHolder(holder: StubViewHolder, groupIndex: Int, childIndex: Int, flattedPosition: Int,
                                       payloads: MutableList<Any>) {
        if (isInSelectMode()) {
            holder.setImageSelectionMode(true)
            if (selectedImages.contains(getItem(flattedPosition))) {
                holder.getView<MaskImageView>(R.id.itemImageView).isChecked = true
                holder.setImageChecked(true)
            } else {
                holder.getView<MaskImageView>(R.id.itemImageView).isChecked = false
                holder.setImageChecked(false)
            }
        } else {
            holder.getView<MaskImageView>(R.id.itemImageView).isChecked = false
            holder.setImageSelectionMode(false)
        }
    }

    fun setSelected(position: Int, isSelected: Boolean, refreshItem: Boolean = true) {
        if (!isChildItem(position)) {
            return
        }
        if (isSelected) {
            selectedImages.add(getItem(position) as Image)
        } else {
            selectedImages.remove(getItem(position))
        }
        if (refreshItem) {
            notifyItemChanged(position, true)
        }
        onSelectChangedCallback?.invoke(selectedImages.size)
    }

person Chenhe    schedule 19.02.2020    source источник
comment
Это интересно. Выложите пожалуйста свой адаптер? RecyclerView обычно отображает не полный набор данных, а только то, что умещается на экране, плюс некоторые дополнительные. Кроме того, о каком объеме данных мы говорим?   -  person ror    schedule 19.02.2020
comment
@ror Я добавил несколько кодов, но думаю, что адаптер не важен. Это правда, что будет отображаться только видимый элемент, но проблема в том, что DiffUtil будет сравнивать весь список данных, чтобы вычислить разницу. Есть около 1000 ~ 4000 элементов, так как это приложение будет работать на часовых устройствах, этот размер уже не маленький.   -  person Chenhe    schedule 19.02.2020


Ответы (1)


Может я ошибаюсь, но похоже, вы не пользуетесь пейджингом? В зависимости от характера ваших данных вы можете использовать любые источники данных-потомков https://developer.android.com/reference/androidx/paging/DataSource.html. Тогда ваш DiffUtils будет работать с гораздо меньшим подмножеством данных. Вы можете смещать выбранные элементы (я предполагаю, что их может быть больше одного) для модели просмотра, сохранять их как состояние и даже динамически переносить исходные данные в некоторую оболочку с указанием выбора - внутри самого источника данных. Таким образом, ваш адаптер будет управляться исключительно данными.

person ror    schedule 19.02.2020
comment
Некоторые вопросы: 1. Пейджинг не поддерживает изменение данных после загрузки. 2. Из-за 1 я должен использовать другую переменную для сохранения состояния, это означает, что я все еще поддерживаю состояние между данными и представлением - данные не управляются. 3. В дополнение к 1, даже если использовать подкачку, данных будет все больше и больше. Предположим, пользователь загружает 10 страниц, каждая отправка по-прежнему сравнивается с общим сравнением (это способ разбиения на страницы не поддерживает изменение) - person Chenhe; 19.02.2020
comment
Но вдохновленный вами, я сохраняю выбранные элементы во ViewModle, когда onDestroy, и восстанавливаю их при воссоздании пользовательского интерфейса. Это не шаблон, управляемый данными, но он решает проблему. Я все еще ищу лучшее решение. - person Chenhe; 19.02.2020
comment
@Chenhe вы, конечно, правы в отношении неизменяемости, но вы выполняете выбор на странице, видимой пользователю, верно? Что в основном означает для меня, что я могу загрузить ту же страницу в качестве первого действия в новом листаемом списке? (developer.android.com/reference/android/arch / paging /) - person ror; 19.02.2020
comment
Кажется, это работает. Однако переход на пейджинг - это большой проект, так как у меня есть другие функции, такие как Group (выделение), FastScroll, TotalCount и т. Д. Возможно, пейджинг сломает их. На самом деле то, что я создаю, похоже на Google Фото - приложение-галерею. Я попробую это решение позже. Спасибо за такую ​​уникальную идею. - person Chenhe; 19.02.2020