今天在使用
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();
}
});