FindViewById vs View Owner Template in ListView Adapter

I always use LayoutInflater and findViewById to create a new item in the getView method of Adapter .

But in many articles, people write that findViewById very slow and highly recommend using a view holder template.

Can anyone explain why findViewById so slow? And why is the owner view template faster?

And what if I need to add different elements to the 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; } 
+46
android listview android-adapter
Oct 10 '13 at 7:40
source share
2 answers

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

If you are not using Holder, the getView() method of getView() will call findViewById() as many times as you will not see. So if you have 1000 rows in the List and 990 rows will be absent, then findViewById() will be called 990 times again.

The holder design pattern is used to view caching. The Holder object (arbitrary) contains the child widgets of each row, and when the row is not in the view, findViewById () will not be called, but the View will be processed and the 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 the Holder class might look like this:

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

Since @meredrica has told you if you want to improve performance, you can use public fields (but this destroys encapsulation).

Update:

Here is a second approach on how to use the ViewHolder template:

 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 you know, Google and AppCompat v7 as a support library have released a new ViewGroup called RecyclerView , which is designed to render any types based on adapters, As @antonioleiva says in a post : "It is the successor to ListView and GridView."

To be able to use this element, you need basically one most important thing, and this is a special kind of adapter that is wrapped in the mentioned ViewGroup - RecyclerView.Adapter , where ViewHolder is what we are talking about here :) Just this new ViewGroup element has your own ViewHolder template. All you have to do is create your own ViewHolder class, which should extend from RecyclerView.ViewHolder , and you do not need to worry about whether the current row in the adapter is null or not.

The adapter will do this for you, and you can be sure that the line will be inflated only if it should be inflated (I would say). Here is a simple read:

 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 need to call the constructor super () , in which you need to pass your root string view
  • You can get a specific row position directly from ViewHolder using the getPosition () method. This is useful when you want to do some action after pressing 1 on the row widget.

And using ViewHolder in the adapter. The adapter has three methods that you must implement:

  • onCreateViewHolder () - when creating ViewHolder
  • onBindViewHolder () - where you update your row. We can say that this is a piece of code in which you process a string
  • getItemCount () - I would say the same as the regular getCount () method in BaseAdapter

So a small 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 is good to note that RecyclerView does not provide a direct interface for listening to element click events. It may be interesting for someone, but here is a good explanation why it is not as curious as it actually looks.

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

 public interface RecyclerViewCallback<T> { public void onItemClick(T item, int position); } 

I bind it to the adapter through the constructor, and then call this callback in the ViewHolder:

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

This is a basic example, so do not take it as the only possible way. The possibilities are endless.

+84
Oct 10 '13 at 7:45
source share

The ViewHolder template 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 extracted from this view tag for future calls. since we knew that the getView () method is called very often, especially when many of the elements in the listview scroll, it is actually called every time the listview element becomes visible in the scroll.

The ViewHolder template will prevent findViewById() called useless many times, keeping the views on a static link, this is a good template for saving some resources (especially if you need to reference many views in list items).

very well said @RomainGuy

ViewHolder can and should also be used to store temporary structure data to avoid memory allocation in getView (). ViewHolder contains a char buffer to avoid selection when retrieving data from the Cursor.

+5
Oct 10 '13 at 7:50
source share



All Articles