web-dev-qa-db-ja.com

AndroidのViewHolderパターンの利点は何ですか?

Androidプログラム;およびArrayAdapterが必要な場合は、単にクラスを作成できます(ほとんどの場合ViewHolderサフィックス)またはconvertViewを直接膨らませて、IDでビューを見つけます。
つまり、ViewHolderを使用する利点は何ですか?
ここに両方の​​例:

@Override
public View getView(int position, View convertView, ViewGroup parent) {
    if (convertView == null) {
        convertView = ((Activity)getContext()).getLayoutInflater().inflate(R.layout.row_phrase, null);
    }
    ((TextView) convertView.findViewById(R.id.txtPhrase)).setText("Phrase 01");
}

または、次のようにArrayAdapterで内部クラスを作成します。

static class ViewHolder {   
    ImageView leftIcon;   
    TextView upperLabel;  
    TextView lowerLabel;  
}

そして最後にgetViewで:

@Override
public View getView(int position, View convertView, ViewGroup parent) {
    ViewHolder holder = null;
    if (view == null) {
        view = LayoutInflater.from(context).inflate(R.layout.row_layout,
                    null, false);
        holder = new ViewHolder();
        holder.leftIcon = (ImageView) view.findViewById(R.id.leftIcon);
    }
}
36

リストビューリサイクルの仕組みを理解する

ListViewのリサイクルメカニズムの仕組み

現在使用中の行をリサイクルすることはできません。上記のリンクは、リストビューリサイクルメカニズムの仕組みを説明しています

それでは、ViewHolderを使用する利点は何ですか?

ドキュメントの引用

コードは、ListViewのスクロール中にfindViewById()を頻繁に呼び出し、パフォーマンスを低下させる可能性があります。アダプターがリサイクルのために拡大表示を返した場合でも、要素を検索して更新する必要があります。 findViewById()を繰り返し使用する方法は、「ビューホルダー」デザインパターンを使用することです。

_    public View getView(int position, View convertView, ViewGroup parent) { 
             ViewHolder holder; 

             if (convertView == null) { // if convertView is null
                 convertView = mInflater.inflate(R.layout.mylayout, 
                         parent, false);
                 holder = new ViewHolder(); 
                     // initialize views  
                convertView.setTag(holder);  // set tag on view
            } else { 
                holder = (ViewHolder) convertView.getTag();
                        // if not null get tag 
                        // no need to initialize
            } 

            //update views here  
            return convertView; 
    }
_

convertView.setTag(holder)holder = (ViewHolder) ConvertView.getTag()の重要な部分を見逃した

http://developer.Android.com/training/improving-layouts/smooth-scrolling.html

34
Raghunandan

ListViewを飛び回ると、常に表示されるビューはほんの一握りです。つまり、アダプター内のすべてのアイテムのビューをインスタンス化する必要はありません。ビューが画面外にスクロールすると、再利用するか、recycledできます。

ビューのリサイクルとViewHolderパターンは同じではありません。 ViewHolderパターンは、単にview.findViewById(int)呼び出しの回数を減らすためのものです。 ViewHolderパターンは、ビューのリサイクルを利用する場合にのみ機能します。

getView(int position, View convertView, ViewGroup parent)では、convertViewパラメーターはeithernullであるか、リサイクルされたビューです。別のリストアイテムのデータをバインドします。

ViewHolderパターンがなくても、ビューのリサイクルを活用できます(つまり、盲目的にビューをインスタンス化しない)。

_public View getView(int position, View convertView, ViewGroup parent) {
  View view = convertView;
  if (view == null) {
    view = // inflate new view
  }

  ImageView imageView = (ImageView) view.findViewById(R.id.listitem_image);
  TextView textView = (TextView) view.findViewById(R.id.listitem_text);
  TextView timestampView = (TextView) view.findViewById(R.id.listitem_timestamp);
  ProgressBar progressSpinnerView = (ProgressBar) view.findViewById(R.id.progress_spinner);

  // TODO: set correct data for this list item
  // imageView.setImageDrawable(...)
  // textView.setText(...)
  // timestampView.setText(...)
  // progressSpinnerView.setProgress(...)

  return view;
}
_

上記はビューのリサイクルの例です。各行の新しいビューは膨らませません。再利用するビューが与えられていない場合にのみビューを膨張させます。 ビューを膨らませることを避けることは、リストをスクロールするときのパフォーマンスを確実に助ける部分です:ビューのリサイクルを活用してください。

それでは、その時のViewHolderは何ですか?現在、行自体がすでに存在しているかどうかに関係なく、everyアイテムに対して4x findViewById(int)を実行しています。 findViewById(int)は、指定されたIDを持つ子孫を見つけるまでViewGroupを再帰的に反復するため、これはリサイクルビューにとっては無意味です。すでに参照しているビューを再検索しています。

これを回避するには、ViewHolderオブジェクトを使用して、サブビューを「見つけた」後の参照を保持します。

_private static class ViewHolder {
  final TextView text;
  final TextView timestamp;
  final ImageView icon;
  final ProgressBar progress;

  ViewHolder(TextView text, TextView timestamp, ImageView icon, ProgressBar progress) {
    this.text = text;
    this.timestamp = timestamp;
    this.icon = icon;
    this.progress = progress;
  }
}
_

View.setTag(Object)を使用すると、ビューに任意のオブジェクトを保持するように指示できます。 findViewById(int)呼び出しを行った後にViewHolderのインスタンスを保持するために使用する場合、リサイクルビューでView.getTag()を使用して、呼び出しを何度も繰り返す必要をなくすことができます。

_public View getView(int position, View convertView, ViewGroup parent) {
  View view = convertView;
  if (view == null) {
    view = // inflate new view
    ViewHolder holder = createViewHolderFrom(view);
    view.setTag(holder);  
  }
  ViewHolder holder = view.getTag();
  // TODO: set correct data for this list item
  // holder.icon.setImageDrawable(...)
  // holder.text.setText(...)
  // holder.timestamp.setText(...)
  // holder.progress.setProgress(...)
  return view;
}

private ViewHolder createViewHolderFrom(View view) {
    ImageView icon = (ImageView) view.findViewById(R.id.listitem_image);
    TextView text = (TextView) view.findViewById(R.id.listitem_text);
    TextView timestamp = (TextView) view.findViewById(R.id.listitem_timestamp);
    ProgressBar progress = (ProgressBar) view.findViewById(R.id.progress_spinner);

    return new ViewHolder(text, timestamp, icon, progress);
}
_

この最適化のパフォーマンス上の利点は疑わしい 、しかしそれは ViewHolderの利点 です。

17
ataulm

ViewHolderデザインパターンは、ListViewのレンダリングを高速化するために使用されます-実際にスムーズに動作させるために、findViewByIdはリストアイテムがレンダリングされるたびに使用すると非常に高価です(DOM解析を行います)。レイアウト階層を横断する必要があります また、オブジェクトをインスタンス化します。リストはスクロール中にそのアイテムを頻繁に再描画できるため、このようなオーバーヘッドは相当なものになる可能性があります。

これがどのように機能するかの良い説明を見つけることができます:

http://www.youtube.com/watch?v=wDBM6wVEO70&feature=youtu.be&t=7m

10分から、Googleの専門家がViewHolderのデザインパターンを説明しました。

[編集]

findViewByIdは新しいオブジェクトをインスタンス化せず、階層を横断するだけです-ここに参照があります http://androidxref.com/5.1.1_r6/xref/frameworks/base/core/Java/Android/view/ViewGroup.Java#361

14
marcinj