2011-02 << 2011-03 >> 2011-04

2011-03-19 (土)

*[Android] ListViewをちゃんと使おう

Androidアプリを作る上で,ListViewは色々な場面で使える便利なViewですが,いざ使い方を調べてみると変な使い方をしている例がとても多いです.AndroidのSDKをよく読んで,設計意図を汲んであげましょう.

基本的な部分

基本的には,このあたりを読んでおくと良いです.

特に,view.setTag()とview.findViewWithTag()はViewに任意のデータを持たせることが可能で,しかも検索できるという便利なものなので,使うと綺麗に書けることが多いです.

内容の更新

リスト内のデータが変わったときは,表示内容を更新する必要があります.

notifyDataSetChanged()を使う

AdapterのnotifyDataSetChanged()等を呼び出すとListViewの表示を更新することが出来ます.

ただ気をつけなければいけないのは,このメソッドを呼び出すと表示されている要素を全て再表示します.このときgetView()が画面に表示されている要素数分だけ呼び出されます.

リストの要素数が変わったときなどは仕方がないことが多いですが,ひとつの要素の画像やテキストを変更したいという場合もあると思います.例えば,画像をAsyncTaskで読んでいて,読み終わったら反映したいという場合です.

画像ひとつ表示したいだけなのに,全てのViewの内容をセットしなおすのはコストが高すぎます.そもそも,変更したい要素が画面内に無いのに,notifyDataSetChanged()を呼ぶのは完全に無駄になります.

view.setTag()とview.findViewWithTag()を使う

特定の要素を書き換えたい場合,view.setTag()とview.findViewWithTag()の出番です.

例えば,getView()の中で,まだ読み込まれていない画像のImageViewに対して,

    tumbnailImage.setTag(position);

として,positionをタグとしてセットします.

画像が複数ある場合などは,リストの要素自体のViewにセットするか,position以外の識別子をつけましょう.比較可能ならInteger以外でも何でも良いです.

あとは画像の読み込みが完了したときにfindViewWithTag()を使ってListViewから探します.

    ImageView tumbnailImage = (ImageView) mListView.findViewWithTag(pos);
    if (tumbnailImage != null) {
        tumbnailImage.setImageBitmap(img);
    }

画像が画面内に表示されていない(正確にはListViewが保持していない)場合は,見つからずにnullが返ってくるので更新の必要はありません.

読み込んだ画像は,改めて表示されたときにgetView()で使うことになるので,キャッシュにでも入れておけばよいでしょう.

ヘッダとフッタをつける

ListViewの最初や最後に,特別な要素を加えたい場合があると思います.例えば,リストの最後に続きを表示するためのボタンを設置したい場合などです.

ダメな例

最初に見たときはびっくりしましたが,下のコードのように,positionを見て生成するlayoutを分けるというコードを見ることが多いです.

    public View getView(int position, View convertView, ViewGroup parent) {
        View v = null;

        if (position == 0) {
            v = inflater.inflate(R.layout.list_header, null);
            // 色々
        } else if (position == mArray.size()-1) {
            v = inflater.inflate(R.layout.list_footer, null);
            // 色々
        } else {
            v = inflater.inflate(R.layout.list_row, null);
            // 色々
        }

        return v;
    }

こういうことをすると,viewの再利用が出来ないのでパフォーマンスが落ちますし,体感的には気にならなくても,スマートフォンの場合は,CPUパワー使うことはバッテリーの持続時間に直結するのでやめてもらいたいです.

WrapperListAdapterを使う

リストの先頭や末尾に指定した要素を入れてくれる,WrapperListAdapterというものがるので,これを使うのがまともな方法です.

WrapperListAdapterは自分で生成しても良いですが,少し面倒なのと,ListView自体が,渡されたAdapterをWrapperListAdapterでラッピングしてくれる機能を提供しているので,ListViewに任せます.

使い方は簡単で,ListViewに対して,addHeaderView()やaddFooterView()でヘッダやフッタを追加してやると,自動的にWrapperListAdapterが内部で生成されてよしなにやってくれます.ただし,setAdapter()を呼び出す前にやる必要があるので気をつけます.

        TextView tv = new TextView(this);
        tv.setText("This is footer");
        getListView().addFooterView(tv, null, false);

ここでは単なるTextViewにしましたが,お好きなlayoutをinflateしてください.

あと,getAdapter()で取得できるアダプタは自分で設定したものではなく,WrapperListAdapterになっているので気をつけましょう.

フッタやヘッダを選択不可にしたいときには,addHeaderView()等を呼ぶときに指定できます.ただ,選択不可にすると区切り線が消えてしまったりするので,自分で解決する必要がありそうです.

動的に選択不可・可を切り替えたいときは,WrapperListAdapterのisEnable()をオーバーライドする必要が出てきて,そうなるとListViewが勝手にやってくれなくなるので,ArrayAdapter等を使うのをやめて,WrapperListAdapter相当の機能を持ったAdapterを実装したほうが楽かもしれません.

基本は選択不可でフッタ上にボタンを置いてそれの有効無効を切り替えるのが楽で良い気がします.