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

Posted

技术标签:

【中文标题】android studio中的recyclerview项目事故(不良行为)【英文标题】:recyclerview item mishap(undesirable behaviour) in android studio 【发布时间】:2021-12-09 22:07:06 【问题描述】:

我有一个包含项目列表的回收站视图,每个项目都附有复选框。选中复选框时,它会正常运行,并且检查不需要的项目没有问题。但是当一个选中的项目被删除时,不需要的项目也会被选中。

我的适配器类:

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);

编辑:我想问题出在与保存复选框状态相关的代码上,因为选中了特定项目位置的复选框,因此当删除该项目时,下面的项目将取代它,因此它被选中。假设第二个位置的项目被选中,我删除了那个项目,然后第三个位置的项目取而代之,这样就被选中了。我需要知道如何解决这个问题。我知道我应该做些什么改变来纠正这个问题吗? 谢谢你

【问题讨论】:

从列表中删除项目时是否通知更改? 我提供 notifyItemChanged(position)。我现在已经发布了我的删除代码 在 notifyItemChanged(position) 之后使用 adapter.notifyDataSetChanged()。 @Danish 还是不行 @Danish 不,它仍然无法正常工作 【参考方案1】:

回到那天,没有删除直觉,现在因为您使用其位置存储是否在 SharedPreferences 中检查项目的状态,如果您有 3 个项目,其中位置 =2 被选中,并且您删除这个在 pos=2 时选中的项目,然后第三个项目将在 pos=2 处作为新项目代替它,这就是为什么当你删除一个时,所有选中的状态都会转移。

我想这里没有其他选择,只能在您的 Task 类中使用某个标识符,其中一个 Task 使用此数字/字符串唯一标识,您使用该唯一标识符将项目状态存储在 SharedPreferences 中作为键。

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

在您的 Task 类中定义一个静态 int/long 计数器字段,该字段标识您到目前为止使用了多少个 id,以便不重复之前获取的任何 id(即使它已被删除且现在未使用)

在 Task 类中定义一个私有 int/long id 字段,而在 Task 类构造函数中,当您初始化一个新任务时,您将增加静态计数器字段并将这个新值用作您的私有 id 的值您新创建的任务。 注意:如果您正在为其创建对象的任务是您从 SharedPreferences/Database 检索到的已具有旧 ID 的旧任务,则不应增加静态值并为每个创建的任务分配新 ID,在这种情况下您可能有两个构造函数,一个接受旧 id 作为参数,一个增加 taskCounter 并获取新 id,您可能还有两个构造函数,一个调用另一个,以防您有一些其他参数传递给任务对象在创建时,您希望避免在两个构造函数中重复代码。

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

您的 Task 类可能如下所示(如果有两个构造函数,而构造函数中没有任何附加代码):

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 ;
    

您的 Task 类可能如下所示(如果有两个构造函数并且您希望避免代码重复):

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 中

当你创建一个新任务时,它的值会增加,所以你应该存储新值 或 就在通过覆盖活动或片段中的onDestroy() 方法而破坏活动或片段之前,您应该存储它的最后一个值 当您开始您的活动或片段时,您需要从 sharedpreferences 中获取它并将其分配给Task.tasksCounter

注意:如果你不知道,你可以通过调用它自己的类来获取静态字段的值,你不需要创建这个类的新对象来获取它的值,调用下一个代码是足以获取和编辑它的值:

Task.tasksCounter

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

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

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

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

【讨论】:

非常感谢,我的Task 类中已经有了task.id,但没想到这样使用它。这帮助了很多!【参考方案2】:

我对 lambda 函数不太熟悉,所以我将分享我将如何完成相同的任务。我为自己的应用程序尝试了这段代码,它运行得非常好。 检查以下代码:

        @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);
    

【讨论】:

它仍然没有解决我的复选框问题。我想问题出在与保存复选框状态相关的代码上,因为选中了特定项目位置的复选框,因此当删除该项目时,下面的项目将取而代之,因此它会被选中。假设第二个位置的项目被选中,我删除了那个项目,然后第三个位置的项目取而代之,这样就被选中了。我需要知道如何解决这个问题,但感谢您的帮助 是的,这就是如果您查看复选框代码的原因,您将在共享首选项中保存复选框的状态,当您删除回收站视图项目时,您的复选框再次设置为选中状态。 尝试将您的复选框设为公开静态,然后在您的 onSwipe 中访问它并设置为 true。像这样:checkbox.setChecked(false) onSwiped() 在 HomeFragment.java 中,而复选框在 TaskAdapter.java 中,它们在两个不同的活动中,那么我该如何访问它呢? 公开静态。

以上是关于android studio中的recyclerview项目事故(不良行为)的主要内容,如果未能解决你的问题,请参考以下文章

android studio怎样运行打包后的apk

无法按照 Google“入门”页面中的说明在 Android Studio 中添加地图; Android Studio 报错

android studio 怎么添加module

android studio application怎么创建

android - android studio中的proguard错误

Android studio 报错问题