ViewHolder - Good Practice

Little newbie question. Why should we initialize ViewHolder in getView() ? Why can't we initialize it in the constructor?

+9
android android-viewholder
Oct 31 '12 at 18:03
source share
2 answers

You will have several ViewHolder objects.

A ListView by its nature, does not create new instances of View for each of its rows. This is so that if you have a ListView of a million things, you don’t need to store layout information for a million things. So what do you need to store? Just what is on the screen. Then you can reuse these views over and over again. Thus, your ListView of a million objects can have only 10 child views.

In your custom array adapter, you get a function called getView() that looks something like this:

 public View getView(int position, View convertView, ViewGroup parent) { //Here, position is the index in the list, the convertView is the view to be //recycled (or created), and parent is the ListView itself. //Grab the convertView as our row of the ListView View row = convertView; //If the row is null, it means that we aren't recycling anything - so we have //to inflate the layout ourselves. if(row == null) { LayoutInflater inflater = (LayoutInflater)getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE); row = inflater.inflate(R.layout.list_item, parent, false); } //Now either row is the recycled view, or one that we've inflated. All that left //to do is set the data of the row. In this case, assume that the row is just a //simple TextView TextView textView = (TextView) row.findViewById(R.id.listItemTextView); //Grab the item to be rendered. In this case, I'm just using a string, but //you will use your underlying object type. final String item = getItem(position); textView.setText(item); //and return the row return row; } 

It will work, but take a moment and see if you can find inefficiency here. Consider which of the above codes will be called redundant.

The problem is that we call row.findViewById again and again, even if we don’t change it after the first search. Although, if you only have a simple TextView in your list, it is probably not so bad if you have a complex layout or you have several views on which you want to set the data, you can lose a little time finding your look again and again.

So how do we fix this? Well, it would be wise to keep this TextView somewhere after we look at it. So, we introduce a class called a ViewHolder that “holds” views. Therefore, inside the adapter, enter the inner class as follows:

 private static class ViewHolder { TextView textView; } 

This class is private, because it is just a caching mechanism for the adapter, and it is static, so we don’t need a link to the adapter to use it.

This will keep our view, so we do not need to call row.findViewById several times. Where should we install it? When we first blow out the show. Where do we keep it? Views have their own tag field, which can be used to store meta-information about the view - exactly what we want! Then, if we have already seen this view, we just need to search for the tag instead of looking for each of the views within the string.

So, the if statement inside getView() becomes:

 //If the row is null, it means that we aren't recycling anything - so we have //to inflate the layout ourselves. ViewHolder holder = null; if(row == null) { LayoutInflater inflater = (LayoutInflater)getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE); row = inflater.inflate(R.layout.list_item, parent, false); //Now create the ViewHolder holder = new ViewHolder(); //and set its textView field to the proper value holder.textView = (TextView) row.findViewById(R.id.listItemTextView); //and store it as the 'tag' of our view row.setTag(holder); } else { //We've already seen this one before! holder = (ViewHolder) row.getTag(); } 

Now we just need to update the text value of holder.textView, since it already refers to the redesigned view! Therefore, our final adapter code will look like this:

 public View getView(int position, View convertView, ViewGroup parent) { //Here, position is the index in the list, the convertView is the view to be //recycled (or created), and parent is the ListView itself. //Grab the convertView as our row of the ListView View row = convertView; //If the row is null, it means that we aren't recycling anything - so we have //to inflate the layout ourselves. ViewHolder holder = null; if(row == null) { LayoutInflater inflater = (LayoutInflater)getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE); row = inflater.inflate(R.layout.list_item, parent, false); //Now create the ViewHolder holder = new ViewHolder(); //and set its textView field to the proper value holder.textView = (TextView) row.findViewById(R.id.listItemTextView); //and store it as the 'tag' of our view row.setTag(holder); } else { //We've already seen this one before! holder = (ViewHolder) row.getTag(); } //Grab the item to be rendered. In this case, I'm just using a string, but //you will use your underlying object type. final String item = getItem(position); //And update the ViewHolder for this View text to the correct text. holder.textView.setText(item); //and return the row return row; } 

And you're done!

Some things to think about:

  • How will this change if you have several views in a row that you want to change? As a task, create a ListView where each row has two TextView and ImageView objects
  • When debugging a ListView, check a few things so that you can really see what is happening:
    • How many times the ViewHolder constructor is called.
    • holder.textView.getText() value before updating at the end of getView()
+29
Oct 31 '12 at 18:40
source share
— -

When we scroll through the list every time a row of rows is filled and a new row representation is created for each row, we need to initialize the view holder. Just as I have two TextViews in a row,

  static class ViewHolder { protected TextView title; protected TextView type; } public View getView(int position, View convertView, ViewGroup parent) { View view = null; if (convertView == null) { LayoutInflater inflator = context.getLayoutInflater(); view = inflator.inflate(R.layout.feeds_rowview, null); final ViewHolder viewHolder = new ViewHolder(); view.setTag(viewHolder); viewHolder.title = (TextView) view.findViewById(R.id.Title); viewHolder.type = (TextView) view.findViewById(R.id.Type); } else { view = convertView; } ViewHolder holder = (ViewHolder) view.getTag(); holder.title.setText(list.get(position).getTitle()); holder.type.setText(list.get(position).getType()); return view; } 
+2
Oct 31 '12 at 18:15
source share



All Articles