今天在使用
RecycleView
和checkBox
做列表时发现一个很有趣的问题,当我选中某一个checkBox
后,RecycleView
向下滑动时发现其他的checkBox
也被选中了,bug
图如下:
发生这个问题的原因在于
RecycleView
的复用机制,当我们向下滑动时RecycleView
会复用离开屏幕的Holder
从而来提高效率,而Holder
会保存checkBox
的选中状态,所以出现了上图这个bug。
解决方法:
使用一个集合来标记所有
checkBox
的位置和状态,点击checkBox
时将位置和状态存入集合。当我们向下滑动时,新的item
都会去集合中获取判断自己的选中状态,因为没有在集合存过所以返回false
,因此新的item
都是未选中的。而当我们向上滑动时,每个item
也会去集合中获取判断自己的选中状态。如果之前我们选中过则会在集合中有记录,我们传入下标来获取item
的状态。而SparseBooleanArray
作为这个集合则是最好的选择,它以int
为key
,boolean
作为value
,如果不存在则返回false
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.MyViewHolder> {
List<Integer> mList;
//保存状态的集合,SparseBooleanArray是以int为key,boolean作为value
private SparseBooleanArray mCheckStates = new SparseBooleanArray();
public MyAdapter(List<Integer> mList) {
this.mList = mList;
}
@NonNull
@Override
public MyAdapter.MyViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) {
MyViewHolder holder = new MyViewHolder(View.inflate(Main4Activity.this, R.layout.item_rv, null));
return holder;
}
@Override
public void onBindViewHolder(@NonNull MyViewHolder myViewHolder, final int i) {
//以下标给每个checkBox加上唯一标识
myViewHolder.checkBox.setTag(i);
Log.e("tag", i+"");
//通过标识去集合查找自己的状态,如果不存在则返回false
myViewHolder.checkBox.setChecked(mCheckStates.get((Integer) myViewHolder.checkBox.getTag()));
myViewHolder.checkBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
//checkBox被点击时将自己的标识(下标)和状态存入集合
mCheckStates.put((Integer) buttonView.getTag(), isChecked);
}
});
}
@Override
public int getItemCount() {
return mList.size();
}
class MyViewHolder extends RecyclerView.ViewHolder {
CheckBox checkBox;
public MyViewHolder(View view) {
super(view);
checkBox = (CheckBox) view.findViewById(R.id.checkBox);
}
}
}
RecycleView
加载数据时都会调用onBindViewHolder()
,onBindViewHolder()
中先是给每个item
设置一个tag
值,然后在访问集合获取状态,刚开时集合肯定没有item
的记录因此则返回false
,则调用setChecked()
来设置状态。而我们对CheckBox
进行监听,点击时将状态保存到集合。例如说我们点击了第一个CheckBox
则集合中会保存它的下标和状态(true
)。当我们向下滑动再向上滑动到第一项时,第一个item
会根据自己的下标去集合获取状态,得到的是true
则第一个item
设置成选中的。
RecycleView和checkBox组合实现全选和取消全选功能:
弄懂了上面的问题之后这个功能就变得很简单了,我们只需要开放一个接口,外部通过这个接口去修改集合中所有的
item
的选中状态为true
或者false
,然后刷新RecycleView
即可。
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.MyViewHolder> {
List<Integer> mList;
//保存状态的集合,SparseBooleanArray是以int为key,boolean作为value
private SparseBooleanArray mCheckStates = new SparseBooleanArray();
public MyAdapter(List<Integer> mList) {
this.mList = mList;
}
/**
* 设置全选的方法
*
* @param isSelect
*/
public void setSelect(boolean isSelect) {
for (int i = 0; i < mList.size(); i++) {
//历遍整个数据源,将所有的item状态设成true或false
mCheckStates.put(i, isSelect);
}
}
@NonNull
@Override
public MyAdapter.MyViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) {
MyViewHolder holder = new MyViewHolder(View.inflate(Main4Activity.this, R.layout.item_rv, null));
return holder;
}
@Override
public void onBindViewHolder(@NonNull MyViewHolder myViewHolder, final int i) {
//给每个checkBox加上唯一标识
myViewHolder.checkBox.setTag(i);
Log.e("tag", i+"");
//通过标识去集合查找自己的状态,如果不存在则返回false
myViewHolder.checkBox.setChecked(mCheckStates.get((Integer) myViewHolder.checkBox.getTag()));
myViewHolder.checkBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
//checkBox被点击时将自己的状态存入集合,tag作为key值
mCheckStates.put((Integer) buttonView.getTag(), isChecked);
}
});
}
@Override
public int getItemCount() {
return mList.size();
}
class MyViewHolder extends RecyclerView.ViewHolder {
CheckBox checkBox;
public MyViewHolder(View view) {
super(view);
checkBox = (CheckBox) view.findViewById(R.id.checkBox);
}
}
}
调用:
button1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//调用接口
adapter.setSelect(true);
//刷新
adapter.notifyDataSetChanged();
}
});
button2.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
adapter.setSelect(false);
adapter.notifyDataSetChanged();
}
});