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

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

Javaのコンストラクターなどの初期化について (1)

Javaコンストラクターなどの初期化について整理してみようと思います。

  • title(String型)
  • message(String型)
  • description(String型)

という3つのString型のフィールドを持ち,コンストラクタでそれらを初期化するクラスを作ります。 下記は良くない実装です。

package com.mrstar.sample_initialize;

public class PrimaryConstructorSample {
    private final String title;
    private final String message;
    private final String description;

    public PrimaryConstructorSample(String title, String message, String description){
        this.title = title;
        this.message = message;
        this.description = description;
    }

    public PrimaryConstructorSample(String title, String message){
        this.title = title;
        this.message = message;
        this.description = "Default Description.";
    }

    public PrimaryConstructorSample(String title){
        this.title = title;
        this.message = "Default Message";
        this.description = "Default Description.";
    }
    // ~~ 以下省略 ~~ //
}

この実装は重複があります。 例えば,文字列型の引数を2つとるコンストラクタと3つとるコンストラクタ,それぞれ二カ所でdescriptionフィールドに"Default Description."という,文字列リテラルを代入しています。"Default Description."を違う文字列に変えることにしたとします。片方を変更したは良いがもう片方を忘れてしまようなことが起きてしまう可能性があります。

package com.mrstar.sample_initialize;

public class PrimaryConstructorSample {
    private final String title;
    private final String message;
    private final String description;

    public PrimaryConstructorSample(String title, String message, String description){
        this.title = title;
        this.message = message;
        this.description = description;
    }

    public PrimaryConstructorSample(String title, String message){
        this(title, message, "Default Description.");
    }

    public PrimaryConstructorSample(String title){
        this(title, "Default Message");
    }
    // ~~ 以下省略 ~~ //
}

引数が1つのコンストラクタと2つのコンストラクタは,引数を3つのコンストラクタに処理を委譲しています。 一番上の3つのString型をとるコンストラクタのように,他のコンストラクタからthisで呼ばれるコンストラクタをプライマリコンストラクタと呼ぶようです。プライマリコンストラクタではないコンストラクタでは,プライマリコンストラクタと違う部分のみを記述すれば良くなります。これによりコードの重複が消えました。

少しコンストラクタの引数に処理を加えて,フィールドを変えてみます。 下記はコンパイルエラーになります。

    // コンパイルエラー
    public PrimaryConstructorSample(String title, String message){
        String decoratedMessage = String.format("「%s」", message);
        this(title, decoratedMessage);
    }

コンストラクタ内でthis(またsuperの呼び出しも),コンストラクタの先頭でしか呼べないからです。

さて,先頭でしかthisを呼び出せないのでメソッド化してみます。 しかし,これもコンパイルエラーになってしまいます。

    // コンパイルエラー
    private String getDecoratedMessage(String message){
        return String.format("「%s」", message);
    }

    public PrimaryConstructorSample(String title, String message){
        this(title, getDecoratedMessage(message), "Sample description.");
    }

thisの呼び出し(superの呼び出し)の際には,インスタンスメソッドは呼び出せません。 インスタンスメソッドの呼び出しは,this.getDecoratedMessage(message)と等価ですが,コンストラクタ内のthisの呼び出しが終わるまで,thisのメソッドの呼び出し・フィールドの参照はできません。

    private static String getDecoratedMessage(String message){
        return String.format("「%s」", message);
    }

これでコンパイルエラーはとれます。

ジェネリクス(総称型)について その3

前回,型パラメータが一つのジェネリクスを使ったクラスを作ってみました。 今回は型パラメータが二つのクラスを試してみます。

package com.mrstar.practice_generics;

public class SampleGeneric<T1, T2> {
    private T1 mValue1;
    private T2 mValue2;

    public SampleGeneric(T1 value1, T2 value2) {
        mValue1 = value1;
        mValue2 = value2;
    }

    public T1 getValue1() {
        return mValue1;
    }

    public T2 getValue2() {
        return mValue2;
    }

    @Override
    public String toString() {
        return String.format("%s %s", mValue1.toString(), mValue2.toString());
    }
}

使う側はこんな感じで。

SampleGeneric<Integer, Integer> sample0 = new SampleGeneric<Integer, Integer>(2, 1);
System.out.println(sample0);  // 2 1

SampleGeneric<String, Integer> sample1 = new SampleGeneric<String, Integer>("Hello", 0);
System.out.println(sample1);  // Hello 0

SampleGeneric<Integer, String> sample2 = new SampleGeneric<Integer, String>(1, "World");
System.out.println(sample2);  // 1 World

もう一つ。

インターフェースでも型パラメータを使ってみます。

package com.mrstar.practice_generics;

public interface MyFunction<S, R> {
    R func(S source);
}

このインタフェースは,funcというメソッドのみを持ちます。

このインタフェースを実装したクラスを定義してみます。

package com.mrstar.practice_generics;

public class StringLengthFunction implements MyFunction<String, Integer> {
    @Override
    public Integer func(String source) {
        return source.length();
    }
}

使ってみます。

System.out.println(new StringLengthFunction().func("hello"));  // 5
System.out.println(new StringLengthFunction().func("Hello world!"));  // 12

もう一つ。

package com.mrstar.practice_generics;

public class DegreeToRadianFunction implements MyFunction<Integer, Double> {
    @Override
    public Double func(Integer degree) {
        return Math.PI * degree / 180.0;
    }
}

これも使ってみます。

System.out.println(new DegreeToRadianFunction().func(0));  // 0.0
System.out.println(new DegreeToRadianFunction().func(90));  // 1.5707963267948966
System.out.println(new DegreeToRadianFunction().func(180));  // 3.141592653589793
System.out.println(new DegreeToRadianFunction().func(360));  // 6.283185307179586

ジェネリクス(総称型)について その2

Javaプログラムにおいて,型パラメータを持つクラスを定義してみました

package com.mrstar.practice_generics;

public class GenericSample<T> {
    private T mValue;

    public GenericSample(T value) {
        mValue = value;
    }

    public T getValue() {
        return mValue;
    }

    public void setValue(T value) {
        mValue = value;
    }
}

使ってみました。

GenericSample<Sample> sample0 = new GenericSample<Sample>(new Sample());
Sample value = sample0.getValue();
sample0.setValue(new Sample());
// sample0.setValue("hello"); コンパイルエラー

GenericSample<String> sample1 = new GenericSample<String>("Hello Generics.");
String value1 = sample1.getValue();
sample1.setValue("Hello World.");
// sample1.setValue(1); コンパイルエラー

使い方はこんな感じです。 次に,static メソッドの型パラメータを持つメソッドを定義して使ってみました。

package com.mrstar.practice_generics;

public class GenericStaticSample {
    public static <T> T staticFunction(T value) {
        return value;
    }
}

呼び出してみます。

Sample sample0 = GenericStaticSample.staticFunction(new Sample());
Sample sample1 = GenericStaticSample.<Sample>staticFunction(new Sample());

下の呼び方は知りませんでした。 型パラメータを指定して呼び出すこともできるんですね。

ジェネリクス(総称型)について その1

型とか継承とかジェネリクスの話。 特にジェネリクスは理解していると思っていたのですが,理解が甘かったです。

package com.mrstar.practice_generics;

public class Super {
}

このSuperというクラスと,

package com.mrstar.practice_generics;

public class Sub extends Super {
}

Superを継承したSubというクラスがあるとします。

Super object0 = new Super();
Sub object1 = new Sub();

任意の型の変数に,その型のインスタンスを代入することは当然できます。

Super object2 = new Sub();
// Sub object3 = new Super(); コンパイルエラー

スーパークラスの変数に,サブクラスのインスタンスを代入することは可能だけれども,サブクラスの変数にスーパークラスインスタンスは代入することができません。

ArrayListを使ってみます。

ArrayList<Super> list0 = new ArrayList<Super>();
list0.add(new Super());
list0.add(new Sub());

ArrayList <Super>の変数にArrayList<Super>型のインスタンスを代入します。 それに対して,Super型のインスタンスや,Sub型のインスタンスをaddします。

ArrayList<Sub> list1 = new ArrayList<Sub>();
//list1.add(new Super()); コンパイルエラー
list1.add(new Sub());

ArrayList<Sub>の変数にArrayList<Sub>型のインスタンスを代入します。 それに対してSub型のインスタンスはaddできますが,当然Super型のインスタンスはaddできません。

さて,ここからです。

ArrayList<Super> list = new ArrayList<Sub>(): // これはコンパイルが通る?コンパイルエラー?

がっつり迷ってしまいました。
ていうか分かりませんでした。

コンパイルエラーでした。

仮にこれがコンパイルエラーになら無い場合を考えてみます。
その場合はArrayLlist<Sub>型のインスタンスArrayList<Super>型の変数listに入っています。

ArrayList<Super>型の変数listは,Super型のインスタンスやSub型以外のSuper型を継承したインスタンスもaddできなくてはいけません。 ですが,ArrayList<Sub>のインスタンスは,Super型のインスタンスもSub型以外のSuper型を継承したインスタンスもaddはできません。 矛盾が生じますね。

そもそもスーパークラスの変数にサブクラスのインスタンスは代入出来ますが,型パラメータですからね,今回の問題は。

分かったつもりになっていました。
ジェネリクスについてはもっと勉強しないといけません。

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

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

 前々回はArrayAdapterを,前回はSimpleAdapterを使ってみました。今回は前回の続きとして,SimpleAdapterのソースコードとクラスリファレンスを詳しく見て行きたいと思います。


バインドできるビューとデータ型

 BaseAdapterを継承してAdapterを作る場合,getViewメソッドを実装し,getItemメソッドを使ってデータを取得して,そのデータをセルの子Viewにバインドする必要があります。また,Viewを使い回したりViewHolderを定義し,セルのViewのタグにセットしたりなど毎回毎回同じような処理を記述する必要があります。


 一方で,SimpleAdapterは(List< Map< String,?>>型の)データとセルのレイアウト中の子Viewを,(どういう対応でバインドするかを引数で指定すれば)勝手にバインドしてくれます。勝手にバインドしてくれますが,全てのViewに対してバインドできるわけではありません。つまりSimpleAdapterのコンストラクタの第5引数,int型の配列の要素に入るViewのidは,全てのViewのサブクラスを指定出来るわけではありません。

  • CheckBoxなどCheckableインターフェースを実装したView
  • TextViewとそのサブクラス
  • ImageViewとそのサブクラス

 また,それぞれのViewに対応出来るデータの型にも決まりがあります。

SimpleAdapterのbindViewの一部をみてみる

 今回,クラスリファレンスに加えて,Android4.3のSimpleAdapterのコードのbindViewメソッドを見てみました。


 下記はbindViewメソッドの中の一つのViewと(コード中v),それに対応する一つのデータ(コード中data)を実際にバインドする箇所を抜き出した物です。また,ここでtextというのは,dataのtoString()メソッドの結果を格納している変数です。(nullな場合は,空文字に置き換わっています。)

if (v instanceof Checkable) {
	if (data instanceof Boolean) {
		((Checkable) v).setChecked((Boolean) data);
	} else if (v instanceof TextView) {
		// Note: keep the instanceof TextView check at the bottom of these
		// ifs since a lot of views are TextViews (e.g. CheckBoxes).
		setViewText((TextView) v, text);
	} else {
		throw new IllegalStateException(v.getClass().getName() +
			" should be bound to a Boolean, not a " +
			(data == null ? "<unknown type>" : data.getClass()));
	}
} else if (v instanceof TextView) {
	// Note: keep the instanceof TextView check at the bottom of these
	// ifs since a lot of views are TextViews (e.g. CheckBoxes).
	setViewText((TextView) v, text);
} else if (v instanceof ImageView) {
	if (data instanceof Integer) {
		setViewImage((ImageView) v, (Integer) data);                            
	} else {
		setViewImage((ImageView) v, text);
	}
} else {
	throw new IllegalStateException(v.getClass().getName() + " is not a " +
		" view that can be bounds by this SimpleAdapter");
}
CheckBoxなどCheckableインターフェースを実装したViewのバインドをみてみる

まずCheckableインターフェースを実装したViewかどうかをチェックします。

if (v instanceof Checkable) {

そして条件が真であれば,下記のようにバインドします。

if (data instanceof Boolean) {
	((Checkable) v).setChecked((Boolean) data);
} else if (v instanceof TextView) {
	// Note: keep the instanceof TextView check at the bottom of these
	// ifs since a lot of views are TextViews (e.g. CheckBoxes).
	setViewText((TextView) v, text);
} else {
	throw new IllegalStateException(v.getClass().getName() +
		" should be bound to a Boolean, not a " +
		(data == null ? "<unknown type>" : data.getClass()));
}


Boolean型オブジェクトのデータがCheckableインターフェースを実装するViewにバインドされる場合Boolean型のオブジェクトを引数にCheckable#setChecked(boolean)メソッドが実行されます。


また,バインドされるデータがBoolean型ではなくかつ,ViewがTextViewのインスタンスであれば,データのtoStringメソッドの結果を,TextView#setTextメソッドの引数としてバインドします。


それ以外の場合は,IllegalStateExceptionが投げられます。


TextViewとそのサブクラスのバインドをみてみる
} else if (v instanceof TextView) {
	// Note: keep the instanceof TextView check at the bottom of these
	// ifs since a lot of views are TextViews (e.g. CheckBoxes).
	setViewText((TextView) v, text);

 Checkableを実装しているかどうかをチェックして結果が偽であれば,次はTextViewとそのサブクラスかどうかを確認します。もし条件が真ならsetViewTextというメソッドを用いて,バインドします。内部ではTextView#setText(CharSequence)メソッドを実行しています。


ImageViewとそのサブクラスのバインドをみてみる
} else if (v instanceof ImageView) {
	if (data instanceof Integer) {
		setViewImage((ImageView) v, (Integer) data);                            
	} else {
		setViewImage((ImageView) v, text);
	}

 Checkableを実装したViewとTextViewの次は,ViewがImageViewのサブクラスであるか調べます。この場合は,データがInteger型かどうかで処理が変わります。上記のコード中で,Integer型かどうか調べて分岐先両方でsetViewImageを実行していますが,このメソッドは,第2引数がInterger型のものとString型のものがあります。

 データがInteger型の場合,setViewImageメソッド内でImageView.setImageResource(int)が行われます。引数はデータです。

 データがIntegerクラスでない場合,以下のような処理が行われます。

public void setViewImage(ImageView v, String value) {
	try {
		v.setImageResource(Integer.parseInt(value));
	} catch (NumberFormatException nfe) {
		v.setImageURI(Uri.parse(value));
	}
}
まとめ

よく使うTextViewとImageViewがバインドは簡単にできることが分かりました。
これに加えてViewBinderを使えば複雑な処理をすることも可能です。

次回はViewBinderを使ってみたいと思います。

ListAdapterについて調べてみた(2) SimpleAdapter編その1

前回ArrayAdapterを使ってみました。
ArrayAdapterは,一つのTextViewだけセルごとに値を設定する場合,getViewメソッドをオーバーライドしたりせず非常に少ないコード量で書けます。
しかし,TextViewではなくImageViewの要素をセルごとに設定したり,セルごとに複数のTextViewに値を設定したり,画像と文字列両方セルごとに設定したりということは,getViewメソッドのオーバーライドメソッドなしには実現できません。


このようなことはSimpleAdapterを使えば,比較的容易にそしてgetViewメソッドのオーバーライドなしに少ないコード量で書けます。


具体例と一緒に使い方を見ていきます。


やりたいことと設定

こんなクラスがあるとします。

public class ColorItem {
	private final String nameEnglish;
	private final String nameJapanese;
	private final int imageId;

	public ColorItem(String nameEnglish, String nameJapanese, int imageId) {
		this.nameEnglish = nameEnglish;
		this.nameJapanese = nameJapanese;
		this.imageId = imageId;
	}

	public String getNameEnglish() {
		return nameEnglish;
	}

	public String getNameJapanese() {
		return nameJapanese;
	}

	public int getImageId() {
		return imageId;
	}
}

こんなレイアウトXMLファイルがあるとします。
res/layout/list_item_for_simple_adapter.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="70dp" >

    <TextView
        android:id="@+id/name_english_text_view"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_alignParentTop="true"
        android:layout_marginLeft="20dp"
        android:layout_marginTop="15dp"
        android:text="英語名" >
    </TextView>

    <TextView
        android:id="@+id/name_japanese_text_view"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_alignParentLeft="true"
        android:layout_marginBottom="15dp"
        android:layout_marginLeft="20dp"
        android:text="日本語名" >
    </TextView>

    <ImageView
        android:id="@+id/color_image_view"
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:layout_centerHorizontal="true"
        android:layout_centerVertical="true"
        android:layout_margin="10dp" />

</RelativeLayout>

ColorItemのListのインスタンスがあり,そのインスタンスの情報をListViewに表示したいです。
list_item_for_simple_adapterをセルのレイアウトとし,ColorItemクラスのインスタンスの情報をセットするためのSimpleAdapterインスタンスを作成します。


ColorItemのnameEnglishの文字列の値を上記レイアウトXMLファイル中のidがname_english_text_viewのTextView中にセットし,nameJapaneseの文字列をidがname_japanese_text_viewのTextView中にセットし,imageIdの値をidとする画像リソースをcolor_image_viewというidのImageViewにセットします。



サンプルコード
public class SimpleAdapterSampleActivity extends ListActivity {
    private static final String KEY_NAME_ENGLISH = "name_english";
    private static final String KEY_NAME_JAPANESE = "name_japanese";
    private static final String KEY_IMAGE_ID = "image_id";

    private static final String[] FROM = new String[] {
            KEY_NAME_ENGLISH, KEY_NAME_JAPANESE, KEY_IMAGE_ID
    };
    private static final int[] TO = new int[] {
            R.id.name_english_text_view, R.id.name_japanese_text_view, R.id.color_image_view
    };

    private static final List<ColorItem> COLOR_ITEM;

    static {
        COLOR_ITEM = new ArrayList<ColorItem>();
        COLOR_ITEM.add(new ColorItem("Blue", "青", R.drawable.blue));
        COLOR_ITEM.add(new ColorItem("Cyan", "シアン", R.drawable.cyan));
        COLOR_ITEM.add(new ColorItem("Green", "緑", R.drawable.green));
        COLOR_ITEM.add(new ColorItem("Magenta", "マゼンタ", R.drawable.magenta));
        COLOR_ITEM.add(new ColorItem("Red", "赤", R.drawable.red));
        COLOR_ITEM.add(new ColorItem("Yellow", "黄色", R.drawable.yellow));
    }

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

        List<ColorItem> colorItemList = new ArrayList<ColorItem>(COLOR_ITEM);

        List<Map<String, ?>> data = colorItemListToMapList(colorItemList);
        int layoutResourceId = R.layout.list_item_for_simple_adapter;

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

        setListAdapter(adapter);
    }

    private List<Map<String, ?>> colorItemListToMapList(List<ColorItem> colorItemList) {
        List<Map<String, ?>> data = new ArrayList<Map<String, ?>>();
        for (ColorItem colorItem : colorItemList) {
            data.add(colorItemToMap(colorItem));
        }
        return data;
    }

    private Map<String, ?> colorItemToMap(ColorItem colorItem) {
        Map<String, Object> map = new HashMap<String, Object>();
        map.put(KEY_NAME_ENGLISH, colorItem.getNameEnglish());
        map.put(KEY_NAME_JAPANESE, colorItem.getNameJapanese());
        map.put(KEY_IMAGE_ID, Integer.valueOf(colorItem.getImageId()));

        return map;
    }
}

次の節から詳しく中身を見ていきます

SimpleAdapterのコンストラクタ

SimpleAdapterコンストラクタは一つで

public SimpleAdapter (Context context, List<? extends Map<String, ?>> data, int resource, String[] from, int[] to)

のみとなっています。

それぞれの引数の意味を簡単に説明すると,
第2引数は,キーがString型のMapのList(詳しくは後述)
第3引数は,セルのレイアウトXMLファイルのid
第4引数は,第2引数のMapのキーの配列
第5引数は,第3引数のレイアウトXMLファイル内のビューのidの配列

です。

まずColorItemの各フィールドの値とビューのidを紐付ける仲介を行うマップのキーを定義します。

static final String KEY_NAME_ENGLISH = "name_english"; // 英語名のデータとビューのためのキー
static final String KEY_NAME_JAPANESE = "name_japanese"; // 日本語名のデータとビューのためのキー
static final String KEY_IMAGE_ID = "image_id"; // 画像のデータとビューのためのキー

このキーを使って,SimpleAdapterの第4引数を作ります。

static final String[] FROM = new String[] {
    KEY_NAME_ENGLISH,
    KEY_NAME_JAPANESE,
    KEY_IMAGE_ID
};

次はint[]型の第5引数です。
第4引数の配列は,英語名,日本語名,画像idの順番でキーが並んでいます。
その順番で,それぞれのキーと対応するビューのidを要素とするint型の配列を作ります。それそれのビューのidは,第3引数のレイアウト中のビューのidでないといけません。

static final int[] TO = new int[] { 
    R.id.name_english_text_view, 
    R.id.name_japanese_text_view, 
    R.id.color_image_view 
};

最後に第2引数です。これはキーがString型のMapのListになっています。
SimpleAdapterを使うためには表示したいデータをこの型に変換する必要があります。


コンストラクタの第2引数 キーがString型のMapのリスト

コンストラクタの第2引数はキーがString型のMapのListです。
まずColorItemのインスタンスを,キーがString型のMapに変換するメソッドを作ります。

private Map<String, ?> colorItemToMap(ColorItem colorItem) {
	Map<String, Object> map = new HashMap<String, Object>();
	map.put(KEY_NAME_ENGLISH, colorItem.getNameEnglish());
	map.put(KEY_NAME_JAPANESE, colorItem.getNameJapanese());
	map.put(KEY_IMAGE_ID, Integer.valueOf(colorItem.getImageId()));

	return map;
}

キーは第4引数にも用いた文字列で,バリューはそのキーと対応するColorItem型のフィールドです。
このメソッドを使って,ColorItemのListを変換するメソッドを定義します。

private List<Map<String, ?>> colorItemListToMapList(List<ColorItem> colorItemList) {
	List<Map<String, ?>> data = new ArrayList<Map<String, ?>>();
	for (ColorItem colorItem : colorItemList) {
		data.add(colorItemToMap(colorItem));
	}
	return data;
}
SimpleAdapterのインスタンスの作成

ActivityのonCreate内で,

List<Map<String, ?>> data = colorItemListToMapList(colorItemList);
int layoutResourceId = R.layout.list_item_for_simple_adapter;

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

とすれば,前述のやりたいことを実現できるSimpleAdapterが作成できます。


まとめ

SimpleAdapterを使って,セルごとに複数のTextViewに文字列とImageViewに画像を設定することができました。