我在recyclerview中有一个5个元素的列表,像一个待办事项列表一样设置。每行复选框上都有一个侦听器,对于此最小可复制示例,每当您选中任何复选框时,它都会随机设置5个复选框的值。取消选中某个项目时,它应该以黑色文本显示,选中一个项目时,它应该以灰色文本和斜体显示。
当我选中一个框并重置值时,通常UI会按预期更新。但是,有时某个项目的布局错误,因此该复选框显示正确的值,但是文本样式错误。为什么此行为不一致,如何确保每次刷新UI?
这是整个MRE:
MainActivity.kt
".+_.+_p\\d+$"
ToDoItem.kt
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.databinding.DataBindingUtil
import androidx.recyclerview.widget.LinearLayoutManager
import com.dalydays.android.mre_recyclerview_refresh_last_item.databinding.ActivityMainBinding
import kotlin.random.Random
class MainActivity : AppCompatActivity() {
private lateinit var adapter: ToDoAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding: ActivityMainBinding = DataBindingUtil.setContentView(this, R.layout.activity_main)
binding.lifecycleOwner = this
binding.itemsList.layoutManager = LinearLayoutManager(this)
val onCheckboxClickListener: (ToDoItem) -> Unit = { _ ->
adapter.submitList(getSampleList())
}
adapter = ToDoAdapter(onCheckboxClickListener)
binding.itemsList.adapter = adapter
adapter.submitList(getSampleList())
}
private fun getSampleList(): List<ToDoItem> {
val sampleList = mutableListOf<ToDoItem>()
sampleList.add(ToDoItem(id=1, description = "first item", completed = Random.nextBoolean()))
sampleList.add(ToDoItem(id=2, description = "second item", completed = Random.nextBoolean()))
sampleList.add(ToDoItem(id=3, description = "third item", completed = Random.nextBoolean()))
sampleList.add(ToDoItem(id=4, description = "fourth item", completed = Random.nextBoolean()))
sampleList.add(ToDoItem(id=5, description = "fifth item", completed = Random.nextBoolean()))
return sampleList
}
}
ToDoAdapter.kt
data class ToDoItem(
var id: Long? = null,
var description: String,
var completed: Boolean = false
)
activity_main.xml
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import com.dalydays.android.mre_recyclerview_refresh_last_item.databinding.ChecklistItemCheckedBinding
import com.dalydays.android.mre_recyclerview_refresh_last_item.databinding.ChecklistItemUncheckedBinding
const val ITEM_UNCHECKED = 0
const val ITEM_CHECKED = 1
class ToDoAdapter(private val onCheckboxClick: (ToDoItem) -> Unit): ListAdapter<ToDoItem, RecyclerView.ViewHolder>(ToDoItemDiffCallback()) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return when (viewType) {
ITEM_CHECKED -> ViewHolderChecked.from(parent)
else -> ViewHolderUnchecked.from(parent)
}
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val toDoItem = getItem(position)
when (holder) {
is ViewHolderChecked -> {
holder.bind(toDoItem, onCheckboxClick)
}
is ViewHolderUnchecked -> {
holder.bind(toDoItem, onCheckboxClick)
}
}
}
override fun getItemViewType(position: Int): Int {
val toDoItem = getItem(position)
return when (toDoItem.completed) {
true -> ITEM_CHECKED
else -> ITEM_UNCHECKED
}
}
class ViewHolderChecked private constructor(private val binding: ChecklistItemCheckedBinding)
: RecyclerView.ViewHolder(binding.root) {
fun bind(toDoItem: ToDoItem, onCheckboxClick: (ToDoItem) -> Unit) {
binding.todoItem = toDoItem
binding.checkboxCompleted.setOnClickListener {
onCheckboxClick(toDoItem)
}
binding.executePendingBindings()
}
companion object {
fun from(parent: ViewGroup): ViewHolderChecked {
val layoutInflater = LayoutInflater.from(parent.context)
return ViewHolderChecked(ChecklistItemCheckedBinding.inflate(layoutInflater, parent, false))
}
}
}
class ViewHolderUnchecked private constructor(private val binding: ChecklistItemUncheckedBinding)
: RecyclerView.ViewHolder(binding.root) {
fun bind(toDoItem: ToDoItem, onCheckboxClick: (ToDoItem) -> Unit) {
binding.todoItem = toDoItem
binding.checkboxCompleted.setOnClickListener {
onCheckboxClick(toDoItem)
}
binding.executePendingBindings()
}
companion object {
fun from(parent: ViewGroup): ViewHolderUnchecked {
val layoutInflater = LayoutInflater.from(parent.context)
return ViewHolderUnchecked(ChecklistItemUncheckedBinding.inflate(layoutInflater, parent, false))
}
}
}
}
class ToDoItemDiffCallback : DiffUtil.ItemCallback<ToDoItem>() {
override fun areItemsTheSame(oldItem: ToDoItem, newItem: ToDoItem): Boolean {
return oldItem.id == newItem.id
}
override fun areContentsTheSame(oldItem: ToDoItem, newItem: ToDoItem): Boolean {
return oldItem == newItem
}
}
checklist_item_checked.xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
</data>
<RelativeLayout
android:id="@+id/linearLayout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/items_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingStart="8dp"
android:paddingEnd="8dp"
android:scrollbars="none" />
</RelativeLayout>
</layout>
checklist_item_unchecked.xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="todoItem"
type="com.dalydays.android.mre_recyclerview_refresh_last_item.ToDoItem" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<CheckBox
android:id="@+id/checkbox_completed"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:checked="@{todoItem.completed}"
android:textAppearance="?attr/textAppearanceListItem"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/tv_description"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:ellipsize="end"
android:text="@{todoItem.description}"
android:textAppearance="?attr/textAppearanceListItem"
android:textColor="#65000000"
android:textStyle="italic"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/checkbox_completed"
app:layout_constraintTop_toTopOf="parent"
tools:text="Mow the lawn" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
答案 0 :(得分:0)
修改这些方法
override fun areItemsTheSame(oldItem: ToDoItem, newItem: ToDoItem): Boolean {
return oldItem.id == newItem.id
}
override fun areContentsTheSame(oldItem: ToDoItem, newItem: ToDoItem): Boolean {
return ((oldItem.id == newItem.id) && (oldItem.description == newItem.description) && (oldItem.completed == newItem.completed)
}
还覆盖方法getNewListSize()
和getOldListSize()
答案 1 :(得分:0)
也许不是“最佳”解决方案,但这是我想出的。我确定复选框动画和recyclerview刷新动画之间的时间安排可能存在问题。有时,根据recyclerview进行比较所花费的时间,recyclerview可以尝试在复选框完成动画制作之前或之后刷新。之前完成时,复选框动画将阻止recyclerview动画并使UI处于错误状态。否则,它似乎可以正常工作。
我决定手动运行adapter.notifyItemChanged(position)
,即使应该RecyclerView.ListAdapter
自动处理它。根据差异计算的完成时间,它的动画效果仍然有些不一致,但这比使UI处于不良状态要好得多,并且比每次使用notifyDataSetChanged()
刷新整个列表要好得多。
在MainActivity中,将复选框侦听器更改为此:
val onCheckboxClickListener: (ToDoItem, Int) -> Unit = { _, position ->
adapter.submitList(getSampleList())
adapter.notifyItemChanged(position)
}
在ToDoAdapter中,将类标题更改为此:
class ToDoAdapter(private val onCheckboxClick: (ToDoItem, Int) -> Unit): ListAdapter<ToDoItem, RecyclerView.ViewHolder>(ToDoItemDiffCallback()) {
并将两个bind()函数都更改为此:
fun bind(toDoItem: ToDoItem, onCheckboxClick: (ToDoItem, Int) -> Unit) {
binding.todoItem = toDoItem
binding.checkboxCompleted.setOnClickListener {
onCheckboxClick(toDoItem, layoutPosition)
}
binding.executePendingBindings()
}