android studio中的recyclerview项目事故(不良行为) [英] recyclerview item mishap(undesirable behaviour) in android studio

查看:29
本文介绍了android studio中的recyclerview项目事故(不良行为)的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

我的适配器类:

public class TaskAdapter extends RecyclerView.Adapter{私有静态列表<任务>任务 = 新的 ArrayList<>();私有静态 OnItemClickListener 侦听器;私有静态 TaskAdapter 适配器 = new TaskAdapter();@非空@覆盖public TaskHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {查看 itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.task_item, parent, false);返回新的 TaskHolder(itemView);}@覆盖public void onBindViewHolder(@NonNull TaskHolder holder, int position) {任务 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.bind2(tasks.get(position));}@覆盖公共 int getItemCount() {返回tasks.size();}public void setTasks(List tasks) {this.tasks = 任务;Collections.sort(tasks, Task.comparepriority);notifyDataSetChanged();}公共任务 getTaskAt(int position){返回 tasks.get(position);}类 TaskHolder 扩展 RecyclerView.ViewHolder {私人最终 TextView a_tname;私人最终 TextView a_tdate;私人最终 TextView a_ttime;私有最终 TextView a_tprior;ImageView 优先级指示器;复选框复选框;公共 TaskHolder(查看 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);priorityIndi​​cator = itemView.findViewById(R.id.priorityIndi​​cator);checkbox = itemView.findViewById(R.id.checkbox);itemView.setOnClickListener(new View.OnClickListener() {@覆盖public void onClick(View v) {int position = getAdapterPosition();if(listener!=null&&position!=RecyclerView.NO_POSITION){listener.onItemClick(tasks.get(position));}}});}私人无效绑定(任务任务){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);开关(任务.t_prior){情况1":drawableId = R.drawable.ic_baseline_priority_high_24;a_tprior.setTextColor(color1);休息;情况2":drawableId = R.drawable.ic_baseline_priority_middle_24;a_tprior.setTextColor(color2);休息;案例3":drawableId = R.drawable.ic_baseline_low_priority_24;a_tprior.setTextColor(color3);休息;默认值:drawableId = R.drawable.ic_baseline_crop_square_24;}priorityIndi​​cator.setImageDrawable(ContextCompat.getDrawable(priorityIndi​​cator.getContext(),drawableId));}public void bind2(任务任务){最终布尔 [] 检查 = {真};checkbox.setOnClickListener(new View.OnClickListener() {@覆盖public void onClick(View v) {if(checkbox.isChecked()) {String pos = Integer.valueOf(getAdapterPosition()).toString();PreferenceManager.getDefaultSharedPreferences(checkbox.getContext()).edit().putBoolean(复选框"+ pos , 选中[0]).apply();Toast.makeText(checkbox.getContext(), "Way to go! Now swipe to delete", Toast.LENGTH_LONG).show();}别的  {检查[0] =假;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(复选框"+ pos, false);checkbox.setChecked(cb);}}公共接口 OnItemClickListener {void onItemClick(Task ta);}public void setOnItemClickListener(OnItemClickListener 监听器){this.listener = 听众;}

}

我在 HomeFragment.java 中要删除的代码 -

 new ItemTouchHelper(new ItemTouchHelper.SimpleCallback(0,ItemTouchHelper.LEFT |ItemTouchHelper.RIGHT) {@覆盖public boolean onMove(@NonNull RecyclerView recyclerView, @NonNullRecyclerView.ViewHolder viewHolder,@NonNull RecyclerView.ViewHolder 目标){返回假;}@覆盖public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) {int position = viewHolder.getAdapterPosition();新的 AlertDialog.Builder(getActivity()).setMessage("你要删除这个任务吗?").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 个位置的项目取而代之,这样就被检查了.我需要知道如何解决这个问题.我可以知道我应该做哪些更改来纠正这个问题?谢谢你

解决方案

过去,没有删除直觉,现在因为您使用其位置在 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.注意:如果您为其创建对象的对象是从已有旧 ID 的 SharedPreferences/Database 检索的旧任务,则不应增加静态值并为每个创建的任务分配新 ID,在这种情况下你可能有两个构造函数,一个接受旧的 id 作为参数,一个增加 taskCounter 并获得一个新的 id,你可能还有两个构造函数,一个调用另一个 incase 你有一些其他的参数传递给任务对象在创建时,您希望避免在两个构造函数中重复代码.

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

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

公共类任务{public static int tasksCounter =0;公共 int taskId ;...//新任务的构造函数公共任务(){this.taskId= ++tasksCounter ;}//旧任务的构造函数公共任务(int oldId){this.taskId= oldId ;}

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

公共类任务{public static int tasksCounter =0;公共 int taskId ;//如果你正在创建一个全新的任务,你会调用那个,它会调用你的另一个构造函数公共任务(){任务(++任务计数器)}//如果你正在创建一个你已经有一个id的任务,你会调用那个公共任务(int id){this.taskId= id ;//这里还有一些其他代码}

在您的适配器中,当您询问它是否已检查时,它会是这样的:

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

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

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

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

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

Task.tasksCounter

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

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

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

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

I have a recycler view with a list of items and each of those items have checkboxes attached to them. When a checkbox is checked, it behaves properly and there is no problem with unwanted items getting checked. But when a checked item is deleted, then the unwanted item gets checked.

My Adapter 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;
}

}

My code to delete in 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);

Edit : I guess the problem is with the code associated with saving the state of the checkbox because the checkbox at a particular item position is checked so when the item is deleted, the below item takes its place and so it gets checked. Suppose item at 2nd position is checked, and I delete that item, then the item at the 3rd position takes its place and so that gets checked. I need to know how to resolve this.May i know what changes should i make to rectify this problem? Thankyou

解决方案

back at the day, there was no delete intuition, now since you're storing the state of whether an item checked or not in SharedPreferences using its position, if you have 3 items where position =2 is checked, and you delete this checked item at pos=2, then the third item would take its place behaving as new item at pos=2, that's why when you delete one, all checked states will get shifted.

I guess there's no option here but to use some identifier in your Task class, where one Task is uniquely identified using this number/string and you use that unique identifier to store the items state in your SharedPreferences as a key.

a quick cheap way to do it is to make Task class behave like how Room database identify its automatic unique int/long identifiers.
the way to do it is by

  • defining a static int/long counter field in your Task class that identifies how many ids you used so far so to not repeat any id that was taken before (even if it was deleted and not used now)

  • define a private int/long id field in the Task class, while in the Task class constructor when you're initializing a new Task you would increment the static counter field and use this new value as a value for your private id for your newly created Task. N.B : you shouldn't increment the static value and assign a new id for every created task if the one you're creating an object for is an old one that you retrieved from SharedPreferences/Database that already have an old id, for that case you might have two constructors one that accepts an old id as paramter and one that increments the taskCounter and get a new id, you might also have two constructors that one calls the other incase you have some other paramteres you're passing to the task object at creation and you want to avoid duplicating code in both constructors.

  • that way when you're checking status using SharedPreferences for some task, you would use the private id value of Task instead of its position.

your Task class might look like this (in case of two constructors without any additional code in the constructor):

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

your Task class might look like this (in case of two constructors and you want to avoid code dublication):

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
    }

and in your adapter when you ask about whether its checked or not it would be like this :

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

and when you the checkbox status get changed you'd change its value in preferences likewise using the task.taskId instead of position.

that of course would raise another problem that every time you start your application that static field will get reset to 0 again.
so you should store its value too in sharedpreferences maybe either

  • when you create a new Task, its value will be incremented so you should store the new value
    or
  • just before the activity or fragment getting destroyed by overriding the method onDestroy() in either the activity or the fragment, you should store the last value it had and when you start your activity or fragment you need to fetch it from sharedpreferences and assign it to Task.tasksCounter.

N.B : just incase you don't know, you get the value of static field by calling the class it self and you don't need to create a new object of this class to get its value, calling the next code is sufficent to get and edit its value :

Task.tasksCounter

and at the end,
since you have your complex data now (Task class), I would highly suggest to stop using SharedPreferences for storing everything and you start reading and switching to Room Database

Room Database provides you with necessary storing abilities of a Database including :

  • having an autoincrement identifier without having you worried about their values
  • storing and getting your data with just one line of code with a simple query instead of calling get with keys a 100 times.

with changing your Task class into an entity that have an autoincrement field you'll be good to go and ready to use Room to store your tasks.

这篇关于android studio中的recyclerview项目事故(不良行为)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆