ViewHolderを使わないでListViewを高速化する

# 概要
一般的なListViewの高速化手法として`ViewHolder`というものがあります。

これはAdapter#getView()でアイテムの`findViewById()`の回数を減らす(生成済みのものを使いまわす)ことで速度を改善するというもの。

今回はこのViewHolderを使わないでListViewを高速化する方法を書きます。

その方法はズバリ、`Custom View`を使う方法です！！

順に説明していきます。

コードはGitHubに上げていますので、参考にしてみてください。

[mofumofu3n/NonViewHolder](https://github.com/mofumofu3n/NonViewHolder)

# アイテムをカスタムViewにする
ListViewのアイテムのレイアウトファイルと実装を書いていきます。

アイテムのルートレイアウトの名前は`ItemLayout.java`としています。

## レイアウトファイル
タイトル、概要、アイコンを持ったアイテムを想定しています。

```
<?xml version="1.0" encoding="utf-8"?>
<com.example.nonviewholder.ItemLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >
    
    <TextView 
        android:id="@+id/titleView"
       	android:layout_width="fill_parent" 
       	android:layout_height="wrap_content"
         />
         
    <TextView
       	android:id="@+id/descriptionView" 
       	android:layout_width="fill_parent"
       	android:layout_height="wrap_content"
        />
    
    <ImageView
        android:id="@+id/iconView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="@android:drawable/ic_dialog_alert"
        />

</com.example.nonviewholder.ItemLayout>
```

## ItemLayoutの実装
アイテムのレイアウトはLinearLayoutを継承して実装しています。

ここでのポイントは`onFinishInflate()`が呼ばれた時に`findViewById()`することです。

こうすることでAdapterで新しくアイテムのレイアウトを生成するときにだけ`findViewById()`されます。

まさにViewHolderと同様の効果が得られます。

```
public class ItemLayout extends LinearLayout {
	// タイトル
	TextView mTitleView;
	// 概要
	TextView mDescriptionView;
	// アイコン
	ImageView mIconView;

	public ItemLayout(Context context, AttributeSet attrs) {
		super(context, attrs);
	}
	
	@Override
	protected void onFinishInflate() {
		super.onFinishInflate();
		
		mTitleView = (TextView) findViewById(R.id.titleView);
		mDescriptionView = (TextView) findViewById(R.id.descriptionView);
		mIconView = (ImageView) findViewById(R.id.iconView);
	}
	
	public void bindView(Item item) {
		mTitleView.setText(item.title);
		mDescriptionView.setText(item.description);
		mIconView.setImageResource(item.icon);
	}
}

```

# Adapterの処理
アダプターの処理はViewHolderを利用する時よりも簡単にかけます。

`getView()`の動きはほぼ一緒ですがViewHolderと同様の処理を

Custom Viewの方で行っているため、レイアウトの生成(or 再利用)とアイテムをレイアウトに渡すだけで済みます。

```
public class CustomItemAdapter extends ArrayAdapter<Item> {
	private LayoutInflater mFactory;
	private int mItemLayoutResource;

	public CustomItemAdapter(Context context, int resource, List<Item> objects) {
		super(context, resource, objects);

		mFactory = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
		mItemLayoutResource = resource;
	}
	
	@Override
	public View getView(int position, View convertView, ViewGroup parent) {
		final ItemLayout view;
		
		if (convertView == null) {
			// Viewがなかったら生成
			view = (ItemLayout) mFactory.inflate(mItemLayoutResource, null);
		} else {
			view = (ItemLayout) convertView;
		}
		
		view.bindView(getItem(position));
		
		return view;
	}
}
```


# ViewHolderとの比較
ViewHolderを利用した時のAdapterはこちら。

Viewに対する処理がAdapterに依存するので、見通しが悪いです。(工夫の余地はあるかもしれません)

AdapterにViewの処理を書くよりもCustom Viewにアイテムだけ渡しそちらでViewの処理を任せたほうが
Adapterの肥大化が防げます。

```
public class ItemAdapter extends ArrayAdapter<Item> {
	private LayoutInflater mFactory;
	private int mItemLayoutResource;

	public ItemAdapter(Context context, int resource, List<Item> objects) {
		super(context, resource, objects);

		mFactory = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
		mItemLayoutResource = resource;
	}

	@Override
	public View getView(int position, View convertView, ViewGroup parent) {
		final ViewHolder holder;
		
		if (convertView == null) {
			convertView = mFactory.inflate(mItemLayoutResource, null);
			holder = new ViewHolder();
			holder.title = (TextView) convertView.findViewById(R.id.titleView);
			holder.desc = (TextView) convertView.findViewById(R.id.descriptionView);
			holder.icon = (ImageView) convertView.findViewById(R.id.iconView);

			convertView.setTag(holder);
		} else {
			holder = (ViewHolder) convertView.getTag();
		}

		final Item item = getItem(position);

		holder.title.setText(item.title);
		holder.desc.setText(item.description);
		holder.icon.setImageResource(item.icon);

		return convertView;
	}

	class ViewHolder {
		TextView title;
		TextView desc;
		ImageView icon;
	}
}
```

# まとめ
ViewHolderを使わないでListViewを高速化する方法を書いてみました。

個人的にはTextViewに文字をセットする等のViewを操作する処理をCustom Viewに任せられ、

Adapterの見通しもよくなるため、ViewHolderよりもCustom Viewの方が好きです。

ViewHolderでもっと良い書き方あるよ！と言う方がいらっしゃいましたら、

ぜひコメントに書いて下さい。

あと、個人的なことですがQiitaに3日連続で投稿しています。

そろそろ投稿するネタが尽きてきました。。