What is the benefit of ViewHolder pattern in android?
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
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
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.