У меня есть список с 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)
}
Вам необходимо уведомить 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)
}
}
Вам не нужно уведомлять адаптер об изменении, потому что пользовательский интерфейс уже изменен к моменту вызова прослушивателя, и когда элемент прокручивается за пределы экрана и обратно, onBindViewHolder
все равно будет вызываться снова для элемента. Уведомление — это принудительное onBindViewHolder
обращение к элементу, но в этом нет необходимости, если он уже был привязан, а флажок был изменен вручную.
@ Abdo21 Abdo21 Это редактирование совершенно не нужно. Он делает то же самое, что и код OP, за исключением более подробного описания.
У меня есть теория о том, что происходит не так, но я ее не проверял. Это связано с этим кодом:
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
. Большое спасибо!
Он должен быть доступен: developer.android.com/reference/androidx/recyclerview/widget/… Убедитесь, что у вас установлены последние версии библиотек Jetpack.
Спасибо за вашу помощь @Abdo21. Я уже сделал это, и я получил эту ошибку:
java.lang.IllegalStateException: Cannot call this method while RecyclerView is computing a layout or scrolling androidx.recyclerview.widget.RecyclerView
. Затем я попытался применить это решение: здесь, которое тоже не сработало.