前回の続き。今回は独自のモデルクラスの情報を一覧表示するサンプルです。今までより実践的な内容になります。

サンプルコード: https://github.com/stack3/AndroidListViewSamples

サンプルコードを起動して、CustomListItemを選択してください。このような画面が表示されます。

01

各行にTitle(大サイズの文字)とDescription(中サイズの文字)が表示されています。

custom_list_item.xml

1行ごとのレイアウトXMLです。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  android:orientation="vertical"
  android:padding="@dimen/normal_padding" >
  <TextView
    android:id="@+id/titleTextView"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:textSize="@dimen/large_text_size" />
  <TextView
    android:id="@+id/descriptionTextView"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:textSize="@dimen/medium_text_size" />
</LinearLayout>

titleTextViewとdescriptioniTextViewというIDで2つのTextViewを並べています。textSizeでそれぞれ異なるサイズを指定しています。

CustomListItem

行に表示する元データとなるモデルクラスです。TitleとDescriptionを保持します。

public class CustomListItem {
  private String title;
  private String description;

  public String getTitle() {
    return title;
  }

  public void setTitle(String title) {
    this.title = title;
  }

  public String getDescription() {
    return description;
  }

  public void setDescription(String description) {
    this.description = description;
  }
}

CustomListItemAdapter

今回は独自のモデルクラスを使うので、Adapterも独自にカスタマイズする必要があります。CustomListItemAdapterはBaseAdapterを継承した独自のAdapterクラスです。

public class CustomListItemAdapter extends BaseAdapter {
    private Context context;
    private List items;

    public CustomListItemAdapter(Context context, List items) {
        this.context = context;
        this.items = items;
    }

Contextが必要になるのでコンストラクタで受け取ります。もちろん一覧表示するCustomListItemのListも必要なので、それも受け取ります。それらはメンバ変数に保存します。

次にBaseAdapterのメソッドをオーバーライドします。

@Override
public int getCount() {
  return items.size();
}

@Override
public Object getItem(int position) {
  return items.get(position);
}

@Override
public long getItemId(int position) {
  return position;
}

getCountは一覧表示する行がいくつあるかを返すようにします。itemsのsizeをそのまま返しています。
getItemは指定position(行のindex)のオブジェクトを返します。itemsのgetメソッドを経由してオブジェクトを返しています。
getItemIdはpositionごとにユニークなIDを返します。ここは基本的にpositionをそのまま返せば良いです。

ここからが本番、getViewメソッドのオーバーライドです。このメソッドは指定positionに対応するViewを返すようにします。

@Override
public View getView(int position, View convertView, ViewGroup parent) {
    View customItemView = null; 
    if (convertView == null) {
         LayoutInflater inflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
         customItemView = inflater.inflate(R.layout.custom_list_item, parent, false);
         Log.d(this.getClass().getName(), "created");
    } else {
         customItemView = convertView;
         Log.d(this.getClass().getName(), "recycled");
    }

    TextView titleTextView = (TextView)customItemView.findViewById(R.id.titleTextView);
    TextView descriptionTextView = (TextView)customItemView.findViewById(R.id.descriptionTextView);

    CustomListItem item = items.get(position);
    titleTextView.setText(item.getTitle());
    descriptionTextView.setText(item.getDescription());

    return customItemView;
}

比較的長いコードなので順に説明します。

以下の部分はcustom_list_item.xmlに従ってレイアウトされたViewを生成しています。

LayoutInflater inflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
customItemView = inflater.inflate(R.layout.custom_list_item, parent, false);

ここで得られたcustomItemViewはcustom_list_item.xmlで定義したとおりTitleとDescriptionの2つのTextViewが配置されています。よってそれらをfindViewByIdで取得できます。

TextView titleTextView = (TextView)customItemView.findViewById(R.id.titleTextView);
TextView descriptionTextView = (TextView)customItemView.findViewById(R.id.descriptionTextView);

次にこれらのTextViewにCustomListItemのTitleとDescriptionを設定します。

CustomListItem item = items.get(position);
titleTextView.setText(item.getTitle());
descriptionTextView.setText(item.getDescription());

わかりにくいのは、ここの条件分岐だと思います。

if (convertView == null) {
     LayoutInflater inflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
     customItemView = inflater.inflate(R.layout.custom_list_item, parent, false);
     Log.d(this.getClass().getName(), "created");
} else {
     customItemView = convertView;
     Log.d(this.getClass().getName(), "recycled");
}

ListViewは場合によってはたくさんの行を扱う必要があります。Viewオブジェクトは通常のオブジェクトに比べてサイズが大きいです。1000行あって1000個Viewを作っていたらメモリを圧迫します。たとえば1000行あるが画面上では10行しか表示しないなら10行+α分のViewだけ生成して、スクロールアウトしたものは次にスクロールインする行に使いまわすようにするとメモリを圧迫せずに済みます。

02-1

ListViewはこの仕組みを持っています。画面に表示される分+αだけ行表示用のViewを生成し、できるだけViewを使いまわせるようになっています。ただしプログラマが意識してコーディングしないと使い回しは行われません。

getViewの引数convertViewは、使いまわせるViewがあるときはそれが代入されています。逆にない場合はnullです。よってconvertViewがnullのときは、行のViewを生成し、あればconvertViewを使うようにします。

本サンプルのgetViewではViewを生成した時は”created”、使いまわした時は”recycled”とLogに出力するようにしています。サンプルを起動してスクロールさせると、createdがいくつか表示された後は、recycledのみが出力され続けるはずです。

前回の記事までで用いたArrayAdapterは、使い回すように実装されているので特に意識する必要はないですが、自分でAdapterを作る時は必ずこの点を意識する必要があります。そうしないとOut of Memoryエラーでアプリがクラッシュする危険があります。

その5へ続く