RecyclerView с CheckBox не сохраняет выбранные элементы

У меня есть список с RecyclerView + Adapter + ViewHolder, и каждый список элементов содержит описание и флажок. Если я выбираю элемент и прокручиваю список вниз, RecyclerView не сохраняет выбор, когда я снова прокручиваю вверх.

Вот мой код:

class MainActivity : AppCompatActivity() {

    private lateinit var binding: ActivityMainBinding
    private lateinit var adapter: MyAdapter
    private val items = mutableListOf<Item>()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        adapter = MyAdapter()
        binding.recyclerView.adapter = adapter
    }

    override fun onResume() {
        super.onResume()

        for (i in 1..100) {
            items.add(Item("Item $i", false))
        }
    }

    inner class MyAdapter : RecyclerView.Adapter<MyViewHolder>() {

        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
            val view = LayoutInflater.from(parent.context).inflate(R.layout.item_layout, parent, false)
            return MyViewHolder(view)
        }

        override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
            val item = items[position]
            holder.textView.text = item.description
            holder.checkBox.isChecked = item.isSelected
            holder.checkBox.setOnCheckedChangeListener {_, isChecked ->
                item.isSelected = isChecked
                holder.checkBox.isChecked = isChecked
            }
        }

        override fun getItemCount(): Int {
            return items.size
        }
    }

    inner class MyViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {

        val textView: TextView = itemView.findViewById(R.id.textView)
        val checkBox: CheckBox = itemView.findViewById(R.id.checkBox)
    }

    data class Item(val description: String, var isSelected: Boolean)
}
0
0
97
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Вам необходимо уведомить RecyclerView об изменениях. В вашем случае вы можете вызвать notifyDataSetChanged() внутри setOnCheckedChangeListener вашего CheckBox, чтобы указать RecyclerView обновить все его содержимое:

holder.checkBox.setOnCheckedChangeListener {_, isChecked ->
    //...
    notifyDataSetChanged()
}

Имейте в виду, что notifyDataSetChanged() — это простой, но менее эффективный способ обновления RecyclerView. Если вы обновляете только определенный элемент в списке, рассмотрите возможность использования вместо него notifyItemChanged(position). Таким образом, RecyclerView будет обновлять только затронутый элемент, что приведет к повышению производительности.

Обновлено: вам также нужно использовать эти две строки в конструкторе:

val textView: TextView = itemView.findViewById(R.id.textView)
val checkBox: CheckBox = itemView.findViewById(R.id.checkBox)

Так:

inner class MyViewHolder(view: View) : RecyclerView.ViewHolder(view) {
    val textView: TextView
    val checkBox: CheckBox

    init {
        textView = itemView.findViewById(R.id.textView)
        checkBox = itemView.findViewById(R.id.checkBox)
    }
}

Спасибо за вашу помощь @Abdo21. Я уже сделал это, и я получил эту ошибку: java.lang.IllegalStateException: Cannot call this method while RecyclerView is computing a layout or scrolling androidx.recyclerview.widget.RecyclerView. Затем я попытался применить это решение: здесь, которое тоже не сработало.

Anderson Gabriel Ferreira 19.04.2023 03:53

Вам не нужно уведомлять адаптер об изменении, потому что пользовательский интерфейс уже изменен к моменту вызова прослушивателя, и когда элемент прокручивается за пределы экрана и обратно, onBindViewHolder все равно будет вызываться снова для элемента. Уведомление — это принудительное onBindViewHolder обращение к элементу, но в этом нет необходимости, если он уже был привязан, а флажок был изменен вручную.

Tenfour04 19.04.2023 04:01

@ Abdo21 Abdo21 Это редактирование совершенно не нужно. Он делает то же самое, что и код OP, за исключением более подробного описания.

Tenfour04 19.04.2023 04:30
Ответ принят как подходящий

У меня есть теория о том, что происходит не так, но я ее не проверял. Это связано с этим кодом:

holder.checkBox.isChecked = item.isSelected
holder.checkBox.setOnCheckedChangeListener {_, isChecked ->
    item.isSelected = isChecked
    holder.checkBox.isChecked = isChecked
}

Когда вы впервые привязываете держатель представления, к нему добавляется этот прослушиватель изменений. Слушатель изменений фиксирует ссылку на текущий item.

Когда держатель представления прокручивается с экрана, а затем возвращается на экран, старый слушатель все еще подключен и захватывает старый item. Поэтому, когда вы вызываете holder.checkBox.isChecked = item.isSelected, вы можете активировать старый слушатель, который мутирует старый item в неправильное состояние.

Поэтому, чтобы исправить это, установите прослушиватель перед обновлением состояния флажка. Вы также можете удалить бесполезную строку holder.checkBox.isChecked = isChecked. Состояние флажка автоматически обновляется до нового состояния перед вызовом прослушивателя.

Исправлена ​​версия приведенного выше кода:

holder.checkBox.setOnCheckedChangeListener {_, isChecked ->
    item.isSelected = isChecked
}
holder.checkBox.isChecked = item.isSelected

Альтернативное решение — настроить прослушиватель кликов только один раз при создании ViewHolder. В любом случае это более эффективно, потому что не нужно выделять новый слушатель каждый раз, когда представление прокручивается на экране. Для этого мы находим текущий элемент в момент вызова слушателя с помощью absoluteAdapterPosition. Тогда он не захватывает конкретный item, поэтому один и тот же слушатель можно использовать в любое время.

inner class MyViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {

    val textView: TextView = itemView.findViewById(R.id.textView)
    val checkBox: CheckBox = itemView.findViewById(R.id.checkBox).apply {
        setOnCheckedChangeListner { _, isChecked ->
            items[absoluteAdapterPosition].isSelected = isChecked
        }
    }
}

Спасибо за помощь @Tenfour04! ваша теория была верна, я изменил порядок и вызвал слушателя перед обновлением, и это сработало. Меня просто заинтересовал второй подход, мне нужно создать переменную absoluteAdapterPosition? Потому что, если я просто добавлю его в код, Android Studio вернет мне Unresolved reference, я также пробовал с adapterPositionAbsolute. Большое спасибо!

Anderson Gabriel Ferreira 19.04.2023 21:26

Он должен быть доступен: developer.android.com/reference/androidx/recyclerview/widget‌​/… Убедитесь, что у вас установлены последние версии библиотек Jetpack.

Tenfour04 19.04.2023 21:31

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