いろいろガンガンいこうぜ。体も大事に

普段はJavaでAndroidアプリ開発しているプログラマーのブログです。

ListAdapterについて調べてみた(4) SimpleAdapter編その3

 前回の続きということで,SImpleAdapterの使い方です。今回はViewBinderを見て行きます。

 前回,SimpleAdapterの内部でどのようにViewとデータがバインドされるかを確認しました。SimpleAdapterは,BaseAdapterを継承していろいろ実装するよりも少ないコード量でバインドはできるのですが,柔軟な処理はできません。例えば,URLのデータが文字列形式であり,AsyncTaskを用いてそのURLから画像を取得し,その画像をImageViewにバインドする,というようなよく行われる処理ができません。

 このような処理は,ViewBinderを使えば実行することが可能です。実例を挙げて,使い方を見て行きます。


ViewBinderを使ってやりたいこと

 先ほど上げた例を実際に例示するとViewBinder以外のコード量が多くなってしまうので,今回は違う例を示します。

 色の情報を表現する以下のようなシンプルなモデルがあります。

public class ColorData {
    private final String name;
    private final String colorString;

    public ColorData(String name, String colorString) {
        this.name = name;
        this.colorString = colorString;
    }

    public String getName() {
        return name;
    }

    public String getColorString() {
        return colorString;
    }
}

 nameには,青や赤など色の名前が文字列で入れます。colorStringには,#0000ffや#ff0000などその色の16進表現の文字列が入れます。次のような感じです。

Color red = new ColorData("赤", "#ff0000");
Color blue = new ColorData("青", "#0000ff");


 次にViewです。ListView中の1つのセルは,色の名前を表示するTextViewと色を実際に表示する単色のImageViewを構成要素とします。

 色の名前をTextViewにバインドすることは前回と前々回でみてきた内容から,SimpleAdapterの機能を使えば可能です。

 しかし,色の16進表現の文字列とImageViewをどのようにSimpleAdapterでバインドすればいいでしょうか。文字列のデータとImageViewのバインドは,デフォルトでは次のどちらかの処理が行われます。文字列の中身がイメージリソースのidの数値の文字列表現になっていて,Integer型に変換され,setImageResourceが呼ばれる。もしくは,その文字列をUri.parseした結果がImageView#setImageURIの引数になる。
 今回はそのどちらでもありません。色の16進表現の文字列をImageViewとバインドする時,通常のバインドでなくて,独自のバインド処理を定義して実行をする必要があります。

 それではこのような処理を,ViewBinderを使って実現します。

ViewBinderの定義と使い方
    private static class ColorViewBinder implements ViewBinder {

        @Override
        public boolean setViewValue(View view, Object data, String textRepresentation) {
            if (view.getId() == R.id.color_image_view) {
                int color = Color.parseColor(data.toString());
                ImageView imageView = (ImageView) view;
                imageView.setImageDrawable(new ColorDrawable(color));
                return true;
            } else {
                return false;
            }
        }
    }


このViewBinderを,ListActivityのサブクラスのonCreateメソッド内で,

SimpleAdapter adapter = new SimpleAdapter(this, data, layoutResourceId, FROM, TO);
adapter.setViewBinder(new ColorViewBinder());

setListAdapter(adapter);

の用に記述すれば,やりたいことは実現可能です。(コード全体は最後に載せます。)
それではViewBinderの詳細をみていきましょう。


ViewBinderについて

Android Developersのクラスリファレンスはこちら

 完全修飾クラス名は,android.widget.SimpleAdapter.ViewBinder。クラスでなくて,public static なSImpleAdapterのクラス内のinterfaceですね。メソッドは次の一つのみです。

abstract boolean setViewValue(View view, Object data, String textRepresentation)

このメソッドの引数は

  • View型のview データをバインドするView
  • Object型のdata Viewにバインドするデータ
  • String型のtextRepresentation dataのtoStringメソッドの結果。もしこれがnullだったら空文字

となっています。特別なバインドを行いたい場合このsetViewValueメソッドをその目的に応じて実装すればいいです。

 boolean型の返り値は,もしViewBinderによってデータとビューがバインドされたらtrueを返し,バインドしないのであればfalseを返します。SimpleAdapterインスタンスはViewBinderのインスタンスがセットされると,全てのビューとデータのバインド時に,このsetViewValueメソッドを呼び出します。色の文字列表現をImageViewにバインドする場合は,ViewBinder#setViewValueで定義した処理を行いたいです。しかし,今回の例の色の名前をTextViewにバインドするような処理は,デフォルトのSimpleAdapterの処理に任せ,自分で実装はしたくありません。Viewのidを調べ,独自の処理を行いたいものでなかったら,返り値でfalseを返せば,前回のSimpleAdapterのデフォルトのバインド処理が行われます。

今回のViewBinderをみてみる


今回の作ったViewBinderを実装したクラスのsetViewValueをみてみます。

public boolean setViewValue(View view, Object data, String textRepresentation) {
	if (view.getId() == R.id.color_image_view) {
		int color = Color.parseColor(data.toString());
		ImageView imageView = (ImageView) view;
		imageView.setImageDrawable(new ColorDrawable(color));
		return true;
	} else {
		return false;
	}
}


 まず,Viewのidを調べます。独自でバインド処理を行いたいImageView以外ではfalseを返すようにします。独自処理をしたいImageView(idはcolor_image_view)では,文字列をcolorのint表現に変換,ColorDrawableインスタンスの作成,ImageViewへのセットを行います。最後にViewBinder内でバインドし,デフォルトのバインド処理をする必要が無いので,返り値はtrueとします。



まとめと個人的な意見

 3回に渡ってSimpleAdapterを見てきました。Adapterは「BaesAdapterを継承したクラスを定義し,その中でgetViewメソッドをオーバーライドして,ViewHolderを定義して,セルのViewのタグにViewHolderをセットする」という風に書かれた記事やブログなどは数も多くよく見るのですが,それに比べるとSimpleAdapterの記事やブログは少なく感じます。

 パフォーマンスの問題はあれどSimpleAdapter(場合によってはArrayAdapter)をうまく使うことでで少ないコード量で目的のアダプターを作れるます。ViewHolderを使った場合とパフォーマンスの差がどうなのかにもよりますが,個人的にはSImpleAdapterの方が好きです。findViewByIdってどのくらい重いのかな?

 2010年のGoogle IOのなかで,ListViewに関するセッションがあります。(こちら)

 上記の講演・スライドでもあるように,たしかにViewHolderを使う方が早くなるというのは分かるのですが,2013年の今の端末だとその差がどれくらいになるのかと,その差がどのくらい気になる物なのか,すこし興味があります。


 それでは,また会えることを祈りつつ。


サンプルコード
package com.mrstar.hello_adapter;

import android.app.ListActivity;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.os.Bundle;
import android.view.View;
import android.widget.ImageView;
import android.widget.SimpleAdapter;
import android.widget.SimpleAdapter.ViewBinder;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class SimpleAdapterWithViewBinderActivity extends ListActivity {
    private static final String KEY_NAME = "name";
    private static final String KEY_COLOR = "color";

    private static final String[] FROM = new String[] {
            KEY_NAME, KEY_COLOR
    };
    private static final int[] TO = new int[] {
            R.id.name_text_view, R.id.color_image_view
    };

    private static final List<ColorData> COLOR_DATA;
    static {
        COLOR_DATA = new ArrayList<ColorData>();
        COLOR_DATA.add(new ColorData("青", "#0000ff"));
        COLOR_DATA.add(new ColorData("シアン", "#00ffff"));
        COLOR_DATA.add(new ColorData("緑", "#00ff00"));
        COLOR_DATA.add(new ColorData("マゼンタ", "#ff00ff"));
        COLOR_DATA.add(new ColorData("赤", "#ff0000"));
        COLOR_DATA.add(new ColorData("黄色", "#ffff00"));
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        List<ColorData> colorDataList = new ArrayList<ColorData>(COLOR_DATA);
        List<Map<String, ?>> data = colorDataListToMapList(colorDataList);

        int layoutResourceId = R.layout.list_item_for_simple_adapter;

        SimpleAdapter adapter = new SimpleAdapter(this, data, layoutResourceId, FROM, TO);
        adapter.setViewBinder(new ColorViewBinder());

        setListAdapter(adapter);
    }

    private static class ColorViewBinder implements ViewBinder {

        @Override
        public boolean setViewValue(View view, Object data, String textRepresentation) {
            if (view.getId() == R.id.color_image_view) {
                int color = Color.parseColor(data.toString());
                ImageView imageView = (ImageView) view;
                imageView.setImageDrawable(new ColorDrawable(color));
                return true;
            } else {
                return false;
            }
        }
    }

    private List<Map<String, ?>> colorDataListToMapList(List<ColorData> colorDataList) {
        List<Map<String, ?>> data = new ArrayList<Map<String, ?>>();
        for (ColorData colorItem : colorDataList) {
            data.add(colorDataToMap(colorItem));
        }
        return data;
    }

    private Map<String, Object> colorDataToMap(ColorData colorData) {
        Map<String, Object> map = new HashMap<String, Object>();
        map.put(KEY_NAME, colorData.getName());
        map.put(KEY_COLOR, colorData.getColorString());

        return map;
    }
}