51

I always use LayoutInflater and findViewById for creating new item in thegetView method of an Adapter.

But in many articles people write that findViewById is very very slow and strongly recommend to use the View Holder Pattern.

Can anyone explain why findViewById is so slow? And why the View Holder Pattern is faster?

And what should I do if needed to add different items to a ListView? Should I create classes for each type?

static class ViewHolderItem1 {
    TextView textViewItem;
}

static class ViewHolderItem2 {
    Button btnViewItem;
}
static class ViewHolderItem3 {
    Button btnViewItem;
    ImageView imgViewItem;
}
serv-inc
  • 29,557
  • 9
  • 128
  • 146

2 Answers2

86

Can anyone explain why findViewById is so slow? And why View Holder Pattern is faster?

When you are not using Holder so getView() method will call findViewById() as many times as you row(s) will be out of View. So if you have 1000 rows in List and 990 rows will be out of View then 990 times will be called findViewById() again.

Holder design pattern is used for View caching - Holder (arbitrary) object holds child widgets of each row and when row is out of View then findViewById() won't be called but View will be recycled and widgets will be obtained from Holder.

if (convertView == null) {
   convertView = inflater.inflate(layout, null, false);
   holder = new Holder(convertView);
   convertView.setTag(holder); // setting Holder as arbitrary object for row
}
else { // view recycling
   // row already contains Holder object
   holder = (Holder) convertView.getTag();
}

// set up row data from holder
titleText.setText(holder.getTitle().getText().toString());

Where Holder class can looks like:

public class Holder {

   private View row;
   private TextView title;

   public Holder(View row) {
      this.row = row;
   }

   public TextView getTitle() {
      if (title == null) {
         title = (TextView) row.findViewById(R.id.title);
      }
      return title;
   }
}

As @meredrica pointed your if you want to get better performance, you can use public fields (but it destroys encapsulation).

Update:

Here is second approach how to use ViewHolder pattern:

ViewHolder holder;
// view is creating
if (convertView == null) {
   convertView = LayoutInflater.from(mContext).inflate(R.layout.row, parent, false);
   holder = new ViewHolder();   
   holder.title = (TextView) convertView.findViewById(R.id.title);
   holder.icon = (ImageView) convertView.findViewById(R.id.icon);
   convertView.setTag(holder);
}
// view is recycling
else {
   holder = (ViewHolder) convertView.getTag();
}

// set-up row
final MyItem item = mItems.get(position);
holder.title.setText(item.getTitle());
...

private static class ViewHolder {

   public TextView title;
   public ImageView icon;
}

Update #2:

As everybody know, Google and AppCompat v7 as support library released new ViewGroup called RecyclerView that is designed for rendering any adapter-based views. As @antonioleiva says in post: "It is supossed to be the successor of ListView and GridView".

To be able to use this element you need basically one the most important thing and it's special kind of Adapter that is wrapped in mentioned ViewGroup - RecyclerView.Adapter where ViewHolder is that thing we are talking about here :) Simply, this new ViewGroup element has its own ViewHolder pattern implemented. All what you need to do is to create custom ViewHolder class that has to extend from RecyclerView.ViewHolder and you don't need to care about checking whether current row in adapter is null or not.

Adapter will do it for you and you can be sure that row will be inflated only in the case it must be inflated (i would say). Here is simple imlementation:

public static class ViewHolder extends RecyclerView.ViewHolder {

   private TextView title;

   public ViewHolder(View root) {
      super(root);
      title = root.findViewById(R.id.title);
   }
}

Two important things here:

  • You have to call super() constructor in which you need to pass your root view of row
  • You are able to get specific position of row directly from ViewHolder via getPosition() method. This is useful when you want to do some action after tapping1 on row widget.

And an usage of ViewHolder in Adapter. Adapter has three methods you have to implement:

  • onCreateViewHolder() - where ViewHolder is created
  • onBindViewHolder() - where you are updating your row. We can say it's piece of code where you are recycling row
  • getItemCount() - i would say it's same as typical getCount() method in BaseAdapter

So a little example:

@Override 
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
   View root = LayoutInflater.from(mContext).inflate(myLayout, parent, false);
   return new ViewHolder(root);
}

@Override public void onBindViewHolder(ViewHolder holder, int position) {
   Item item = mItems.get(position);
   holder.title.setText(item.getTitle());
}

@Override public int getItemCount() {
   return mItems != null ? mItems.size() : 0;
}

1 It's good to mention that RecyclerView doesn't provide direct interface to be able to listen item click events. This can be curious for someone but here is nice explanation why it's not so curious as it actually looks.

I solved this by creating my own interface that is used to handle click events on rows (and any kind of widget you want in row):

public interface RecyclerViewCallback<T> {

   public void onItemClick(T item, int position); 
}

I'm binding it into Adapter through constructor and then call that callback in ViewHolder:

root.setOnClickListener(new View.OnClickListener {
   @Override
   public void onClick(View v) {
      int position = getPosition();
      mCallback.onItemClick(mItems.get(position), position);
   }
});

This is basic example so don't take it as only one possible way. Possibilities are endless.

Community
  • 1
  • 1
Simon Dorociak
  • 32,775
  • 10
  • 65
  • 104
  • 5
    Also, findViewById uses DOM getter, which are slow. Make sure to not use getters/setters in view holders to get even more performance. I know, that breaks encapsulation a bit but nobody will ever expose a viewholder to begin with. – meredrica Oct 10 '13 at 07:48
  • 1
    @meredrica you are right but sometimes an usage of public fields is not bad. In this case is not bad to make holder inner class and create public fields. – Simon Dorociak Oct 10 '13 at 07:53
  • 2
    That's what I said :) I would never use public writable fields outside of a Viewholder tho. – meredrica Oct 10 '13 at 07:54
  • Thanks for answer. Now i understand) But, what's about last part of my question? What should i do if needed to add different items to ListView? Should i create static classes for each type? – Suvitruf - Andrei Apanasik Oct 10 '13 at 07:59
  • Do you mean different appearance of each row? – Simon Dorociak Oct 10 '13 at 08:01
  • @H.Moodym i need to add items(rows) to ListView based on 3 different layouts. – Suvitruf - Andrei Apanasik Oct 10 '13 at 08:04
  • 1
    @Suvitruf So then look at [this thread](http://stackoverflow.com/questions/4777272/android-listview-with-different-layout-for-each-row) at Cristian's answer. – Simon Dorociak Oct 10 '13 at 08:06
  • But when we write new Holder(convertView), isnt findViewById is called in ViewHolder class? This becomes same as the normal pattern because in that also we check that if convertView is NULL, then do findViewById(). ? – Diffy Nov 13 '14 at 07:43
  • @Diffy i recently updated my answer. To clarify what you are asking , `findViewById()` is called in both cases only during view's creation e.q. when `convertView` is null. In other case view is recycling with `ViewHolder` object. – Simon Dorociak Nov 13 '14 at 11:53
  • But recycling happens even when we dont use ViewHolder and we check for if(convertView==null). If its not null, then there is no requirement for findViewById().So where is ViewHolder helpful? – Diffy Nov 13 '14 at 11:56
  • @Diffy You are not reading what im saying. if you do not use ViewHolder then findViewById is called in both cases - when is convertView null and when view is recycling. But if you are using ViewHolder pattern, findViewById will be called only when convertView is null and not when view is recycling. – Simon Dorociak Nov 13 '14 at 11:58
  • Okay. I got it. But in case of not using ViewHolder shouldnt we write findViewById() inside the condition if(convertView==null)? Will the elements of a view be null even if the view is recycled? – Diffy Nov 13 '14 at 12:03
  • @Diffy yes if you dont use ViewHolder pattern you need to call findViewById in both cases because you have any object that holds row's widgets. – Simon Dorociak Nov 13 '14 at 12:15
  • You are mixing two completely different notions here. View reuse is nothing to do with *holder pattern* (hence the question itself). You are doing view reuse if you handle the `if (convertView != null) {` case (to minimze calls to `inflate`). You need no holder to do this. – vbence Jan 01 '15 at 12:50
  • @vbence you completely not understand a problem. We talking about number of `findViewById()` calls and not about minimize calls of inflate method. For future read question and answer more carefully. – Simon Dorociak Jan 01 '15 at 12:54
  • @Ragnar I read the answer several times. I am familiar with the concepts you mention but you are not making the clear distinction in it between the merits of the holder pattern and the merits of view recycling in general. - Also you are speculating what the OS does with off-screen items (I don't know on which version of the OS you made these observations - 4.1 certainly does nothing like that). – vbence Jan 01 '15 at 14:17
  • The above explanation is great. But the Google doc simplifies the example. [Android View Holder](http://developer.android.com/training/improving-layouts/smooth-scrolling.html) – VirtualProdigy May 14 '15 at 20:24
  • @Ragnar is the scenario described here possible http://stackoverflow.com/q/32901035/2079692 – Anudeep Samaiya Oct 02 '15 at 11:58
  • What should you do w/ different listview group holders? – Tim Nuwin Dec 01 '15 at 20:41
  • @TimNuwin make abstract class for viewholder and then create generic adapter where parameter will be that abstractviewholder. Then create some factory for generating viewholder subclasses which will be used in onCreateViewHolder method and then in onBindViewHolder you will use particular item at position which has same method but with custom implementation. Hope it makes sence for you. Sorry for very late comment but currently i dont have time to contribute here. – Simon Dorociak Feb 16 '17 at 08:52
6

ViewHolder pattern will create a static instance of the ViewHolder and attach it to the view item the first time it is loaded, and then it will be retrieved from that view tag on the future calls. as we knew getView() method is called very frequently, expecially when lots of elements in listview to scroll, in fact it is called each time a listview item becomes visible on scroll.

ViewHolder Pattern will prevents findViewById() to being called lots of times uselessy, keeping the views on a static reference, it is a good pattern to save some resources (expecially when you need to reference many views in your listview items).

very well said by @RomainGuy

The ViewHolder can, and should, also be used to store temporary data structures to avoid memory allocations in getView(). The ViewHolder contains a char buffer to avoid allocations when getting data from the Cursor.

Bhavesh Patadiya
  • 25,555
  • 14
  • 76
  • 105