调用 LiveData onChanged() 方法时,ListAdapter 项目呈现不正确。 DiffUtil areContentsTheSame() 可能存在问题?

ListAdapter items rendered incorrectly when LiveData onChanged() method is called. Possible issue with DiffUtil areContentsTheSame()?

我目前正在创建一个 Android 应用程序来帮助您完成任务。我将 LiveData 与列出 Fragment 和 DiffUtil 中所有不同任务的 ListAdapter 一起使用,以测试 ListAdapter 中的更改。在我解释我的问题之前:这里有一些相关代码:

SubTaskFragment.java:

public class SubTaskFragment extends Fragment {
private RecyclerView recyclerView;

private SubTaskAdapter adapter;
private SubTaskViewModel viewModel;

private static final String TAG = "SubTaskFragment";

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
                         Bundle savedInstanceState) {
    // Inflate the layout for this fragment and setup recycler view
    View rootView = inflater.inflate(R.layout.fragment_sub_task, container, false);
    recyclerView = (RecyclerView) rootView.findViewById(R.id.sub_task_recycler_view);
    recyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));

    //set adapter
    adapter = new SubTaskAdapter(getContext());
    recyclerView.setAdapter(adapter);

    viewModel = ViewModelProviders.of(this).get(SubTaskViewModel.class);
    viewModel.getSubTasks().observe(getViewLifecycleOwner(), new Observer<List<SubTask>>() {
        @Override
        public void onChanged(@Nullable List<SubTask> subTasks) {
            //Update RecyclerView
            Log.d(TAG, "onChanged: subTasks length = " + subTasks.size());
            adapter.submitList(subTasks);
        }
    });

SubTaskAdapter.java

public class SubTaskAdapter extends ListAdapter<SubTask, SubTaskAdapter.ViewHolder> {
private static final String TAG = "SubTaskAdapter";

private SubTaskViewModel subTaskViewModel;

public SubTaskAdapter(Context context) {
    super(DIFF_CALLBACK);
    this.context = context;
}

private static final DiffUtil.ItemCallback<SubTask> DIFF_CALLBACK = new DiffUtil.ItemCallback<SubTask>() {
    @Override
    public boolean areItemsTheSame(@NonNull SubTask oldTask, @NonNull SubTask newTask) {
        Log.d(TAG, "areItemsTheSame: oldTask name: " + oldTask.getName() + " id: " + oldTask.getId() + " ### newTask name: " + newTask.getName() + " id: " + newTask.getId());
        return oldTask.getId() == newTask.getId();
    }

    @Override
    public boolean areContentsTheSame(@NonNull SubTask oldTask, @NonNull SubTask newTask) {
        Log.d(TAG, "areContentsTheSame: oldTask name: " + oldTask.getName() + " ### newTask name: " + newTask.getName());
        boolean contentsSame = oldTask.getName().equals(newTask.getName()) &&
                oldTask.getDueDate().equals(newTask.getDueDate()) &&
                oldTask.isCompleted() == newTask.isCompleted() &&
                oldTask.getMainTaskId() == (newTask.getMainTaskId());
        Log.d(TAG, "areContentsTheSame: = " + contentsSame);
        return contentsSame;
    }
};

@Override
public void onBindViewHolder(@NonNull ViewHolder viewHolder, int i) {
    SubTask subTask = getItem(i);
    Log.d(TAG, "onBindViewHolder: pos: " + i + " name: " + subTask.getName() + " Is overdue: " + subTask.isOverdue());

    Log.d(TAG, "onBindViewHolder: " + subTask.getName() + " isOverdue = " + subTask.isOverdue());
    //SET BACKGROUND COLOR OF LIST ITEM
    if (subTask.isOverdue()) {
        viewHolder.card.setCardBackgroundColor(context.getResources().getColor(R.color.red));
        viewHolder.subTaskName.setTextColor(context.getResources().getColor(R.color.colorAccent));
        viewHolder.mainTaskName.setTextColor(context.getResources().getColor(R.color.colorAccent));
    }

    if (subTask.isCompleted()) {
        viewHolder.checkBox.setChecked(true);
        Log.d(TAG, "onBindViewHolder: " + subTask.getName() + " set to checked");
    } else {
        viewHolder.checkBox.setChecked(false);
        Log.d(TAG, "onBindViewHolder: " + subTask.getName() + " set to unchecked");
    }

    viewHolder.checkBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() 
    {
        @Override
        public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
            Log.d(TAG, "onCheckedChanged: called for subTask " + subTask.getName() + " isChecked = " + isChecked);
            subTaskViewModel = ViewModelProviders.of((FragmentActivity) context).get(SubTaskViewModel.class);
            if (isChecked) {
                subTask.setCompleted(true);
                subTaskViewModel.updateSubTask(subTask);
            } else {
                subTask.setCompleted(false);
                subTaskViewModel.updateSubTask(subTask);
            }
        }
    });

我遇到的第一个问题是当用户选中列表项的复选框时,列表项变为灰色 like so。这不是预期的行为,因为列表项的颜色不应该在检查时改变。取消选中时不会发生这种情况。

这是此操作的日志:

2020-01-19 15:24:47.572 27375-27375/com.johnsorhannus.divideandconquer D/SubTaskAdapter: onCheckedChanged: called for subTask Test 1 isChecked = true
2020-01-19 15:24:47.575 27375-27375/com.johnsorhannus.divideandconquer D/SubTaskViewModel: updateSubTask: CALLED for Test 1 isCompleted = true isOverdue = false
2020-01-19 15:24:47.602 27375-27375/com.johnsorhannus.divideandconquer D/SubTaskFragment: onChanged: subTasks length = 5
2020-01-19 15:24:47.603 27375-27409/com.johnsorhannus.divideandconquer D/SubTaskAdapter: areItemsTheSame: oldTask name: Wash car id: 3 ### newTask name: Wash car id: 3
2020-01-19 15:24:47.603 27375-27409/com.johnsorhannus.divideandconquer D/SubTaskAdapter: areItemsTheSame: oldTask name: Read OS book id: 2 ### newTask name: Read OS book id: 2
2020-01-19 15:24:47.603 27375-27409/com.johnsorhannus.divideandconquer D/SubTaskAdapter: areItemsTheSame: oldTask name: Read database book id: 1 ### newTask name: Read database book id: 1
2020-01-19 15:24:47.603 27375-27409/com.johnsorhannus.divideandconquer D/SubTaskAdapter: areItemsTheSame: oldTask name: Test 1 id: 4 ### newTask name: Test 1 id: 4
2020-01-19 15:24:47.603 27375-27409/com.johnsorhannus.divideandconquer D/SubTaskAdapter: areItemsTheSame: oldTask name: Test 2 id: 5 ### newTask name: Test 2 id: 5
2020-01-19 15:24:47.603 27375-27409/com.johnsorhannus.divideandconquer D/SubTaskAdapter: areItemsTheSame: oldTask name: Test 2 id: 5 ### newTask name: Test 2 id: 5
2020-01-19 15:24:47.603 27375-27409/com.johnsorhannus.divideandconquer D/SubTaskAdapter: areItemsTheSame: oldTask name: Test 1 id: 4 ### newTask name: Test 1 id: 4
2020-01-19 15:24:47.603 27375-27409/com.johnsorhannus.divideandconquer D/SubTaskAdapter: areItemsTheSame: oldTask name: Read database book id: 1 ### newTask name: Read database book id: 1
2020-01-19 15:24:47.603 27375-27409/com.johnsorhannus.divideandconquer D/SubTaskAdapter: areItemsTheSame: oldTask name: Read OS book id: 2 ### newTask name: Read OS book id: 2
2020-01-19 15:24:47.603 27375-27409/com.johnsorhannus.divideandconquer D/SubTaskAdapter: areItemsTheSame: oldTask name: Wash car id: 3 ### newTask name: Wash car id: 3
2020-01-19 15:24:47.603 27375-27409/com.johnsorhannus.divideandconquer D/SubTaskAdapter: areContentsTheSame: oldTask name: Wash car ### newTask name: Wash car
2020-01-19 15:24:47.603 27375-27409/com.johnsorhannus.divideandconquer D/SubTaskAdapter: areContentsTheSame: = true
2020-01-19 15:24:47.603 27375-27409/com.johnsorhannus.divideandconquer D/SubTaskAdapter: areContentsTheSame: oldTask name: Read OS book ### newTask name: Read OS book
2020-01-19 15:24:47.603 27375-27409/com.johnsorhannus.divideandconquer D/SubTaskAdapter: areContentsTheSame: = true
2020-01-19 15:24:47.603 27375-27409/com.johnsorhannus.divideandconquer D/SubTaskAdapter: areContentsTheSame: oldTask name: Read database book ### newTask name: Read database book
2020-01-19 15:24:47.603 27375-27409/com.johnsorhannus.divideandconquer D/SubTaskAdapter: areContentsTheSame: = true
2020-01-19 15:24:47.603 27375-27409/com.johnsorhannus.divideandconquer D/SubTaskAdapter: areContentsTheSame: oldTask name: Test 1 ### newTask name: Test 1
2020-01-19 15:24:47.603 27375-27409/com.johnsorhannus.divideandconquer D/SubTaskAdapter: areContentsTheSame: = false
2020-01-19 15:24:47.603 27375-27409/com.johnsorhannus.divideandconquer D/SubTaskAdapter: areContentsTheSame: oldTask name: Test 2 ### newTask name: Test 2
2020-01-19 15:24:47.603 27375-27409/com.johnsorhannus.divideandconquer D/SubTaskAdapter: areContentsTheSame: = true
2020-01-19 15:24:47.652 27375-27375/com.johnsorhannus.divideandconquer D/SubTaskAdapter: onBindViewHolder: pos: 3 name: Test 1 Is overdue: false
2020-01-19 15:24:47.652 27375-27375/com.johnsorhannus.divideandconquer D/SubTaskAdapter: onBindViewHolder: Test 1 isOverdue = false
2020-01-19 15:24:47.655 27375-27375/com.johnsorhannus.divideandconquer D/SubTaskAdapter: onBindViewHolder: Test 1 set to checked
2020-01-19 15:24:50.633 27375-27375/com.johnsorhannus.divideandconquer D/SubTaskAdapter: onCheckedChanged: called for subTask Test 1 isChecked = false
2020-01-19 15:24:50.633 27375-27375/com.johnsorhannus.divideandconquer D/SubTaskViewModel: updateSubTask: CALLED for Test 1 isCompleted = false isOverdue = false
2020-01-19 15:24:50.664 27375-27375/com.johnsorhannus.divideandconquer D/SubTaskFragment: onChanged: subTasks length = 5
2020-01-19 15:24:50.664 27375-27504/com.johnsorhannus.divideandconquer D/SubTaskAdapter: areItemsTheSame: oldTask name: Wash car id: 3 ### newTask name: Wash car id: 3
2020-01-19 15:24:50.664 27375-27504/com.johnsorhannus.divideandconquer D/SubTaskAdapter: areItemsTheSame: oldTask name: Read OS book id: 2 ### newTask name: Read OS book id: 2
2020-01-19 15:24:50.664 27375-27504/com.johnsorhannus.divideandconquer D/SubTaskAdapter: areItemsTheSame: oldTask name: Read database book id: 1 ### newTask name: Read database book id: 1
2020-01-19 15:24:50.664 27375-27504/com.johnsorhannus.divideandconquer D/SubTaskAdapter: areItemsTheSame: oldTask name: Test 1 id: 4 ### newTask name: Test 1 id: 4
2020-01-19 15:24:50.664 27375-27504/com.johnsorhannus.divideandconquer D/SubTaskAdapter: areItemsTheSame: oldTask name: Test 2 id: 5 ### newTask name: Test 2 id: 5
2020-01-19 15:24:50.664 27375-27504/com.johnsorhannus.divideandconquer D/SubTaskAdapter: areItemsTheSame: oldTask name: Test 2 id: 5 ### newTask name: Test 2 id: 5
2020-01-19 15:24:50.664 27375-27504/com.johnsorhannus.divideandconquer D/SubTaskAdapter: areItemsTheSame: oldTask name: Test 1 id: 4 ### newTask name: Test 1 id: 4
2020-01-19 15:24:50.664 27375-27504/com.johnsorhannus.divideandconquer D/SubTaskAdapter: areItemsTheSame: oldTask name: Read database book id: 1 ### newTask name: Read database book id: 1
2020-01-19 15:24:50.664 27375-27504/com.johnsorhannus.divideandconquer D/SubTaskAdapter: areItemsTheSame: oldTask name: Read OS book id: 2 ### newTask name: Read OS book id: 2
2020-01-19 15:24:50.664 27375-27504/com.johnsorhannus.divideandconquer D/SubTaskAdapter: areItemsTheSame: oldTask name: Wash car id: 3 ### newTask name: Wash car id: 3
2020-01-19 15:24:50.664 27375-27504/com.johnsorhannus.divideandconquer D/SubTaskAdapter: areContentsTheSame: oldTask name: Wash car ### newTask name: Wash car
2020-01-19 15:24:50.665 27375-27504/com.johnsorhannus.divideandconquer D/SubTaskAdapter: areContentsTheSame: = true
2020-01-19 15:24:50.665 27375-27504/com.johnsorhannus.divideandconquer D/SubTaskAdapter: areContentsTheSame: oldTask name: Read OS book ### newTask name: Read OS book
2020-01-19 15:24:50.665 27375-27504/com.johnsorhannus.divideandconquer D/SubTaskAdapter: areContentsTheSame: = true
2020-01-19 15:24:50.665 27375-27504/com.johnsorhannus.divideandconquer D/SubTaskAdapter: areContentsTheSame: oldTask name: Read database book ### newTask name: Read database book
2020-01-19 15:24:50.665 27375-27504/com.johnsorhannus.divideandconquer D/SubTaskAdapter: areContentsTheSame: = true
2020-01-19 15:24:50.665 27375-27504/com.johnsorhannus.divideandconquer D/SubTaskAdapter: areContentsTheSame: oldTask name: Test 1 ### newTask name: Test 1
2020-01-19 15:24:50.665 27375-27504/com.johnsorhannus.divideandconquer D/SubTaskAdapter: areContentsTheSame: = true
2020-01-19 15:24:50.665 27375-27504/com.johnsorhannus.divideandconquer D/SubTaskAdapter: areContentsTheSame: oldTask name: Test 2 ### newTask name: Test 2
2020-01-19 15:24:50.665 27375-27504/com.johnsorhannus.divideandconquer D/SubTaskAdapter: areContentsTheSame: = true

我遇到的下一个可能相关的问题是列表项是 "overdue"(红色任务)and I check it off, other items in the ListAdapter turn red when I hit it's corresponding checkbox。这不是它应该运行的方式。这些列表项应该是白色的,因为这些任务没有过期。当我第二次 check/uncheck 它最终又变回白色。

检查过期任务时应用程序正常运行。它已从屏幕上删除,因为我的 SQL SELECT 语句不再返回已检查的逾期任务。

这是我 check/uncheck 两次未过期列表项时的日志(第 3 个 gif)。这里要注意的一件重要事情是,当检查 Test 1 时,areContentsTheSame() 正确 returns false 当列表项被检查时,因为 isCompleted 已从 true 设置为 false。但是,即使 isCompleted 已从 true 设置为 false,即使未选中它,areContentsTheSame() 也会错误地 returns true。此外,即使 isOverdue 为 false,列表项也会突然变成红色:

2020-01-19 15:28:52.071 27375-27375/com.johnsorhannus.divideandconquer D/SubTaskAdapter: onCheckedChanged: called for subTask Test 1 isChecked = true
2020-01-19 15:28:52.072 27375-27375/com.johnsorhannus.divideandconquer D/SubTaskViewModel: updateSubTask: CALLED for Test 1 isCompleted = true isOverdue = false
2020-01-19 15:28:52.104 27375-27375/com.johnsorhannus.divideandconquer D/SubTaskFragment: onChanged: subTasks length = 4
2020-01-19 15:28:52.104 27375-27504/com.johnsorhannus.divideandconquer D/SubTaskAdapter: areItemsTheSame: oldTask name: Wash car id: 3 ### newTask name: Wash car id: 3
2020-01-19 15:28:52.104 27375-27504/com.johnsorhannus.divideandconquer D/SubTaskAdapter: areItemsTheSame: oldTask name: Read OS book id: 2 ### newTask name: Read OS book id: 2
2020-01-19 15:28:52.104 27375-27504/com.johnsorhannus.divideandconquer D/SubTaskAdapter: areItemsTheSame: oldTask name: Test 1 id: 4 ### newTask name: Test 1 id: 4
2020-01-19 15:28:52.104 27375-27504/com.johnsorhannus.divideandconquer D/SubTaskAdapter: areItemsTheSame: oldTask name: Test 2 id: 5 ### newTask name: Test 2 id: 5
2020-01-19 15:28:52.104 27375-27504/com.johnsorhannus.divideandconquer D/SubTaskAdapter: areItemsTheSame: oldTask name: Test 2 id: 5 ### newTask name: Test 2 id: 5
2020-01-19 15:28:52.104 27375-27504/com.johnsorhannus.divideandconquer D/SubTaskAdapter: areItemsTheSame: oldTask name: Test 1 id: 4 ### newTask name: Test 1 id: 4
2020-01-19 15:28:52.105 27375-27504/com.johnsorhannus.divideandconquer D/SubTaskAdapter: areItemsTheSame: oldTask name: Read OS book id: 2 ### newTask name: Read OS book id: 2
2020-01-19 15:28:52.105 27375-27504/com.johnsorhannus.divideandconquer D/SubTaskAdapter: areItemsTheSame: oldTask name: Wash car id: 3 ### newTask name: Wash car id: 3
2020-01-19 15:28:52.105 27375-27504/com.johnsorhannus.divideandconquer D/SubTaskAdapter: areContentsTheSame: oldTask name: Wash car ### newTask name: Wash car
2020-01-19 15:28:52.105 27375-27504/com.johnsorhannus.divideandconquer D/SubTaskAdapter: areContentsTheSame: = true
2020-01-19 15:28:52.105 27375-27504/com.johnsorhannus.divideandconquer D/SubTaskAdapter: areContentsTheSame: oldTask name: Read OS book ### newTask name: Read OS book
2020-01-19 15:28:52.105 27375-27504/com.johnsorhannus.divideandconquer D/SubTaskAdapter: areContentsTheSame: = true
2020-01-19 15:28:52.105 27375-27504/com.johnsorhannus.divideandconquer D/SubTaskAdapter: areContentsTheSame: oldTask name: Test 1 ### newTask name: Test 1
2020-01-19 15:28:52.105 27375-27504/com.johnsorhannus.divideandconquer D/SubTaskAdapter: areContentsTheSame: = false
2020-01-19 15:28:52.105 27375-27504/com.johnsorhannus.divideandconquer D/SubTaskAdapter: areContentsTheSame: oldTask name: Test 2 ### newTask name: Test 2
2020-01-19 15:28:52.105 27375-27504/com.johnsorhannus.divideandconquer D/SubTaskAdapter: areContentsTheSame: = true
2020-01-19 15:28:52.136 27375-27375/com.johnsorhannus.divideandconquer D/SubTaskAdapter: onBindViewHolder: pos: 2 name: Test 1 Is overdue: false
2020-01-19 15:28:52.137 27375-27375/com.johnsorhannus.divideandconquer D/SubTaskAdapter: onBindViewHolder: Test 1 isOverdue = false
2020-01-19 15:28:52.137 27375-27375/com.johnsorhannus.divideandconquer D/SubTaskAdapter: onBindViewHolder: Test 1 set to checked
2020-01-19 15:28:54.080 27375-27375/com.johnsorhannus.divideandconquer D/SubTaskAdapter: onCheckedChanged: called for subTask Test 1 isChecked = false
2020-01-19 15:28:54.081 27375-27375/com.johnsorhannus.divideandconquer D/SubTaskViewModel: updateSubTask: CALLED for Test 1 isCompleted = false isOverdue = false
2020-01-19 15:28:54.102 27375-27375/com.johnsorhannus.divideandconquer D/SubTaskFragment: onChanged: subTasks length = 4
2020-01-19 15:28:54.103 27375-27409/com.johnsorhannus.divideandconquer D/SubTaskAdapter: areItemsTheSame: oldTask name: Wash car id: 3 ### newTask name: Wash car id: 3
2020-01-19 15:28:54.103 27375-27409/com.johnsorhannus.divideandconquer D/SubTaskAdapter: areItemsTheSame: oldTask name: Read OS book id: 2 ### newTask name: Read OS book id: 2
2020-01-19 15:28:54.103 27375-27409/com.johnsorhannus.divideandconquer D/SubTaskAdapter: areItemsTheSame: oldTask name: Test 1 id: 4 ### newTask name: Test 1 id: 4
2020-01-19 15:28:54.103 27375-27409/com.johnsorhannus.divideandconquer D/SubTaskAdapter: areItemsTheSame: oldTask name: Test 2 id: 5 ### newTask name: Test 2 id: 5
2020-01-19 15:28:54.103 27375-27409/com.johnsorhannus.divideandconquer D/SubTaskAdapter: areItemsTheSame: oldTask name: Test 2 id: 5 ### newTask name: Test 2 id: 5
2020-01-19 15:28:54.103 27375-27409/com.johnsorhannus.divideandconquer D/SubTaskAdapter: areItemsTheSame: oldTask name: Test 1 id: 4 ### newTask name: Test 1 id: 4
2020-01-19 15:28:54.103 27375-27409/com.johnsorhannus.divideandconquer D/SubTaskAdapter: areItemsTheSame: oldTask name: Read OS book id: 2 ### newTask name: Read OS book id: 2
2020-01-19 15:28:54.103 27375-27409/com.johnsorhannus.divideandconquer D/SubTaskAdapter: areItemsTheSame: oldTask name: Wash car id: 3 ### newTask name: Wash car id: 3
2020-01-19 15:28:54.103 27375-27409/com.johnsorhannus.divideandconquer D/SubTaskAdapter: areContentsTheSame: oldTask name: Wash car ### newTask name: Wash car
2020-01-19 15:28:54.103 27375-27409/com.johnsorhannus.divideandconquer D/SubTaskAdapter: areContentsTheSame: = true
2020-01-19 15:28:54.103 27375-27409/com.johnsorhannus.divideandconquer D/SubTaskAdapter: areContentsTheSame: oldTask name: Read OS book ### newTask name: Read OS book
2020-01-19 15:28:54.103 27375-27409/com.johnsorhannus.divideandconquer D/SubTaskAdapter: areContentsTheSame: = true
2020-01-19 15:28:54.103 27375-27409/com.johnsorhannus.divideandconquer D/SubTaskAdapter: areContentsTheSame: oldTask name: Test 1 ### newTask name: Test 1
2020-01-19 15:28:54.103 27375-27409/com.johnsorhannus.divideandconquer D/SubTaskAdapter: areContentsTheSame: = true
2020-01-19 15:28:54.103 27375-27409/com.johnsorhannus.divideandconquer D/SubTaskAdapter: areContentsTheSame: oldTask name: Test 2 ### newTask name: Test 2
2020-01-19 15:28:54.103 27375-27409/com.johnsorhannus.divideandconquer D/SubTaskAdapter: areContentsTheSame: = true
2020-01-19 15:28:58.480 27375-27375/com.johnsorhannus.divideandconquer D/SubTaskAdapter: onCheckedChanged: called for subTask Test 1 isChecked = true
2020-01-19 15:28:58.481 27375-27375/com.johnsorhannus.divideandconquer D/SubTaskViewModel: updateSubTask: CALLED for Test 1 isCompleted = true isOverdue = false
2020-01-19 15:28:58.498 27375-27375/com.johnsorhannus.divideandconquer D/SubTaskFragment: onChanged: subTasks length = 4
2020-01-19 15:28:58.499 27375-27504/com.johnsorhannus.divideandconquer D/SubTaskAdapter: areItemsTheSame: oldTask name: Wash car id: 3 ### newTask name: Wash car id: 3
2020-01-19 15:28:58.499 27375-27504/com.johnsorhannus.divideandconquer D/SubTaskAdapter: areItemsTheSame: oldTask name: Read OS book id: 2 ### newTask name: Read OS book id: 2
2020-01-19 15:28:58.499 27375-27504/com.johnsorhannus.divideandconquer D/SubTaskAdapter: areItemsTheSame: oldTask name: Test 1 id: 4 ### newTask name: Test 1 id: 4
2020-01-19 15:28:58.499 27375-27504/com.johnsorhannus.divideandconquer D/SubTaskAdapter: areItemsTheSame: oldTask name: Test 2 id: 5 ### newTask name: Test 2 id: 5
2020-01-19 15:28:58.499 27375-27504/com.johnsorhannus.divideandconquer D/SubTaskAdapter: areItemsTheSame: oldTask name: Test 2 id: 5 ### newTask name: Test 2 id: 5
2020-01-19 15:28:58.499 27375-27504/com.johnsorhannus.divideandconquer D/SubTaskAdapter: areItemsTheSame: oldTask name: Test 1 id: 4 ### newTask name: Test 1 id: 4
2020-01-19 15:28:58.499 27375-27504/com.johnsorhannus.divideandconquer D/SubTaskAdapter: areItemsTheSame: oldTask name: Read OS book id: 2 ### newTask name: Read OS book id: 2
2020-01-19 15:28:58.499 27375-27504/com.johnsorhannus.divideandconquer D/SubTaskAdapter: areItemsTheSame: oldTask name: Wash car id: 3 ### newTask name: Wash car id: 3
2020-01-19 15:28:58.499 27375-27504/com.johnsorhannus.divideandconquer D/SubTaskAdapter: areContentsTheSame: oldTask name: Wash car ### newTask name: Wash car
2020-01-19 15:28:58.499 27375-27504/com.johnsorhannus.divideandconquer D/SubTaskAdapter: areContentsTheSame: = true
2020-01-19 15:28:58.499 27375-27504/com.johnsorhannus.divideandconquer D/SubTaskAdapter: areContentsTheSame: oldTask name: Read OS book ### newTask name: Read OS book
2020-01-19 15:28:58.499 27375-27504/com.johnsorhannus.divideandconquer D/SubTaskAdapter: areContentsTheSame: = true
2020-01-19 15:28:58.499 27375-27504/com.johnsorhannus.divideandconquer D/SubTaskAdapter: areContentsTheSame: oldTask name: Test 1 ### newTask name: Test 1
2020-01-19 15:28:58.499 27375-27504/com.johnsorhannus.divideandconquer D/SubTaskAdapter: areContentsTheSame: = false
2020-01-19 15:28:58.499 27375-27504/com.johnsorhannus.divideandconquer D/SubTaskAdapter: areContentsTheSame: oldTask name: Test 2 ### newTask name: Test 2
2020-01-19 15:28:58.499 27375-27504/com.johnsorhannus.divideandconquer D/SubTaskAdapter: areContentsTheSame: = true
2020-01-19 15:28:58.529 27375-27375/com.johnsorhannus.divideandconquer D/SubTaskAdapter: onBindViewHolder: pos: 2 name: Test 1 Is overdue: false
2020-01-19 15:28:58.529 27375-27375/com.johnsorhannus.divideandconquer D/SubTaskAdapter: onBindViewHolder: Test 1 isOverdue = false
2020-01-19 15:28:58.529 27375-27375/com.johnsorhannus.divideandconquer D/SubTaskAdapter: onBindViewHolder: Test 1 set to checked
2020-01-19 15:28:59.922 27375-27375/com.johnsorhannus.divideandconquer D/SubTaskAdapter: onCheckedChanged: called for subTask Test 1 isChecked = false
2020-01-19 15:28:59.922 27375-27375/com.johnsorhannus.divideandconquer D/SubTaskViewModel: updateSubTask: CALLED for Test 1 isCompleted = false isOverdue = false
2020-01-19 15:28:59.941 27375-27375/com.johnsorhannus.divideandconquer D/SubTaskFragment: onChanged: subTasks length = 4
2020-01-19 15:28:59.941 27375-27409/com.johnsorhannus.divideandconquer D/SubTaskAdapter: areItemsTheSame: oldTask name: Wash car id: 3 ### newTask name: Wash car id: 3
2020-01-19 15:28:59.941 27375-27409/com.johnsorhannus.divideandconquer D/SubTaskAdapter: areItemsTheSame: oldTask name: Read OS book id: 2 ### newTask name: Read OS book id: 2
2020-01-19 15:28:59.941 27375-27409/com.johnsorhannus.divideandconquer D/SubTaskAdapter: areItemsTheSame: oldTask name: Test 1 id: 4 ### newTask name: Test 1 id: 4
2020-01-19 15:28:59.941 27375-27409/com.johnsorhannus.divideandconquer D/SubTaskAdapter: areItemsTheSame: oldTask name: Test 2 id: 5 ### newTask name: Test 2 id: 5
2020-01-19 15:28:59.941 27375-27409/com.johnsorhannus.divideandconquer D/SubTaskAdapter: areItemsTheSame: oldTask name: Test 2 id: 5 ### newTask name: Test 2 id: 5
2020-01-19 15:28:59.941 27375-27409/com.johnsorhannus.divideandconquer D/SubTaskAdapter: areItemsTheSame: oldTask name: Test 1 id: 4 ### newTask name: Test 1 id: 4
2020-01-19 15:28:59.941 27375-27409/com.johnsorhannus.divideandconquer D/SubTaskAdapter: areItemsTheSame: oldTask name: Read OS book id: 2 ### newTask name: Read OS book id: 2
2020-01-19 15:28:59.941 27375-27409/com.johnsorhannus.divideandconquer D/SubTaskAdapter: areItemsTheSame: oldTask name: Wash car id: 3 ### newTask name: Wash car id: 3
2020-01-19 15:28:59.941 27375-27409/com.johnsorhannus.divideandconquer D/SubTaskAdapter: areContentsTheSame: oldTask name: Wash car ### newTask name: Wash car
2020-01-19 15:28:59.941 27375-27409/com.johnsorhannus.divideandconquer D/SubTaskAdapter: areContentsTheSame: = true
2020-01-19 15:28:59.941 27375-27409/com.johnsorhannus.divideandconquer D/SubTaskAdapter: areContentsTheSame: oldTask name: Read OS book ### newTask name: Read OS book
2020-01-19 15:28:59.941 27375-27409/com.johnsorhannus.divideandconquer D/SubTaskAdapter: areContentsTheSame: = true
2020-01-19 15:28:59.941 27375-27409/com.johnsorhannus.divideandconquer D/SubTaskAdapter: areContentsTheSame: oldTask name: Test 1 ### newTask name: Test 1
2020-01-19 15:28:59.941 27375-27409/com.johnsorhannus.divideandconquer D/SubTaskAdapter: areContentsTheSame: = true
2020-01-19 15:28:59.941 27375-27409/com.johnsorhannus.divideandconquer D/SubTaskAdapter: areContentsTheSame: oldTask name: Test 2 ### newTask name: Test 2
2020-01-19 15:28:59.941 27375-27409/com.johnsorhannus.divideandconquer D/SubTaskAdapter: areContentsTheSame: = true

我注意到的另一件事是 areItemsTheSame() 为列表中的每个项目调用两次,而 areContentsTheSame() 为每个列表项目仅调用一次。这似乎不是正常行为,因此这可能以某种方式导致了问题。关于这两个问题是否相关以及如何解决它们有什么想法吗?

这里有两个问题。第一个(灰色突出显示)是由于 RecyclerView 的内置默认动画而发生的。当您选中该框时,RecyclerView 会创建一个新的 ViewHolder 并进行淡入淡出。有关如何解决此问题的更多信息,请参阅此 question/answer:

第二个(红色背景)是您的 onBindViewHolder() 方法中的一个错误,它阻止了视图被正确回收:

if (subTask.isOverdue()) {
    viewHolder.card.setCardBackgroundColor(context.getResources().getColor(R.color.red));
    viewHolder.subTaskName.setTextColor(context.getResources().getColor(R.color.colorAccent));
    viewHolder.mainTaskName.setTextColor(context.getResources().getColor(R.color.colorAccent));
}

像这样的每个 if 语句都需要一个 else 案例来将视图设置回正常颜色。如果没有这样的 else,您的红色视图将被回收到另一个位置,颜色不会发生任何变化 back