ListView の行のカスタマイズ
以前の記事 「ListView への POJO リストの表示」 では Book というクラスを定義して、 そのオブジェクトのコレクションを ListView に関連付けました。
このときは、アダプターは ArrayAdapter、レイアウトは android.R.layout.simple_list_item_1 でした。
実装方法が非常に簡単、という意味ではそれはそれでいいのですが、それぞれのクラスの内容に合わせて、それのビジュアル表現 (すなわちビューのレイアウト) も変えたほうが良い場合も少なくありません。
ここでは ListView を用いつつ、カスタムのレイアウトを定義する方法を示します。
ListView における行のカスタムレイアウト
フレームワークでは simple_list_item_1 などのレイアウトが用意されていますが、これは TextView をひとつ持つだけの非常に単純なレイアウトです。
より複雑なレイアウトを実現するにはどうしたらよいでしょうか。
例えば、次のスクリーンショットのように ListView のエントリ一個に対して、複数のテキストを表示できる(さらにここでは色も変えている) ようにしたいとします。
この場合の実装方法を示します。実は意外と簡単に実装可能です。
行のレイアウトの定義
まずは行毎のレイアウトを定義します。これを res/layout/row.xml とします。
ここではリニアレイアウトを使って TextView を縦に三つ並べています。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<TextView
android:textSize="30sp"
android:id="@+id/textView1"
android:layout_height="wrap_content"
android:layout_width="match_parent"/>
<TextView
android:textSize="14sp"
android:id="@+id/textView2"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:background="#606060"
android:textColor="#000000"/>
<TextView
android:textSize="14sp"
android:id="@+id/textView3"
android:layout_height="wrap_content"
android:layout_width="match_parent"/>
</LinearLayout>
カスタムのアダプターを実装
次にカスタムのアダプターを実装します。
アダプターはBaseAdapter から派生し、 getCount, getItem, getItemId、getView を実装すれば OK です。
package com.keicode.android.test;
...
public class ListViewTest3 extends Activity
implements OnClickListener {
...
private class BookAdapter extends BaseAdapter {
@Override
public int getCount() {
return dataList.size();
}
@Override
public Object getItem(int position) {
return dataList.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(
int position,
View convertView,
ViewGroup parent) {
TextView textView1;
TextView textView2;
TextView textView3;
View v = convertView;
if(v==null){
LayoutInflater inflater =
(LayoutInflater)
getSystemService(Context.LAYOUT_INFLATER_SERVICE);
v = inflater.inflate(R.layout.row, null);
}
Book book = (Book)getItem(position);
if(book != null){
textView1 = (TextView) v.findViewById(R.id.textView1);
textView2 = (TextView) v.findViewById(R.id.textView2);
textView3 = (TextView) v.findViewById(R.id.textView3);
textView1.setText(book.title);
textView2.setText(book.author);
textView3.setText(book.isbn);
}
return v;
}
}
}
インナークラスとして定義することで、 dataList メンバーに直接アクセスできるようになり、実装が非常に簡単になっていることがポイントです。
getView メソッド内では LayoutInflater を用いて XML ファイルから View オブジェクトを作成しています。この View オブジェクト内で findViewById で View (ウィジェット) を取得して、適切なデータをセットします。
コードの全体像は次のようになります。
package com.keicode.android.test;
import java.util.ArrayList;
import java.util.List;
import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.View.OnClickListener;
import android.widget.ArrayAdapter;
import android.widget.BaseAdapter;
import android.widget.Button;
import android.widget.ListView;
import android.widget.TextView;
public class ListViewTest3 extends Activity
implements OnClickListener {
static final String TAG = "ListViewTest";
ListView listView;
Button addButton;
static List<Book> dataList = new ArrayList<Book>();
//static ArrayAdapter<Book> adapter;
static BookAdapter adapter;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
findViews();
setListeners();
setAdapters();
}
protected void findViews(){
listView = (ListView)findViewById(R.id.listView1);
addButton = (Button)findViewById(R.id.button1);
}
protected void setListeners(){
addButton.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch(v.getId()){
case R.id.button1:
addItem();
break;
}
}
protected void setAdapters(){
/*adapter = new ArrayAdapter<Book>(
this,
android.R.layout.simple_list_item_1,
dataList);*/
adapter = new BookAdapter();
listView.setAdapter(adapter);
}
protected void addItem(){
dataList.add(
new Book(
"The Immortal Life of Henrietta Lacks",
"1400052173", "Rebecca Skloot"));
adapter.notifyDataSetChanged();
}
private class BookAdapter extends BaseAdapter {
@Override
public int getCount() {
return dataList.size();
}
@Override
public Object getItem(int position) {
return dataList.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(
int position,
View convertView,
ViewGroup parent) {
TextView textView1;
TextView textView2;
TextView textView3;
View v = convertView;
if(v==null){
LayoutInflater inflater =
(LayoutInflater)
getSystemService(Context.LAYOUT_INFLATER_SERVICE);
v = inflater.inflate(R.layout.row, null);
}
Book book = (Book)getItem(position);
if(book != null){
textView1 = (TextView) v.findViewById(R.id.textView1);
textView2 = (TextView) v.findViewById(R.id.textView2);
textView3 = (TextView) v.findViewById(R.id.textView3);
textView1.setText(book.title);
textView2.setText(book.author);
textView3.setText(book.isbn);
}
return v;
}
}
}
前回の Book オブジェクトを使った例からの変更点はコメントアウトして残してあります。 手間はほとんど変わりなく、表現力が大幅にアップするのでぜひ活用したいものです。