【Android】カスタムリストビューにCheckBoxを追加したとき別の行までチェックされる事象の対応

AndroidでCheckBox付きのカスタムListViewを実装し、動作確認したところチェックした行以外の行までチェックされてしまう謎現象が発生しました。

カスタムListViewの実装については以下参照

【Android】VideoViewを使って動画再生アプリを作る③ -- カスタムリストビュー --

今回は単純にチェックボックスと単純な名前を一つだけ持ったListを表示しています。

問題のあるコード

Adapterクラス

public class ListAdapter extends ArrayAdapter<String> {
    private int resource;
    private List<String> items;
    private LayoutInflater inflater;

    public ListAdapter(Context context, int resource, List<String> items) {
        super(context, resource, items);
        this.resource = resource;
        this.items = items;
        this.inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        View view;

        if (convertView != null) {
            view = convertView;
        } else {
            view = inflater.inflate(resource, null);
        }

        TextView itemName = view.findViewById(R.id.item_name);
        itemName.setText(items.get(position));

        return view;
    }
}

動作確認

チェックした行以外の行までチェックされる上、スクロールするとチェック状態がめちゃくちゃになってます。

使いにくいというより普通にバグですね。

原因と修正方法について

デバッグしてみたところどうやらこのgetViewメソッドはスクロールするたびに画面に表示されている行毎に呼び出されています。

この事象の原因はgetView内でviewを使いまわしているから発生するようです。

以下の対応をすることで解消できました。

  1. 各行のチェック状態保持するListをメンバー変数に持っておく。
  2. コンストラクタでitemsの行分を初期化する。
  3. getViewで 1. の変数から状態を取得してチェック状態をセットする。
  4. チェックした時に 1. の変数を見て状態を切り替える。

Adapterクラス (修正版)

public class ListAdapter extends ArrayAdapter<String> {
    private int resource;
    private List<String> items;
    private LayoutInflater inflater;
    private List<Boolean> itemsChecked;

    public ListAdapter(Context context, int resource, List<String> items) {
        super(context, resource, items);
        this.resource = resource;
        this.items = items;
        itemsChecked = new ArrayList<>();
        for (int i = 0; i < items.size(); i++) {
            itemsChecked.add(false);
        }
        this.inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        View view;

        if (convertView != null) {
            view = convertView;
        } else {
            view = inflater.inflate(resource, null);
        }

        TextView itemName = view.findViewById(R.id.item_name);
        itemName.setText(items.get(position));

        CheckBox itemCheck = view.findViewById(R.id.item_check);
        itemCheck.setChecked(itemsChecked.get(position));
        itemCheck.setOnClickListener(new View.OnClickListener(){
            @Override
            public void onClick(View v) {
                if (!itemsChecked.get(position)) {
                    itemsChecked.set(position, true);
                    itemCheck.setChecked(true);
                } else {
                    itemsChecked.set(position, false);
                    itemCheck.setChecked(false);
                }
            }
        });

        return view;
    }
}

動作確認

無事意図した動きになりました。

コメント

このブログの人気の投稿

docker-compose up で proxyconnect tcp: dial tcp: lookup proxy.example.com: no such host

docker-compose で起動したweb、MySQLに接続できない事象

【PHP】PHP_CodeSnifferを使う(コーディングルールのカスタマイズ)