Question

When you are developing an Android program; and you want to have a ArrayAdapter you can Simply have a Class (most of times with ViewHolder suffix) or directly inflate your convertView and find your view by id.
So What is the benefit of using ViewHolder?
The example of both here :

@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");
}

Or create an inner class in the ArrayAdapter as following:

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

and finally in the 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);
    }
}
Was it helpful?

Solution

Understand how listview recycling works

How ListView's recycling mechanism works

You cannot recycle a row that is presently in use. The above link explains how listview recycling mechanism works

So What is the benefit of using ViewHolder?

Quoting docs

Your code might call findViewById() frequently during the scrolling of ListView, which can slow down performance. Even when the Adapter returns an inflated view for recycling, you still need to look up the elements and update them. A way around repeated use of findViewById() is to use the "view holder" design pattern.

    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; 
    }

You missed the important part convertView.setTag(holder) and holder = (ViewHolder) ConvertView.getTag()

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

OTHER TIPS

As you fling through your ListView, there's only a handful of views being shown at any given time. This means that you don't have to instantiate a view for every item in your adapter; when a view scrolls off-screen, it can be reused, or recycled.

View recycling and the ViewHolder pattern are not the same. The ViewHolder pattern is solely to reduce the number of view.findViewById(int) calls you make. The ViewHolder pattern only works when you take advantage of view recycling.

In getView(int position, View convertView, ViewGroup parent), the convertView parameter is either null or it's a view that has been recycled: it will still have the data from a different list item bound to it.

Without the ViewHolder pattern, you can still take advantage of view recycling (i.e. not blindly instantiating views):

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;
}

Above is an example of view recycling - we do not inflate a new View for each row; we only inflate a view if we're not given one to reuse. Avoiding having to inflate a view is the part that will definitely help with performance when scrolling through your list: take advantage of view recycling.

So, what's the ViewHolder for then? We're currently doing 4x findViewById(int) for every item, regardless of whether the row itself already existed. As findViewById(int) recursively iterates down a ViewGroup til it finds a descendent with the given ID, this is a bit pointless for our recycled views - we're re-finding views that we already have references to.

Avoid this by using a ViewHolder object to hold references to the sub-views after you "find" them:

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) allows you to tell the View to hold an arbitrary object. If we use it to hold an instance of our ViewHolder after we do our findViewById(int) calls, then we can use View.getTag() on recycled views to avoid having to make the calls again and again.

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);
}

The performance benefits of this optimisation is questionable, but that's the benefit of the ViewHolder.

ViewHolder design pattern is used to speed up rendering of your ListView - actually to make it work smoothly, findViewById is quite expensive (it does DOM parsing) when used each time a list item is rendered, it must traverse your layout hierarchy and also instantiate objects. Since lists can redraw its items quite frequently during scrolling such overhead might be substantial.

you can find good explanation of how this works in :

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

starting from minute 10, you have explained ViewHolder design pattern by google experts.

[edit]

findViewById is not instantiating new objects, it only traverses hierarchy - here is reference http://androidxref.com/5.1.1_r6/xref/frameworks/base/core/java/android/view/ViewGroup.java#3610

Firstly :

In ListView when you scroll the ListView you need to create new item and bind its data on it so if you have much items in ListView it may cause memory leak because more objects you created for items , but Android using concept of recycle most of its API , and it meaning you create one object and use it instead of destroy it and declare new one , so when you scroll ListView API using the invisible items you scrolled and pass it for you in getView method it is convertView so here you deal with more items ListView

Secondly :

if you have custom item in ListView you need to attach your custom Layout on each item of the ListView so you will each time ListView bind new item using findViewById to get reference of the layout items. This method will go to search for your item in recursive way so you ViewHolder will help you to make the recursive done for one time only and then it will hold the reference of the layout item for you till you can attach it for ListView

hope this help you and feed me back in any not obvious thing

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top