ListViewで、独自のレイアウトを使用する
最終更新日:2015-08-16
ArrayAdapterをそのまま使うと、簡単にリスト表示が実装できますが、その反面レイアウトの自由度は減ります。ここでは、次の図のように1行のレイアウトを独自のものにする方法を紹介します。
1行分のレイアウトXMLを作成する
res/layoutに、リストの1行分のレイアウトを作成します。ここではitem_week.xmlとします。
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="16dp"
>
<ImageView
android:id="@+id/icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@android:drawable/star_on"
android:layout_centerVertical="true"
/>
<TextView
android:id="@+id/text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toRightOf="@+id/icon"
android:layout_centerVertical="true"
android:layout_marginLeft="8dp"
/>
</RelativeLayout>
ArrayAdapterを継承したクラスを作成する
次に、ArrayAdapterを継承したクラスを作成します。説明のために簡略化した実装例を示しますが、パフォーマンスの問題があるので、このままコピペして使わないでください。
public class WeekAdapter extends ArrayAdapter<String> {
private LayoutInflater mLayoutInflater;
public WeekAdapter(Context context) {
super(context, 0);
mLayoutInflater = LayoutInflater.from(context);
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View root = mLayoutInflater.inflate(R.layout.item_week, null);
String item = getItem(position);
TextView textView = (TextView) root.findViewById(R.id.text);
textView.setText(item);
return root;
}
}
コンストラクタでLayoutInflaterオブジェクトを取得しておきます。super()の第2引数は今回は独自レイアウトを使用するため、0を渡しておきます。
次に、このクラスのポイントとなるgetView()
をオーバーライドします。第1引数のpositionで
位置が渡されるので、getItem(position)
でデータを取得し、Viewを生成してreturnします。
パフォーマンス改善
この実装で論理的には正しそうですが、パフォーマンスの面で次の問題を抱えています。パフォーマンスが悪いと、スクロール時にひっかかりが生じ、ユーザー体験の低下につながってしまいます。
- 毎回ViewをXMLから生成している点
- findViewById()でViewを探している点
mLayoutInflater.inflate(R.layout.item_week, null)
はレイアウトXMLの複雑度にもよりますが、一般的にはコストの高い処理です。なので、できれば以前作った1行分のレイアウト(=View)は再利用したいものです。実はgetView()には、再利用のための仕組みがすでに備わっています。再利用可能なViewがある場合は、getView()
の第2引数であるconvertViewに渡されます。なので、convertViewがnullのときだけViewを生成するよう、次のように書き換えます。
@Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = mLayoutInflater.inflate(R.layout.item_week, null);
}
String item = getItem(position);
TextView textView = (TextView) convertView.findViewById(R.id.text);
textView.setText(item);
return convertView;
}
次に、getView()
の中で毎回findViewById()
でデータをセットするためのViewを取得しています。findViewById()
は該当Viewが見つかるまで、順にViewを探していくので時間のかかる処理となります。なので、Viewを生成した直後にfindViewById()で該当Viewを探しておき、その結果を後から使い回すようにします。
View
には、setTag()
/ getTag()
という、任意のオブジェクトをセットし、取得するためのメソッドがあります。ここでは、findViewById()
で取得したViewを保存するためのViewHolder
というオブジェクトをセットすることにします。
以上を踏まえ、パフォーマンスも考慮したWeekAdapterの実装は次のようなものになります。
public class WeekAdapter extends ArrayAdapter<String> {
private LayoutInflater mLayoutInflater;
public WeekAdapter(Context context) {
super(context, 0);
mLayoutInflater = LayoutInflater.from(context);
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = mLayoutInflater.inflate(R.layout.item_week, null);
convertView.setTag(new ViewHolder(convertView));
}
ViewHolder holder = (ViewHolder) convertView.getTag();
String item = getItem(position);
holder.mTextView.setText(item);
return convertView;
}
private static class ViewHolder {
TextView mTextView;
ViewHolder(View root) {
mTextView = (TextView) root.findViewById(R.id.text);
}
}
}