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

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

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


### 問題(具体例)

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

```java
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
<?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>
```

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

```java
    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;
    }
```