ListView + CheckBoxでチェック可能なリストを作るときの注意点
- 各行にチェックボックスがあって
- チェック状態が変わると、それが変数に保持される
こんな一見簡単そうなものを実装しようとしてハマったメモ。
まず、自前でArrayAdapterを継承してアダプタを作る事になると思うんですが、
getViewをオーバーライドして、各行のViewが要求されたときにチェックボックスを含むViewを返してやるだけ…だとだめなことがあります。
具体的に言うと、以下の第2引数のconvertViewを使い回してposition用のViewとしてreturnするような場合です。
public View getView(int position, View convertView, ViewGroup parent)
convertView != nullの場合、その中身は「どこかのpositionの表示のためにgetViewでreturnされたView」です。
必ずしも、いまgetViewが呼ばれてるpositionで使われたViewではないので、returnする前にチェック状態をリセットしないといけない。
それをしないと、
「あるチェックボックスをチェックしたら、表示範囲外のチェックボックスが何故か一緒にチェックされてた。何を言っているかわからないと思うが(ry」ということになります。
このあたりに対処できるコードを置いておきます。
特に BooleanListAdapter#getViewあたり。
参考になれば幸いです。
public class MultipleChoiceListActivity extends ListActivity { /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setListAdapter(new BooleanListAdapter(this, R.layout.list_row1, buildTestData())); } protected List<Boolean> buildTestData() { List<Boolean> items = new ArrayList<Boolean>(); items.add(true); items.add(false); items.add(true); items.add(false); items.add(true); items.add(false); items.add(true); items.add(false); items.add(true); items.add(false); items.add(true); items.add(false); items.add(true); items.add(false); return items; } class BooleanListAdapter extends ArrayAdapter<Boolean> { protected LayoutInflater inflater; private List<Boolean> items; private int rowLayoutResourceId; public BooleanListAdapter(Context context, int rowLayoutResourceId, List<Boolean> items) { super(context, rowLayoutResourceId, items); this.items = items; this.inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); this.rowLayoutResourceId = rowLayoutResourceId; } @Override public View getView(int position, View convertView, ViewGroup parent) { View view = convertView; if (view == null) { Log.i("MultipleChoiceListActivity", "constructing a new view for " + String.valueOf(position) + "-th list row"); view = inflater.inflate(R.layout.list_row1, null); } else { Log.i("MultipleChoiceListActivity", "reusing the view for " + String.valueOf(position) + "-th list row"); } CheckBox checkBox = (CheckBox) view.findViewById(R.id.CheckBox01); Log.i("MultipleChoiceListActivity", "CheckBox#setChecked(" + String.valueOf(items.get(position)) + ")"); // 明示的にクリアするならこうだけど、とにかくsetChecked前にリスナ登録すればOK // checkBox.setOnCheckedChangeListener(null); final int p = position; // 必ずsetChecked前にリスナを登録(convertView != null の場合は既に別行用のリスナが登録されている!) checkBox.setOnCheckedChangeListener(new OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { Log.i("MultipleChoiceListActivity", "p=" + String.valueOf(p) + ", isChecked=" + String.valueOf(isChecked)); items.set(p, isChecked); } }); checkBox.setChecked(items.get(position)); return view; } } }