RecyclerView中使用CheckBox出现勾选混乱的解决方案
2016年08月28日 18:24:24 Zackratos 阅读数 6289
版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/Zackratos/article/details/52346986
熟悉RecyclerView的人应该都知道,RecyclerView使用了复用机制,当在RecyclerView中得每一项都添加一个CheckBox时,勾选当前页面的几个CheckBox会发现下面还有其他的CheckBox也被勾选了,今天我们就来讨论一下如何解决这个问题。
首先当然是创建一个项目,然后在activity_main中添加一个RecyclerView控件,当然,在这之前,我们需要先添加RecyclerView的依赖,如下图:
然后 开始编辑activity_main:
-
<?xml version="1.0" encoding="utf-8"?>
-
<LinearLayout
-
xmlns:android="http://schemas.android.com/apk/res/android"
-
android:layout_width="match_parent"
-
android:layout_height="match_parent"
-
android:orientation="vertical">
-
<android.support.v7.widget.RecyclerView
-
android:id="@+id/id_recycler_view"
-
android:layout_width="match_parent"
-
android:layout_height="match_parent">
-
</android.support.v7.widget.RecyclerView>
-
</LinearLayout>
接下来为这个RecyclerView创建一个item布局文件,命名为item_recyclerview,并添加一个CheckBox空间,代码如下:
-
<?xml version="1.0" encoding="utf-8"?>
-
<LinearLayout
-
xmlns:android="http://schemas.android.com/apk/res/android"
-
android:orientation="vertical"
-
android:layout_width="match_parent"
-
android:layout_height="60dp"
-
android:gravity="center_vertical">
-
<CheckBox
-
android:id="@+id/id_check_box"
-
android:layout_width="match_parent"
-
android:layout_height="wrap_content"
-
android:layout_marginLeft="10dp"
-
android:text="CheckBox"/>
-
</LinearLayout>
接下来要编辑MainActivity了,包括从布局文件中找到刚才的RecyclerView控件,然后为其设置Adapter等,过程不再详细叙述,编辑后的代码如下:
-
public class MainActivity extends AppCompatActivity {
-
private RecyclerView recyclerView;
-
private MyAdapter myAdapter;
-
@Override
-
protected void onCreate(Bundle savedInstanceState) {
-
super.onCreate(savedInstanceState);
-
setContentView(R.layout.activity_main);
-
recyclerView = (RecyclerView) findViewById(R.id.id_recycler_view);
-
myAdapter = new MyAdapter();
-
recyclerView.setLayoutManager(new LinearLayoutManager(this));
-
recyclerView.setAdapter(myAdapter);
-
}
-
private class MyAdapter extends RecyclerView.Adapter {
-
private List<String> content;
-
@Override
-
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
-
View view = LayoutInflater.from(MainActivity.this).inflate(R.layout.item_recyclerview, parent, false);
-
return new MyViewHolder(view);
-
}
-
@Override
-
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
-
MyViewHolder myViewHolder = (MyViewHolder) holder;
-
myViewHolder.checkBox.setText(content.get(position));
-
}
-
@Override
-
public int getItemCount() {
-
content = new ArrayList<>();
-
for (int i = 0; i < 100; i++) {
-
content.add("CheckBox" + i);
-
}
-
return content.size();
-
}
-
}
-
private class MyViewHolder extends RecyclerView.ViewHolder {
-
private CheckBox checkBox;
-
public MyViewHolder(View itemView) {
-
super(itemView);
-
checkBox = (CheckBox) itemView.findViewById(R.id.id_check_box);
-
}
-
}
-
}
可以看到,我们为这个RecyclerView设置了100个item,每个item里面都含有一个CheckBox,这时候运行这个应用,勾选出现的屏幕上的某一个或者多个CheckBox之后,当你向下拉的时候,问题出现了,你会发现下面会有很多的CheckBox也被选中了。下面我们就来着手解决这个问题,其实要解决也很简单,可以定义一个boolean类型的数组或者列表,用它来控制CheckBox的选中状态,当某个CheckBox被选中的时候将其选中状态记录在数组或列表中,当某个CheckBox滚动到屏幕上的时候,再用数组或列表中对应的值把它的选中状态改回来就好了,修改后的代码如下:
-
public class MainActivity extends AppCompatActivity {
-
private RecyclerView recyclerView;
-
private MyAdapter myAdapter;
-
@Override
-
protected void onCreate(Bundle savedInstanceState) {
-
super.onCreate(savedInstanceState);
-
setContentView(R.layout.activity_main);
-
recyclerView = (RecyclerView) findViewById(R.id.id_recycler_view);
-
myAdapter = new MyAdapter();
-
recyclerView.setLayoutManager(new LinearLayoutManager(this));
-
recyclerView.setAdapter(myAdapter);
-
}
-
private class MyAdapter extends RecyclerView.Adapter {
-
private List<String> content;
-
private boolean[] flag = new boolean[100];//此处添加一个boolean类型的数组
-
@Override
-
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
-
View view = LayoutInflater.from(MainActivity.this).inflate(R.layout.item_recyclerview, parent, false);
-
return new MyViewHolder(view);
-
}
-
@Override
-
public void onBindViewHolder(RecyclerView.ViewHolder holder, final int position) {
-
MyViewHolder myViewHolder = (MyViewHolder) holder;
-
myViewHolder.checkBox.setText(content.get(position));
-
myViewHolder.checkBox.setOnCheckedChangeListener(null);//先设置一次CheckBox的选中监听器,传入参数null
-
myViewHolder.checkBox.setChecked(flag[position]);//用数组中的值设置CheckBox的选中状态
-
//再设置一次CheckBox的选中监听器,当CheckBox的选中状态发生改变时,把改变后的状态储存在数组中
-
myViewHolder.checkBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
-
@Override
-
public void onCheckedChanged(CompoundButton compoundButton, boolean b) {
-
flag[position] = b;
-
}
-
});
-
}
-
@Override
-
public int getItemCount() {
-
content = new ArrayList<>();
-
for (int i = 0; i < 100; i++) {
-
content.add("CheckBox" + i);
-
}
-
return content.size();
-
}
-
}
-
private class MyViewHolder extends RecyclerView.ViewHolder {
-
private CheckBox checkBox;
-
public MyViewHolder(View itemView) {
-
super(itemView);
-
checkBox = (CheckBox) itemView.findViewById(R.id.id_check_box);
-
}
-
}
-
}
比较这两段代码,我们会发现,首先我们定义了一个长度为100的数组,然后设置CheckBox的选中监听器,把null作为参数传进去,然后用数组中的值设置对应CheckBox的选中状态,最后再一次设置CheckBox的选中监听器,把CheckBox的选中状态储存在数组中的相应位置中。再次运行,发现问题已解决。
下面我们来讨论一下,如果要在RecyclerView的外面再添加一个CheckBox,用外面的CheckBox来控制RecyclerView中的CheckBox的全选和取消全选,要如何实现呢?
其实也很简单,只要用这个CheckBox来控制之前所定义的数组的指就好了。
首先来修改一下activity_main,代码如下:
-
<?xml version="1.0" encoding="utf-8"?>
-
<LinearLayout
-
xmlns:android="http://schemas.android.com/apk/res/android"
-
android:layout_width="match_parent"
-
android:layout_height="match_parent"
-
android:orientation="vertical">
-
<LinearLayout
-
android:layout_width="match_parent"
-
android:layout_height="60dp"
-
android:gravity="center_vertical">
-
<CheckBox
-
android:id="@+id/id_select_all"
-
android:layout_width="match_parent"
-
android:layout_height="wrap_content"
-
android:layout_marginLeft="10dp"
-
android:text="SelectAll"/>
-
</LinearLayout>
-
<android.support.v7.widget.RecyclerView
-
android:id="@+id/id_recycler_view"
-
android:layout_width="match_parent"
-
android:layout_height="match_parent">
-
</android.support.v7.widget.RecyclerView>
-
</LinearLayout>
我们在RecyclerView的外面添加了一个CheckBox,用这个CheckBox来控制RecyclerView中的CheckBox的全选,接下来修改MainActivity:
-
public class MainActivity extends AppCompatActivity {
-
private CheckBox selectAll;
-
private RecyclerView recyclerView;
-
private MyAdapter myAdapter;
-
private boolean []flag;//把flag数组定义为全局变量
-
@Override
-
protected void onCreate(Bundle savedInstanceState) {
-
super.onCreate(savedInstanceState);
-
setContentView(R.layout.activity_main);
-
selectAll = (CheckBox) findViewById(R.id.id_select_all);
-
recyclerView = (RecyclerView) findViewById(R.id.id_recycler_view);
-
flag = new boolean[100];//初始化flag
-
myAdapter = new MyAdapter();
-
recyclerView.setLayoutManager(new LinearLayoutManager(this));
-
recyclerView.setAdapter(myAdapter);
-
//设置外面CheckBox的选中监听器,把它的选中状态赋值给其他的所有CheckBox,然后更新RecyclerView的Adapter
-
selectAll.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
-
@Override
-
public void onCheckedChanged(CompoundButton compoundButton, boolean b) {
-
for (int i = 0; i < 100; i++) {
-
flag[i] = b;
-
}
-
myAdapter.notifyDataSetChanged();
-
}
-
});
-
}
-
private class MyAdapter extends RecyclerView.Adapter {
-
private List<String> content;
-
// private boolean[] flag = new boolean[100];
-
@Override
-
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
-
View view = LayoutInflater.from(MainActivity.this).inflate(R.layout.item_recyclerview, parent, false);
-
return new MyViewHolder(view);
-
}
-
@Override
-
public void onBindViewHolder(RecyclerView.ViewHolder holder, final int position) {
-
MyViewHolder myViewHolder = (MyViewHolder) holder;
-
myViewHolder.checkBox.setText(content.get(position));
-
myViewHolder.checkBox.setOnCheckedChangeListener(null);
-
myViewHolder.checkBox.setChecked(flag[position]);
-
myViewHolder.checkBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
-
@Override
-
public void onCheckedChanged(CompoundButton compoundButton, boolean b) {
-
flag[position] = b;
-
}
-
});
-
}
-
@Override
-
public int getItemCount() {
-
content = new ArrayList<>();
-
for (int i = 0; i < 100; i++) {
-
content.add("CheckBox" + i);
-
}
-
return content.size();
-
}
-
}
-
private class MyViewHolder extends RecyclerView.ViewHolder {
-
private CheckBox checkBox;
-
public MyViewHolder(View itemView) {
-
super(itemView);
-
checkBox = (CheckBox) itemView.findViewById(R.id.id_check_box);
-
}
-
}
-
}
这里我们先把记录CheckBox选中状态的数组定义为全局变量,然后设置外面的CheckBox的监听器,把它的选中状态赋值给其他的所有CheckBox,紧接着更新一下RecyclerView的Adapter就可以了
这里我们在讨论一下RecyclerView的另外一个问题,就是当要删除某个子项的时候会出现删除紊乱的情况,为了说明这个问题,我们先来尝试实践一下,修改item_recyclerview:
-
<?xml version="1.0" encoding="utf-8"?>
-
<RelativeLayout
-
xmlns:android="http://schemas.android.com/apk/res/android"
-
android:layout_width="match_parent"
-
android:layout_height="60dp"
-
android:gravity="center_vertical">
-
<CheckBox
-
android:id="@+id/id_check_box"
-
android:layout_width="match_parent"
-
android:layout_height="wrap_content"
-
android:layout_marginLeft="10dp"
-
android:text="CheckBox"/>
-
<Button
-
android:layout_width="wrap_content"
-
android:layout_height="wrap_content"
-
android:layout_alignParentRight="true"
-
android:layout_marginRight="10dp"
-
android:text="delete"/>
-
</RelativeLayout>
我们增加了一个Button,接下来设置这个Button,当它被点击的时候就删除它所在位置的item,修改MainActivity如下,主要修改的是Adapter部分,其他部分的代码就不贴了:
-
private class MyAdapter extends RecyclerView.Adapter {
-
// private List<String> content;
-
// private boolean[] flag = new boolean[100];
-
@Override
-
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
-
View view = LayoutInflater.from(MainActivity.this).inflate(R.layout.item_recyclerview, parent, false);
-
return new MyViewHolder(view);
-
}
-
@Override
-
public void onBindViewHolder(RecyclerView.ViewHolder holder, final int position) {
-
final MyViewHolder myViewHolder = (MyViewHolder) holder;
-
myViewHolder.checkBox.setText(content.get(position));
-
myViewHolder.checkBox.setOnCheckedChangeListener(null);
-
myViewHolder.checkBox.setChecked(flag[position]);
-
myViewHolder.checkBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
-
@Override
-
public void onCheckedChanged(CompoundButton compoundButton, boolean b) {
-
flag[position] = b;
-
}
-
});
-
//设置监听器,当按钮被点击是,删除它所在的item
-
myViewHolder.button.setOnClickListener(new View.OnClickListener() {
-
@Override
-
public void onClick(View view) {
-
content.remove(position);
-
notifyItemRemoved(position);
-
}
-
});
-
}
-
@Override
-
public int getItemCount() {
-
// content = new ArrayList<>();
-
// for (int i = 0; i < 100; i++) {
-
// content.add("CheckBox" + i);
-
// }
-
return content.size();
-
}
-
}
-
private class MyViewHolder extends RecyclerView.ViewHolder {
-
private CheckBox checkBox;
-
private Button button;//定义删除按钮
-
public MyViewHolder(View itemView) {
-
super(itemView);
-
checkBox = (CheckBox) itemView.findViewById(R.id.id_check_box);
-
button = (Button) itemView.findViewById(R.id.id_delete);
-
}
-
}
这是点击Button,我们会发现,问题出现了,第一次可以正常删除,第二次删除的item却是我们点击的Button所在的下一个item,后面的删除也会各种混乱,这是因为函数里面的传入的参数position,它是在进行onBind操作时确定的,在删除单项后,已经出现在画面里的项不会再有调用onBind机会,这样它保留的position一直是未进行删除操作前的postion值,对于尚未进入画面的单项来说,它会使用新的position值(好吧这段是抄的,其实我也不太懂啥意思),解决方法如下:
-
myViewHolder.button.setOnClickListener(new View.OnClickListener() {
-
@Override
-
public void onClick(View view) {
-
content.remove(position);
-
notifyItemRemoved(position);
-
notifyItemRangeChanged(position, content.size());//对于被删掉的位置及其后range大小范围内的view进行重新onBindViewHolder
-
}
-
});
只要加一行代码就好了,这行代码的作用就是对于被删掉的位置及其后range大小范围内的view进行重新onBindViewHolder
此项目已上传到githut:点击打开链接