LoginSignup
23
25

More than 5 years have passed since last update.

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

Posted at

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

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


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のアイテムをクリックすると、選択した位置がログに出力されるはずです。

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


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から始まる)位置を知るには、以下のような実装をしてあげないといけません。


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);
    }
}

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

23
25
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
23
25