LoginSignup
5
6

More than 5 years have passed since last update.

複数選択可のListViewに対し要素の追加/削除を行うときは自分で選択をコピーする必要がある

Last updated at Posted at 2013-11-10

AndroidのListView.CHOICE_MODE_MULTIPLEを使うと,複数選択式のリストが簡単に実装できる.

しかし,これは要素のpositionで選択状態を管理しているらしく,単純に要素を追加・削除しようと思うとバグる.

問題(具体例)

具体的には,以下のようなActivityを考える.ある要素が選択されたあとで,その要素より上に別の要素が追加されたり,その要素より上の要素が削除されたりすると,要素の選択状態がずれて,本来チェックしているのと違う要素が選択されてしまう.

public class MainActivity extends Activity {
    private List<String> animals = new ArrayList<String>(Arrays.asList(new String[]{
            "bird", "cat", "dog", "elephant", "giraffe", "pig", "rabbit", "rhino", "sheep"
    }));
    private ArrayAdapter<String> adapter;
    private ListView listView;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        listView = (ListView) findViewById(R.id.list);
        adapter = new ArrayAdapter<String>(
                this, android.R.layout.simple_list_item_multiple_choice, animals);
        listView.setAdapter(adapter);
        listView.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
            @Override
            public boolean onItemLongClick(AdapterView<?> adapterView, View view, int position, long id) {
                removeItem(position);
                return true;
            }
        });

        findViewById(R.id.add_element_button).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                insertItem("New Item");
            }
        });
    }

    private void insertItem(String item) {
        adapter.insert(item, 0);
    }

    private void removeItem(int position) {
        adapter.remove(adapter.getItem(position));
    }
}
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:orientation="vertical"
              android:layout_width="fill_parent"
              android:layout_height="fill_parent">
  <Button
      android:id="@+id/add_element_button"
      android:layout_width="fill_parent"
      android:layout_height="wrap_content"
      android:text="Add new element"/>
  <ListView
      android:id="@+id/list"
      android:layout_width="fill_parent"
      android:layout_height="fill_parent"
      android:choiceMode="multipleChoice"/>
</LinearLayout>

解決

これを回避するには,以下のように自分で要素の選択状態をコピーして移し替えてあげる必要がある

    private void insertItem(String item) {
        Set<Integer> checkedIndices = copyCheckedItemPositions();
        adapter.insert(item, 0);
        listView.clearChoices();
        for (Integer i: checkedIndices) {
            listView.setItemChecked(i + 1, true);
        }
    }

    private void removeItem(int position) {
        Set<Integer> checkedIndices = copyCheckedItemPositions();
        adapter.remove(adapter.getItem(position));
        listView.clearChoices();
        for (Integer i: checkedIndices) {
            if (i < position) {
                listView.setItemChecked(i, true);
            } else if (i > position) {
                listView.setItemChecked(i - 1, true);
            }
        }
    }

    private Set<Integer> copyCheckedItemPositions() {
        SparseBooleanArray selection = listView.getCheckedItemPositions();
        Set<Integer> checkedIndices = new HashSet<Integer>();
        for (int i = 0; i < selection.size(); i++) {
            if (selection.get(selection.keyAt(i))) {
                checkedIndices.add(selection.keyAt(i));
            }
        }
        return checkedIndices;
    }
5
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
6