問題
某次在寫RecyclerView時, 內裝CheckBox的時候,
滾動RecyclerView會造成CheckBox勾選亂跳。
完整程式碼
以下是我的完整程式碼, 先將問題呈現出來。
這邊是改自於如何使用RecyclerView
Gradle部分先加入這行才能使用RecyclerView, 版本記得用android sdk 25
如果你是更低的版本那麼記得調整為屬於你SDK的版本
compile 'com.android.support:recyclerview-v7:25.0.1'
接著是主要程式區 這邊是給MainActivity.java專用的main_activity.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context=".MainActivity">
<android.support.v7.widget.RecyclerView
android:id="@+id/list_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</RelativeLayout>
在來就是我們的MainActivity.java部分
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ArrayList<Item> myDataset = new ArrayList<>();
for(int i = 0; i < 100; i++){
Item item = new Item();
if(i == 2) {
item.setCheck(true);
} else{
item.setCheck(false);
}
item.setText(i+"");
myDataset.add(item);
}
MyAdapter myAdapter = new MyAdapter(myDataset);
RecyclerView mList = (RecyclerView) findViewById(R.id.list_view);
final LinearLayoutManager layoutManager = new LinearLayoutManager(this);
layoutManager.setOrientation(LinearLayoutManager.VERTICAL);
mList.setLayoutManager(layoutManager);
mList.setAdapter(myAdapter);
}
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> {
private List<Item> mData;
public class ViewHolder extends RecyclerView.ViewHolder {
public TextView mTextView;
public CheckBox mCheckBox;
public ViewHolder(View v) {
super(v);
mTextView = (TextView) v.findViewById(R.id.info_text);
mCheckBox = (CheckBox) v.findViewById(R.id.info_chcekbox);
}
}
public MyAdapter(List<Item> data) {
mData = data;
}
@Override
public MyAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext())
.inflate(R.layout.item, parent, false);
ViewHolder vh = new ViewHolder(v);
return vh;
}
@Override
public void onBindViewHolder(final ViewHolder holder, final int position) {
Item item = mData.get(position);
holder.mTextView.setText(item.getText());
holder.mCheckBox.setChecked(item.isCheck());
holder.mCheckBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton compoundButton, boolean b) {
holder.mCheckBox.setChecked(b);
mData.get(position).setCheck(b);
}
});
}
@Override
public int getItemCount() {
return mData.size();
}
}
private static class Item{
String text;
boolean check;
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
public boolean isCheck() {
return check;
}
public void setCheck(boolean check) {
this.check = check;
}
}
}
而你會發現recyclerView的item會有變紅字, 所以我們補上item的xml部分
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:layout_marginLeft="20dp"
android:layout_alignParentLeft="true"
android:id="@+id/info_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<CheckBox
android:layout_marginRight="20dp"
android:layout_alignParentRight="true"
android:id="@+id/info_chcekbox"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</RelativeLayout>
這樣一來就大致都貼上來了,
此時可以執行程式你會看到
有一個選項被打勾惹
原來是我們在初始化的時候所設定的
for(int i = 0; i < 100; i++){
Item item = new Item();
if(i == 2) {
item.setCheck(true);
} else{
item.setCheck(false);
}
item.setText(i+"");
myDataset.add(item);
}
但是持續滾動你會發現
居然打勾消失了
原來原因出在這段
@Override
public void onBindViewHolder(final ViewHolder holder, final int position) {
Item item = mData.get(position);
holder.mTextView.setText(item.getText());
holder.mCheckBox.setChecked(item.isCheck());
holder.mCheckBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton compoundButton, boolean b) {
holder.mCheckBox.setChecked(b);
mData.get(position).setCheck(b);
}
});
}
這段看起來很正常啊! 有什麼問題呢?
因為我們每次滾動的時候都會再去呼叫onBindViewHolder這個方法,
可是呢, 我們又必須初始化checkbox把資料塞進去,
這時候CompoundButton.OnCheckedChangeListener之前就設定過了以後,
在CheckBox被改變值的時候,
就會被呼叫, 看出問題了嗎?
當我們onCheckedChanged被呼叫的時候,
就會去改變我們的List內的Item物件的值,
所以資料就會跟著錯亂。
解法
那怎麼辦呢?其實很簡單,
因為我們不想要CheckBox設定值的時候就去調整值
而是要使用者去勾選CheckBox的時候才去改變值
所以我們改成使用OnClickListener這個事件
就可以判斷是否由使用者來改變值了。
@Override
public void onBindViewHolder(final ViewHolder holder, final int position) {
Item item = mData.get(position);
holder.mTextView.setText(item.getText());
holder.mCheckBox.setChecked(item.isCheck());
holder.mCheckBox.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
boolean b = ((CheckBox) view).isChecked();
holder.mCheckBox.setChecked(b);
mData.get(position).setCheck(b);
}
});
}
此時你就會發現一切都恢復正常了,
怎麼滾動都是正常的。