ButterKnife で ListView に OnItemClick を適用するときの注意

[ButterKnife](https://github.com/JakeWharton/butterknife)、便利ですよね。Activity が沢山のなんちゃら Listener を implements しなくても、アノテーションを付けるだけでその辺を勝手によしなにしてくれますし、findViewById の手間も @InjectView で解決できます。

さて、`ListView`には`OnItemClickListener`という、`ListView`の中のアイテムをクリックした時のハンドラがあります。
ButterKnife を用いると、以下のようにサクッと書くことが出来るようになりますね。

```java

public class ListViewActivity extends Activity {
    @InjectView(R.id.listview)
    ListView listview;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_listview);
        ButterKnife.inject(this);

        // adapter をセットする
    }

    @OnItemClick(R.id.listview)
    public void onListItemClick(int position) {
        Log.v("ListView", "selected pos=" + position);
    }
}
```

`onItemClick`メソッドの引数は、`AdapterView.OnItemClickListener#onItemClick(AdapterView<?>, View, int, long)`の引数の全部またはどれか好きなものを指定できます。おそらく最もよく使うのは、どの`position`のものを選択したかを判定したいので、第三引数の`position`なのではないでしょうか。今回もそれを使っています。

`ListView`のアイテムをクリックすると、選択した位置がログに出力されるはずです。

ここで、以下のような実装をしてみたとします。

```java

public class ListViewActivity extends Activity {
    @InjectView(R.id.listview)
    ListView listview;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_listview);
        ButterKnife.inject(this);

        // ListView にヘッダを追加する
        TextView label = new TextView(this);
        label.setText("List header!");
        listview.addHeaderView(label);

        // adapter をセットする
    }

    @OnItemClick(R.id.listview)
    public void onListItemClick(int position) {
        Log.v("ListView", "selected pos=" + position);
    }
}
```

ヘッダに View を追加しました。簡単ですね。

`ListView`のアイテムをクリックすると、選択した位置がログに出力されるはずです。ヘッダもクリック出来るようになっていると思います。
が、よく見てみると、`position`がヘッダを起点に`0`からカウントされていることがわかると思います。

つまり、セットした`ListAdapter`のアイテムの`position`ではなく、追加したヘッダの分だけずれた`position`が引数に渡されてきます。

ヘッダやフッタを`ListView`にセットした場合、最初に`ListView#setAdapter(ListAdapter)`でセットした`ListAdapter`ではない別のアダプタが内部的に使用されます。つまり、`ListView#getAdapter()`をすると、その内部的に使用されているアダプタが返ってくることになります。これは`HeaderViewListAdapter`と呼ばれ、最初にセットした`ListAdapter`はこの`HeaderViewListAdapter`が内部に保持しています。よって、`position`から正しい（`ListAdapter`が持つアイテムの`0`から始まる）位置を知るには、以下のような実装をしてあげないといけません。

```java

public class ListViewActivity extends Activity {
    @InjectView(R.id.listview)
    ListView listview;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_listview);
        ButterKnife.inject(this);

        // ListView にヘッダを追加する
        TextView label = new TextView(this);
        label.setText("List header!");
        listview.addHeaderView(label);

        // adapter をセットする
    }

    @OnItemClick(R.id.listview)
    public void onListItemClick(int position) {
        //ヘッダやフッタを追加した ListView から getAdapter するときは必ずこうする
        HeaderViewListAdapter wrapper = (HeaderViewListAdapter) listview.getAdapter();

        // 正しい位置を計算するため、ヘッダの数だけ position がズレるので引き算する
        int itemPosition = position - wrapper.getHeadersCount();
        Log.v("ListView", "selected pos=" + itemPosition);

        // ズレた分の引き算で itemPosition が負数になる場合は position がヘッダの位置だったと分かる
        if (itemPosition < 0) {
            return;
        }

        // 選択されたアイテムを引くときはこれを使う
        ListAdapter wrapped = wrapper.getWrappedAdapter();
        Object selectedItem = wrapped.getItem(itemPosition);
    }
}
```

勿論、フッタがある場合はそれも考慮する必要がありますし、両方あるときはそれに応じて計算が必要です。