android studio 中的 recyclerview 项目事故(不良行为)

recyclerview item mishap(undesirable behaviour) in android studio

我有一个带有项目列表的回收站视图,其中每个项目都附有复选框。当一个复选框被选中时,它的行为是正确的,不需要的项目被选中也没有问题。但是当删除选中的项目时,不需要的项目也会被选中。

我的适配器Class:

public class TaskAdapter extends RecyclerView.Adapter<TaskAdapter.TaskHolder> {
private static List<Task> tasks = new ArrayList<>();
private static OnItemClickListener listener;
private static TaskAdapter adapter = new TaskAdapter();


@NonNull

@Override
public TaskHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
    View itemView = LayoutInflater.from(parent.getContext())
            .inflate(R.layout.task_item, parent, false);
    return new TaskHolder(itemView);
}

@Override
public void onBindViewHolder(@NonNull TaskHolder holder, int position) {
    Task currentTask = tasks.get(position);
    holder.a_tname.setText(currentTask.getTname());
    holder.a_tdate.setText(currentTask.getTDate());
    holder.a_ttime.setText(currentTask.getTTime());
    holder.a_tprior.setText(currentTask.getTprior());
    holder.bind(tasks.get(position));
   holder.bind2(tasks.get(position));

}
@Override
public int getItemCount() {
    return tasks.size();
}
public void setTasks(List<Task> tasks) {
    this.tasks = tasks;
    Collections.sort( tasks, Task.comparepriority);
    notifyDataSetChanged();
}
public Task getTaskAt(int position){
    return tasks.get(position);
}

 class TaskHolder extends RecyclerView.ViewHolder  {
    private final TextView a_tname;
    private final TextView a_tdate;
    private  final TextView a_ttime;
    private final TextView a_tprior;
    ImageView priorityIndicator;
    CheckBox checkbox;


    public TaskHolder(View itemView) {
        super(itemView);
        a_tname = itemView.findViewById(R.id.a_tname);
        a_tdate=itemView.findViewById(R.id.a_tdate);
        a_ttime = itemView.findViewById(R.id.a_ttime);
        a_tprior = itemView.findViewById(R.id.a_tprior);
        priorityIndicator = itemView.findViewById(R.id.priorityIndicator);
        checkbox = itemView.findViewById(R.id.checkbox);




        itemView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                int position = getAdapterPosition();
                if(listener!=null&&position!=RecyclerView.NO_POSITION){
                    listener.onItemClick(tasks.get(position));
                }
            }
        });

    }

    private void bind(Task task){
        int drawableId;int red = R.color.red;int yellow = R.color.yellow;int green = R.color.green;
        int color1 = ContextCompat.getColor(a_tprior.getContext(), red);
        int color2 = ContextCompat.getColor(a_tprior.getContext(),yellow);
        int color3 = ContextCompat.getColor(a_tprior.getContext(),green);
        switch(task.t_prior){
            case "1": drawableId = R.drawable.ic_baseline_priority_high_24;
            a_tprior.setTextColor(color1);
            break;
            case "2": drawableId = R.drawable.ic_baseline_priority_middle_24;
            a_tprior.setTextColor(color2);
            break;
            case "3" : drawableId = R.drawable.ic_baseline_low_priority_24;
            a_tprior.setTextColor(color3);
            break;
            default: drawableId = R.drawable.ic_baseline_crop_square_24;
        }
        priorityIndicator.setImageDrawable(ContextCompat.getDrawable(priorityIndicator.getContext(),drawableId));
    }

    public void bind2(Task task){
        final boolean[] checked = {true};
        checkbox.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if(checkbox.isChecked()) {
                    String pos = Integer.valueOf(getAdapterPosition()).toString();
                    PreferenceManager.getDefaultSharedPreferences(checkbox.getContext()).edit().
                            putBoolean("checkbox" + pos , checked[0]).apply();
                    Toast.makeText(checkbox.getContext(), "Way to go! Now swipe to delete", Toast.LENGTH_LONG).show();
                }
                else  {
                    checked[0] =false;
                    String pos = Integer.valueOf(getAdapterPosition()).toString();
                    PreferenceManager.getDefaultSharedPreferences(checkbox.getContext()).edit().
                            putBoolean("checkbox" + pos, checked[0]).apply();
                }
            }
        }); String pos = Integer.valueOf(getAdapterPosition()).toString();
        boolean cb = PreferenceManager.getDefaultSharedPreferences(checkbox.getContext()).getBoolean
                ("checkbox" + pos, false);
        checkbox.setChecked(cb);
    }

 }
public interface  OnItemClickListener {
    void onItemClick(Task ta);
}
public void setOnItemClickListener(OnItemClickListener listener){
    this.listener = listener;
}

}

我要删除的代码 HomeFragment.java -

  new ItemTouchHelper(new ItemTouchHelper.SimpleCallback(0,
            ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT) {
        @Override
        public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull
                RecyclerView.ViewHolder viewHolder, @NonNull  RecyclerView.ViewHolder target) {
            return false;
        }

        @Override
        public void onSwiped(@NonNull  RecyclerView.ViewHolder viewHolder, int direction) {
            int position = viewHolder.getAdapterPosition();
            new AlertDialog.Builder(getActivity())
                    .setMessage("Do you want to delete this task?")
                    .setPositiveButton("Delete", ((dialog, which) ->
                            taskViewmodel.delete(adapter.getTaskAt(viewHolder.getAdapterPosition()))))
                    .setNegativeButton("Cancel", ((dialog, which) -> adapter.notifyItemChanged(position)))
                    .setOnCancelListener(dialog -> adapter.notifyItemChanged(position))
                    .create().show();


        }
    }).attachToRecyclerView(recyclerView);

编辑:我猜问题出在与保存复选框状态相关的代码上,因为特定项目位置的复选框被选中,所以当项目被删除时,下面的项目会取代它,所以它得到检查。假设第 2 个位置的项目被选中,我删除了那个项目,然后第 3 个位置的项目取而代之,这样就被选中了。我需要知道如何解决 this.May 我知道我应该做哪些改变来纠正这个问题? 谢谢

我不太熟悉 lambda 函数,所以我分享了我如何做同样的事情 task.I 为我自己的应用程序尝试了这段代码,它运行得非常好。 检查以下代码:

        @Override
        public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) {
            int position = viewHolder.getAbsoluteAdapterPosition();//getAdapterPosition is depreciated, use getAbsoluteAdapterPosition
            AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
            builder.setMessage("Do you want to delete this task?");
          builder.setPositiveButton("Delete",new DialogInterface.OnClickListener() {
              @Override
              public void onClick(DialogInterface dialog, int which) {
    taskViewmodel.delete(adapter.getTaskAt(viewHolder.getAdapterPosition()))
    adapter.notifyItemRemoved(position);
              }
          });
                  builder.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
                      @Override
                      public void onClick(DialogInterface dialog, int which) {
                          dialog.cancel();
                      }
                  });
                  builder.setOnCancelListener(new DialogInterface.OnCancelListener() {
                      @Override
                      public void onCancel(DialogInterface dialog) {
                   adapter.notifyItemChanged(position);
                      }
                  }).show();

        }
    }).attachToRecyclerView(recyclerView);
    

在那天,没有删除的直觉,现在因为你使用它的位置在 SharedPreferences 中存储一个项目是否被选中的状态,如果你有 3 个项目,位置 =2 被选中,并且你在 pos=2 处删除这个选中的项目,然后第三个项目将取代它在 pos=2 处表现为新项目,这就是为什么当你删除一个时,所有选中的状态都会发生变化。

我想这里别无选择,只能在您的任务 class 中使用一些标识符,其中一个任务使用此 number/string 进行唯一标识,您使用该唯一标识符将项目状态存储在您的 SharedPreferences 作为键。

一种快速廉价的方法是使任务 class 的行为类似于 Room 数据库识别其自动唯一 int/long 标识符的方式。
方法是

  • 在您的任务 class 中定义一个静态 int/long 计数器字段,用于标识您到目前为止使用了多少个 ID,以便不重复之前使用的任何 ID(即使它已被删除,现在不再使用)

  • 在任务 class 中定义一个私有 int/long id 字段,而在任务 class 构造函数中,当您初始化一个新任务时,您将递增静态计数器字段并将此新值用作新创建任务的私有 ID 的值。 N.B :如果您为其创建对象的任务是您从 SharedPreferences/Database 中检索到的旧任务,那么您不应该增加静态值并为每个创建的任务分配一个新 ID旧 id,对于那种情况,您可能有两个构造函数,一个接受旧 id 作为参数,另一个构造函数递增 taskCounter 并获得一个新 id,您可能还有两个构造函数,一个调用另一个构造函数,以防您有其他参数在创建时重新传递给任务对象,并且您想避免在两个构造函数中重复代码。

  • 这样当您使用 SharedPreferences 检查某些任务的状态时,您将使用任务的私有 ID 值而不是其位置。

你的任务 class 可能看起来像这样(如果有两个构造函数而构造函数中没有任何附加代码):

public class Task {
    public static int tasksCounter =0;
    public int taskId ;
    ...
    //constructor for a new Task 
    public Task(){
        this.taskId= ++tasksCounter ;
    }
    //constructor for an old Task 
    public Task(int oldId){
        this.taskId= oldId ;
    }

你的任务 class 可能看起来像这样(如果有两个构造函数并且你想避免代码重复):

public class Task {
    public static int tasksCounter =0;
    public int taskId ;
    //you'd call that one if you're creating a completely new Task and it will call 
    the other constructor for you
    public Task(){
        Task(++tasksCounter)
    }
    //you'd call that one if you're creating a Task that you already have an id for
    public Task(int id){
        this.taskId= id ;
        //some other code here
    }

在你的适配器中,当你询问它是否被选中时,它会是这样的:

PreferenceManager.getDefaultSharedPreferences(checkbox.getContext()).getBoolean
                ("checkbox" + task.taskId, false);

并且当您更改复选框状态时,您将同样使用 task.taskId 而不是位置来更改其在首选项中的值。

这当然会引发另一个问题,即每次启动应用程序时,静态字段都会再次重置为 0。
所以你也应该将它的值存储在 sharedpreferences 中

  • 当你创建一个新任务时,它的值会增加,所以你应该存储新值
    或者
  • 就在 activity 或片段通过覆盖 activity 或片段中的方法 onDestroy() 被销毁之前,您应该存储它具有的最后一个值 当您启动 activity 或片段时,您需要从 sharedpreferences 中获取它并将其分配给 Task.tasksCounter.

N.B :以防万一你不知道,你可以通过调用 class 来获取静态字段的值,你不需要创建这个 class 获取它的值,调用下一个代码就足以获取和编辑它的值:

Task.tasksCounter

最后,
由于您现在拥有复杂的数据(任务 class),我强烈建议您停止使用 SharedPreferences 来存储所有内容,然后开始阅读并切换到 Room Database

Room Database 为您提供数据库的必要存储能力,包括:

  • 拥有一个自动递增的标识符而不用担心它们的值
  • 只需一行代码和一个简单的查询即可存储和获取数据,而不是使用键调用 get 100 次。

将您的任务 class 更改为具有自动增量字段的实体后,您就可以开始使用 Room 来存储您的任务了。