:Tips  ListView を使おう 〜 基礎編 〜 その2

ArrayAdapter

 java.lang.Object
  ↳ android.widget.BaseAdapter
   ↳ android.widget.ArrayAdapter<T>

ArrayAdapter クラスは、 ListView に表示されるアイテム( レイアウトリソースの情報から生成された View )内の "指定された1つの TextView にテキストをセットする" 為の Adapter です(アイテム内の複数の View に対しテキストをセットするといった事は出来ません)



ArrayAdapter を用いた ListView 作成のポイント

ステップごとに作成のポイントをみていきます。

< STEP 1 > データを作成する



データは 配列 と List どちらで用意するべきか

一見、配列の方が参照の速度が速そうなので配列で渡したいところですが、 ArrayAdapter に渡された配列はコンストラクタ内で List に変更されてしまいます。よって、どちらでも変わりはありません。



データ要素の文字列だけでもデザインは変えられる

ここでいう 「テキスト」 「文字列」 とは、 CharSequence インターフェイスを実装したクラスを指しています。実装クラスには代表的な String や StringBuilder はもちろんのこと Spannable インターフェイスを実装している SpannableString や SpannableStringBuilder も含まれます。一見 「 ArrayAdapter はテキストと、1つの TextView だけしか扱えない 」 と聞くと、なにやら表現の乏しい印象を受けますが、当ブログ 「 TextView を使いこなそう 〜 表示編 〜 その 」 でも見てきたようにかなりの事が可能です。例えば、一部のフォントを大きくしたり小さくしたり、または、ある行だけ右寄せにしたり中央寄せにしてみたり、やろうと思えばアイテム毎に異なる画像を表示するなんてことも可能です。

サンプルとして Amazon 風味の ListView を作ってみました。 ArrayAdapter には一切手を加えず、データだけでデザインしています。データ要素である文字列の成型には Html#fromHtml() を用いました。






横向きにして下までスクロールしてみます。




ちょっと分かり辛いかもしれませんが、評価の星マークの画像がアイテム毎に異なってます。




文字列以外のデータ要素の扱い

ArrayAdapter の特徴でも触れたように、この Adapter は TextView に対してテキスト(文字列)をセットすることしか出来ません。しかし、ArrayAdapter で扱えるデータの型は Object の配列か List インターフェイス実装クラスとなっており、共に保持する要素がオブジェクトであれば構わないということになっています。それでは要素が文字列以外の場合、一体どのように扱われるのでしょうか。

T item = getItem(position);
if (item instanceof CharSequence) {
    text.setText((CharSequence)item);
} else {
    text.setText(item.toString());
}

このコードは、実際の ArrayAdapter ソースコードからの抜粋です。 TextView ( = text ) に対してテキストをセットする場面ですが、 ArrayAdapter#getItem() で返ってきたデータの要素( = item )が CharSequence 型かを instanceof 演算子を用いて判定しています。 true ならば、そのまま TextView にセットし、 false ならば toString() からの戻り値を TextView にセットしています。ここから導かれる先程の答えは 「 toString() の戻り値が TextView に表示される 」 ということになります。もし、 CharSequence インターフェイス実装クラス以外のクラスを配列やコレクションの要素として用いたいのならば、 toString() メソッドをオーバーライドし、表示したい文字列を戻り値として返すようコーディングしなくてはなりません。



< STEP 2 > レイアウト情報を作成する



何故、インスタンスではなくレイアウト情報が必要なのか

ListView や Spinner 等の AdapterView 継承クラスでは、メモリの使用を極力抑えるために 「 非表示となったアイテムの View を回収し、新たに表示されるアイテムの View として再利用する 」 といった View の管理を行います、そのため、他の ViewGroup 継承クラスのような View のインスタンスを受け取って表示するといった事は行いません。その代わり、必要最小限の View のインスタンスを Adapter を通して生成させます。その為に Adapter には View の " 設計図 " が必要になるわけです。その " 設計図 " となるのが XML で記述された レイアウト情報 のファイルです。



レイアウト情報のタイプは2つ

後々、 ArrayAdapter のコンストラクタの説明箇所でも触れますが、アイテム内の View が TextView 1つだけのタイプと、複数の View が在るタイプ、との2通りに分けられます。



・ TextView が1つだけのタイプ・・・

/res/layout フォルダに任意のファイル名(今回は layout1.xml とします)で以下のような XML ファイル作ります。

layout1.xml

<?xml version="1.0" encoding="utf-8"?>
<TextView 
  xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="fill_parent"
  android:layout_height="wrap_content"
  />

<TextView> タグ1つだけがレイアウトされています。このレイアウトの場合だけ以下のコンストラクタが使えます。

public ArrayAdapter(Context context, int textViewResourceId)
public ArrayAdapter(Context context, int textViewResourceId, T[] objects)
public ArrayAdapter(Context context, int textViewResourceId, List<T> objects)

第2引数の textViewResourceId には、レイアウト情報のID( R.layout.layout1 )を渡します。



・複数の View が在るタイプ・・・

/res/layout フォルダに任意のファイル名(今回は layout2.xml とします)で以下のような複数の View が在るレイアウトの XML ファイル作ります。

layout2.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
  xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="fill_parent"
  android:layout_height="fill_parent">
  <ImageView
    android:src="@drawable/icon"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    />
  <TextView
    android:id="@+id/targetTextView"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    />
</LinearLayout>

重要なのは、データを表示させる TextView に参照用のID( R.id.* )を設定しておくことです(今回はID名を targetTextView とします)。こうすることで 「 どの View に対してデータを表示させるのか 」 を指定することができます。

public ArrayAdapter(Context context, int resource, int textViewResourceId)
public ArrayAdapter(Context context, int resource, int textViewResourceId, T[] objects)
public ArrayAdapter(Context context, int resource, int textViewResourceId, List<T> objects)

上記のコンストラクタの引数 resource にはレイアウト情報のID( R.layout.layout2 )を、 textViewResourceId には参照用ID ( R.id.targetTextView ) を渡します。



< STEP 3 > Adapter を作成する



コンストラクタの選択

コンストラクについては 前の回 で説明しましたが、どのコンストラクタを選ぶかは以下の2つの条件で決まります。

・アイテム内の View は TextView 1つだけである ( Yes / No )

・データの参照を事前に渡すか? 渡さない・・・(A)、 Object の配列で渡す・・・(B)、 List 型で渡す・・・(C)

public ArrayAdapter(Context context, int textViewResourceId)                                // Yes & A
public ArrayAdapter(Context context, int textViewResourceId, T[] objects)                   // Yes & B
public ArrayAdapter(Context context, int textViewResourceId, List<T> objects)               // Yes & C
public ArrayAdapter(Context context, int resource, int textViewResourceId)                  // No  & A
public ArrayAdapter(Context context, int resource, int textViewResourceId, T[] objects)     // No  & B
public ArrayAdapter(Context context, int resource, int textViewResourceId, List<T> objects) // No  & C

データの参照を事前に「渡さない」で ArrayAdapter が生成された場合、自動的に ArrayAdapter 内に List<CharSequence> 型のインスタンスが生成されます。この List にデータ要素を追加したい場合は ArrayAdapter#add() 、また List にデータ要素を挿入したい場合には ArrayAdapter#insert() を使い、引数として CharSequence 型の参照を渡します。



ジェネリック

ArrayAdapter はジェネリックとして定義されています。インスタンスの生成の時に型を定義しなくてはなりません。では、どの型で定義すれば良いのでしょうか。答えは、引数として渡される 「 データ要素の型 」 です。具体的には、配列ならば配列の要素の型、 List ならば List の要素の型となります。



"静的"な ArrayAdapter の生成

static メソッドで ArrayAdapter のインスタンスを生成する方法も用意されています。ただし、データとしてセット出来るのは XML リソースファイルで 「 配列 」 として定義されたものだけです XML ファイルの 「 配列 」 の設定は こちら を参照して下さい)

public static ArrayAdapter<CharSequence> createFromResource (Context context, int textArrayResId, int textViewResId)

第1引数にはコンテキスト( 通常は Activity )インスタンス。第2引数には「 配列 」として定義された XML リソースのID( R.array.* )。第3引数には TextView 1つだけがレイアウトされている XML リソースのレイアウト情報のID ( R.layout.* ) を渡します。



< STEP 4 > ListView を作成する



ListView のインスタンスを得る方法


・コンストラクタを用いて生成する・・・

ListView listView = new ListView(this);



XML リソースファイルのレイアウト情報から生成する・・・

/res/layout/main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="fill_parent"
  android:layout_height="fill_parent"
  >
  <ListView
    android:id="@+id/listView"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    />
</LinearLayout>

という風にレイアウト情報を定義した場合。 Activity 内であれば・・・

public class ListViewTest extends Activity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.main);
        ListView textView = (ListView)findViewById(R.id.listView);

とするか、または・・・

public class ListViewTest extends Activity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        
        View view = getLayoutInflater().inflate(R.layout.main, null);
        ListView listView = (ListView)view.findViewById(R.id.listView);

とすることで、インスタンスを得ることが出来ます。



ListView に Adapter を組み込む

ListView#setAdapter() メソッドを使います。

public void setAdapter (ListAdapter adapter) 

引数に Adapter のインスタンスを渡すだけです。



実際に ArrayAdapter で ListView を作ってみる

ただテキストを表示するのでは面白味がありませんので、ちょっと一捻りして 「 カラーパレット 」 ・・・のような物を作ってみようと思います。

< STEP 1 > データを作成する

最初に表示するカラーの文字列を配列で作ります。

String[] color = { "Red", "Maroon", "Purple","Olive","Fuchsia","Yellow","Lime","Green","Blue","White","Silver" };

次に、先程作った配列と同じサイズの CharSequence の配列 data を用意します。

CharSequence[] data = new CharSequence[color.length];

配列の数だけループを回しながら Html#fromHtml() メソッドの引数に HTML タグつき文字を渡してフォーマットをし、戻り値のフォーマット後の文字列を配列 data に格納します。

for (int i=0; i<color.length; i++){
    data[i] = Html.fromHtml("<font color=\"" + color[i] + "\"> ■ </font><b>" + color[i] + "</b>");
}

こうすることで文字 「 ■ 」 の部分だけフォントのカラーが color[i] の色に変化するという仕組みです。

これで表示するデータ data が出来ました。



< STEP 2 > レイアウト情報を作成する

今回は一番単純な TextView 1つだけのレイアウトにします。

layout1.xml

<?xml version="1.0" encoding="utf-8"?>
<TextView 
  xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="fill_parent"
  android:layout_height="wrap_content"
  android:textSize="30sp"
  />

layout1.xml は説明で用いたものと同じですが、ただ1つだけ属性を増やしました。 textSize 属性です。ここでテキストのサイズを 30sp に指定しています。

これで、レイアウト情報のID ( R.layout,layout1 ) も準備ができました。



< STEP 3 > Adapter を作成する

今回は、データが 「 CharSequence の配列 」 でレイアウト情報が 「 TextView 1つだけのレイアウト 」 ですので・・・

public ArrayAdapter(Context context, int textViewResourceId, T[] objects)                   // Yes & B

このコンストラクタを用います。そしてジェネリックの型は CharSequence です。

ArrayAdapter<CharSequence> adapter = new ArrayAdapter<CharSequence>(this, R.layout.layout1, data);

これで、 Adapter のインスタンス adapter もできました。



< STEP 4 > ListView を作成する

ListView はコード内で作ることにします。

ListView listView =  new ListView(this);

ListView に Adapter をセットします。

listView.setAdapter(adapter);

これで ListView は完成です。最後に Activity に組み込んでお仕舞いです。

setContentView(listView);



サンプル全体のソースコード

layout1.xml

<?xml version="1.0" encoding="utf-8"?>
<TextView 
  xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="fill_parent"
  android:layout_height="wrap_content"
  android:textSize="30sp"
  />

ListViewTest.java

public class ListViewTest extends Activity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        
        String[] color = { "Red", "Maroon", "Purple","Olive","Fuchsia","Yellow","Lime","Green","Blue","White","Silver" };
        CharSequence[] data = new CharSequence[color.length];
        for (int i=0; i<color.length; i++){
        		data[i] = Html.fromHtml("<font color=\"" + color[i] + "\"> ■ </font><b>" + color[i] + "</b>");
        }
        ArrayAdapter<CharSequence> adapter = new ArrayAdapter<CharSequence>(this, R.layout.layout1, data);
        ListView listView =  new ListView(this);
        listView.setAdapter(adapter);
        setContentView(listView);
    }
}

実行してみます。




以上です。





次回は・・・

SimpleAdapter を使った ListView の作成をやります。