Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    text
    copied!<p>You will have multiple <code>ViewHolder</code> objects in existence. </p> <p>A <code>ListView</code> by its nature doesn't create new <code>View</code> instances for each of its rows. This is so that if you have a <code>ListView</code> of a million things, you don't need to store layout information for a million things. So what do you need to store? Just the things that are on the screen. You can then reuse those views over and over again. This way, your <code>ListView</code> of a million objects can just have maybe 10 child views.</p> <p>In your custom array adapter, you will have a function called <code>getView()</code> that looks something like this:</p> <pre><code>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's 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; } </code></pre> <p>This will work, but take a moment and see if you can spot the inefficiency here. Think about which of the above code will be called redundantly.</p> <p>The problem is that we are calling <code>row.findViewById</code> over and over again, even though after the first time we look it up, it will never change. While if you only have a simple <code>TextView</code> in your list, it's probably not that bad, if you have a complex layout, or you have multiple views that you want to set data for, you could lose a bit of time finding your view over and over again.</p> <p>So how do we fix this? Well, it would make sense to store that TextView somewhere after we look it up. So we introduce a class called a <code>ViewHolder</code>, which "holds" the views. So inside of the adaptor, introduce an inner class like so:</p> <pre><code>private static class ViewHolder { TextView textView; } </code></pre> <p>This class is private, since it's just a caching mechanism for the adapter, and it is static so that we don't need a reference to the adapter to use it.</p> <p>This will store our view so that we don't have to call <code>row.findViewById</code> multiple times. Where should we set it? When we inflate the view for the first time. Where do we store it? Views have a custom "tag" field, which can be used to store meta-information about the view - exactly what we want! Then, if we've already seen this view, we just have to look up the tag instead of looking up each of the views within the row..</p> <p>So the if statement inside of <code>getView()</code> becomes:</p> <pre><code>//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(); } </code></pre> <p>Now, we just have to update the holder.textView's text value, since it's already a reference to the recycled view! So our final adapter's code becomes:</p> <pre><code>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's text to the correct text. holder.textView.setText(item); //and return the row return row; } </code></pre> <p>And we're done!</p> <p>Some things to think about:</p> <ol> <li>How does this change if you have multiple views in a row that you want to change? As a challenge, make a ListView where each row has two <code>TextView</code> objects and an <code>ImageView</code></li> <li>When debugging your ListView, check a few things so that you can really see what's going on: <ol> <li>How many times ViewHolder's constructor is called.</li> <li>What the value of <code>holder.textView.getText()</code> is before you update it at the end of <code>getView()</code></li> </ol></li> </ol>
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload