更新数据导致RecyclerView(List Adapter)将数据移至列表末尾
问题描述
我正在使用List Adapter展示任务列表:
每当我更新RecyclerView中的数据时,数据会滚动至列表末尾:
同时发现,更新数据库中的数据时,Room会自动将该数据移至最后索引位置:
更新前:
更新后:
请问这是什么原因导致的?
参考代码
适配器代码
class TestAdapter(private val taskListener: DetailTaskAdapterListener) : ListAdapter<Task, TestAdapter.TestViewHolder>(DiffUtilCallback) { inner class TestViewHolder(private val binding: DetailSubTaskItemBinding) : RecyclerView.ViewHolder(binding.root) { fun bind(task: Task) { binding.checkbox.isChecked = task.isComplete binding.task.setText(task.task.trim()) } fun setupListener() { binding.checkbox.setOnCheckedChangeListener { _, isChecked -> if (isChecked) { binding.task.paintFlags = Paint.STRIKE_THRU_TEXT_FLAG } else { binding.task.paintFlags = 0 } getItem(absoluteAdapterPosition).copy(isComplete = isChecked) } binding.task.setOnFocusChangeListener { view, hasFocus -> if(!hasFocus){ if(binding.task.text.toString().trim().isEmpty()){ taskListener.deleteTask(getItem(absoluteAdapterPosition).taskId) }else{ taskListener.updateTask(getItem(absoluteAdapterPosition).taskId, binding.task.text.toString().trim()) } } } } } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TestViewHolder { val layoutInflater = LayoutInflater.from(parent.context) return TestViewHolder( DetailSubTaskItemBinding.inflate( layoutInflater, parent, false ) ) } override fun onBindViewHolder(holder: TestViewHolder, position: Int) { val task = getItem(position) holder.setupListener() holder.bind(task) } companion object DiffUtilCallback : DiffUtil.ItemCallback<Task>() { override fun areItemsTheSame(oldItem: Task, newItem: Task): Boolean { return oldItem.task == newItem.task } override fun areContentsTheSame(oldItem: Task, newItem: Task): Boolean { return oldItem == newItem } } }
DAO代码
(注:插入和更新操作都会导致RecyclerView将更新数据推至最后一行)
@Transaction @Query("SELECT * FROM todo WHERE todoId = :todoId") fun getTodoDetails(todoId: String): Flow<TodoDetailsEntity> @Insert(onConflict = OnConflictStrategy.REPLACE) suspend fun insertTask(task: TaskEntity): Long @Query("UPDATE task SET task = :title WHERE taskId = :taskId") suspend fun updateTaskTitle(taskId: String, title: String): Int
实体模型代码
data class TodoDetailsEntity( @Embedded val todo: TodoEntity, @Relation( parentColumn = "todoId", entityColumn = "todoRefId" ) val tasks: List<TaskEntity>, @Relation( parentColumn = "todoId", entityColumn = "noteId" ) val note: NoteEntity?, @Relation( parentColumn = "todoId", entityColumn = "todoRefId" ) val attachments: List<AttachmentEntity> )
Activity中submitList调用代码
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) viewModel.saveTodoId(intent.extras?.getString("todoId")!!) todoId = intent.extras?.getString("todoId") binding = ActivityDetailsBinding.inflate(layoutInflater) setContentView(binding.root) setupToolbar() setupTestAdapter() collectLatestLifecycleFlow(viewModel.uiState) { uiState -> testAdapter.submitList(uiState.todoDetails?.tasks) } }
原因分析与解决方案
核心原因1:DiffUtil的条目判断逻辑错误
当前用oldItem.task == newItem.task判断是否为同一个条目,但任务内容是可修改的——一旦任务名称改变,DiffUtil会认为这是新条目,而非原条目更新。此时ListAdapter会移除原条目、将新条目添加到列表末尾,直接触发滚动到末尾的现象。
修复方式:改用唯一标识(比如taskId)判断条目是否相同:
companion object DiffUtilCallback : DiffUtil.ItemCallback<Task>() { override fun areItemsTheSame(oldItem: Task, newItem: Task): Boolean { // 用唯一ID判断,而非可修改的任务内容 return oldItem.taskId == newItem.taskId } override fun areContentsTheSame(oldItem: Task, newItem: Task): Boolean { return oldItem == newItem } }
核心原因2:Room查询未指定固定排序规则
Room默认按数据插入顺序返回列表,执行REPLACE或UPDATE操作时,底层存储顺序可能变动,导致查询返回的任务列表顺序改变,进而触发ListAdapter提交新列表时出现位置偏移。
修复方式:给任务查询指定固定排序规则,比如按taskId或创建时间排序:
- 方式1:在
TaskEntity的@Entity注解中指定默认排序:
@Entity(tableName = "task", orderBy = "taskId ASC") data class TaskEntity( val taskId: String, val todoRefId: String, val task: String, val isComplete: Boolean // 其他字段... )
- 方式2:在
@Relation注解中直接指定排序:
@Relation( parentColumn = "todoId", entityColumn = "todoRefId", sortColumn = "taskId ASC" ) val tasks: List<TaskEntity>,
额外注意点:submitList的调用细节
collectLatestLifecycleFlow会在uiState变化时提交列表,确保提交的是新的列表实例(避免复用旧列表对象),否则DiffUtil可能无法正确计算差异。如果uiState.todoDetails?.tasks是同一个实例,建议提交前拷贝:
testAdapter.submitList(uiState.todoDetails?.tasks?.toList())
内容的提问来源于stack exchange,提问作者JV17
相关产品推荐
相关产品推荐

