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

  • 201
    いいね
  • 1
    コメント
この記事は最終更新日から1年以上が経過しています。

概要

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

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

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

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

順に説明していきます。

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

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日連続で投稿しています。

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