Findviewbyid VS View Holder Pattern in Listview Adapter

findViewById vs View Holder Pattern in ListView adapter


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.

What is the benefit of ViewHolder pattern in android?

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

Explain individual role of convertView and View Holder Pattern in ListView


  • convertView

it's a view that was previously inflated (when convertView==null) and was on the screen, but now have been scrolled out of the screen, so you can re-use it to display data from the view that will enter the screen soon.

improves performance by reducing view inflation (calls to inflater.inflate are expensive), reduces garbage collection, and reduces memory allocation.

  • view holder

keeps a direct reference to your view.

potentially improves performance because findViewById is a for-loop going around the view hierarchy that can potentially eat away quite precious time of the UI thread. This improvement might be small, but in a fast scrolling list, every nano-second counts to have a nice smoth 60fps.

Should I use View Holder pattern for my adapter?

if you have more data in your list view then you should use recycling, because it improves scrolling and performance also. here is code look like if you use view holder in your adapter.

your ViewHolder looks like

public  class ViewHolder {

public TextView merchant_type;
public TextView merchant_name;
public TextView merchant_location;

}

and your getView method

    View vi = convertView;
ViewHolder holder;

if (convertView == null) {

vi = inflater.inflate(R.layout.list_item, null);

holder = new ViewHolder();
holder.merchant_type = (TextView) vi.findViewById(R.id.merchant_type);
holder.merchant_name = (TextView) vi.findViewById(R.id.merchant_name);
holder.merchant_location = (TextView) vi.findViewById(R.id.merchant_location);

vi.setTag(holder);

} else
holder = (ViewHolder) vi.getTag();

// now set your text view here like

holder.merchant_name.setText("Bla Bla Bla");


// return your view
return vi;

Optimized way to use getView method in Adapter

you should use viewholder like this:

ViewHolder holder;

@Override
public View getView(int position, View convertView, ViewGroup parent) {
// TODO Auto-generated method stub
if (convertView == null) {
holder = new ViewHolder();
convertView = inflater.inflate(R.layout.chocolate_list_item,
parent, false);
holder.txtvwChocolateName = (TextView) convertView
.findViewById(R.id.xtxtvwChocolateName);
holder.imgvwChocolateIcon=(ImageView)convertView.findViewById(R.id.ximgvwChocolateIcon);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}

holder.txtvwChocolateName.setText(alChocolate.get(position)
.getStrChocolateName());
holder.imgvwChocolateIcon.setImageResource(images[position]);
return convertView;
}

class ViewHolder {
TextView txtvwChocolateName;
ImageView imgvwChocolateIcon;
}

Optimized way to use getView method in Adapter

you should use viewholder like this:

ViewHolder holder;

@Override
public View getView(int position, View convertView, ViewGroup parent) {
// TODO Auto-generated method stub
if (convertView == null) {
holder = new ViewHolder();
convertView = inflater.inflate(R.layout.chocolate_list_item,
parent, false);
holder.txtvwChocolateName = (TextView) convertView
.findViewById(R.id.xtxtvwChocolateName);
holder.imgvwChocolateIcon=(ImageView)convertView.findViewById(R.id.ximgvwChocolateIcon);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}

holder.txtvwChocolateName.setText(alChocolate.get(position)
.getStrChocolateName());
holder.imgvwChocolateIcon.setImageResource(images[position]);
return convertView;
}

class ViewHolder {
TextView txtvwChocolateName;
ImageView imgvwChocolateIcon;
}


Related Topics



Leave a reply



Submit