November 26, 2014

Fancy ListViews, Part Three

In our last episode, we saw how we could save processing time — and, hence, battery life — by recycling existing row views in our fancy lists, simply by checking and reusing the convertView parameter passed into our getView().

In his comment to this series’ initial post, Romain Guy also left a bit of code that uses something called ViewHolder. Today, let’s take a look at this technique, with a twist.

The goal, once again, is to reduce the amount of work it takes to render one of our fancy lists, both so users get snappier response and so we don’t drain the battery so quickly. Recycling views saves us constructing new rows from whole cloth, particularly important when we are using ViewInflate and creating them from an XML layout.

Another somewhat expensive operation we do a lot with fancy views is call findViewById(). This dives into our inflated row and pulls out widgets by their assigned identifiers, so we can customize the widget contents (e.g., change the text of a TextView, change the icon in an ImageView). Since findViewById() can find widgets anywhere in the tree of children of the row’s root View, this could take a fair number of instructions to execute, particularly if we keep having to re-find widgets we had found once before.

In some GUI toolkits, this problem is avoided by having the composite Views, like our rows, be declared totally in program code (in this case, Java). Then, accessing individual widgets is merely the matter of calling a getter or accessing a field. And you can certainly do that with Android, but the code gets rather verbose. What would be nice is a way where we can still use the layout XML yet cache our row’s key child widgets so we only have to find them once.

That’s where ViewHolder comes in…or, in this post, a variation I’m calling ViewWrapper.

All View objects have getTag() and setTag() methods. These allow you to associate an arbitrary object with the widget. What the holder pattern does is use that “tag” to hold an object that, in turn, holds each of the child widgets of interest. By attaching that holder to the row View, every time we use the row, we already have access to the child widgets we care about, without having to call findViewById() again.

So, let’s take a look at one of these holder classes:

[sourcecode language=’java’]
class ViewWrapper {
View base;
TextView label=null;
ImageView icon=null;

ViewWrapper(View base) {
this.base=base;
}

TextView getLabel() {
if (label==null) {
label=(TextView)base.findViewById(R.id.label);
}

return(label);
}

ImageView getIcon() {
if (icon==null) {
icon=(ImageView)base.findViewById(R.id.icon);
}

return(icon);
}
}
[/sourcecode]

The main difference between ViewWrapper (shown above) and ViewHolder (shown in the comment to this post) is that ViewWrapper lazy-finds the child widgets. If you create a wrapper and never need a specific child, you never go through the findViewById() operation for it and never have to pay for those CPU cycles. On the flip side, ViewHolder has the child widgets in public fields, whereas ViewWrapper uses getter methods, so there are going to be situations where a ViewHolder approach is more efficient.

The holder pattern also:

  • Allows us to consolidate all our per-widget type casting in one place, rather than having to cast it everywhere we call findViewById()
  • Perhaps track other information about the row, such as state information we are not yet ready to “flush” to the underlying model — more on this in an upcoming post

Using ViewWrapper is a matter of creating an instance whenever we inflate a row and attaching said instance to the row View via setTag(), as shown in this rewrite of last post’s demo’s getView() method:

[sourcecode language=’java’]
public View getView(int position, View convertView, ViewGroup parent) {
View row=convertView;
ViewWrapper wrapper=null;

if (row==null) {
ViewInflate inflater=context.getViewInflate();

row=inflater.inflate(R.layout.row, null, null);
wrapper=new ViewWrapper(row);
row.setTag(wrapper);
}
else {
wrapper=(ViewWrapper)row.getTag();
}

wrapper.getLabel().setText(getModel(position));

if (getModel(position).length()>4) {
wrapper.getIcon().setImageResource(R.drawable.delete);
}

return(row);
}
[/sourcecode]

Just as we check convertView to see if it is null in order to create the row Views as needed, we also pull out (or create) the corresponding row’s ViewWrapper. Then, accessing the child widgets is merely a matter of calling their associated methods on the wrapper.

Many thanks to Romain Guy for pointing out this technique!

In our next episode, we’ll look at putting interactive widgets in a row, not just static ones like TextView and ImageView, and discuss making modifications to the state that supplies the data for the list itself.



  • Michal

    Hello

    First, Nice job with Fancy ListViews articles.

    Second, could you make also a short tutorial on changing the background image and text color of the selection in ListView.

    Thanks a lot.

    Michal

  • http://commonsware.com/Android/ Mark Murphy

    Thanks for the feedback! I’ll add that topic to the roster — it’ll probably be covered next week sometime.

  • Jim Schumacher

    I seem to have missed something.
    Where does ….getModel() comes from?
    (I can’t find it in the 0.9 SDK and didn’t see that you defined it.)

    Thanks!

  • http://commonsware.com Mark Murphy

    Sorry. That’s a helper method implemented on the activity. All it does is call getListAdapter(), then call getItem(position) on the adapter, to obtain the model.

  • Jim Schumacher

    The getItem(position) method of the Adapter class returns an object, and when I enter this code in the Eclipse IDE, it gives me an error and says that the setText method of TextView requires a Char Sequence, so it does not run.

    And if i need to wrap the object result into a char sequence to make it work, is this really more efficient, since you made the point above that we are trying to avoid too many calls to wrap things…

    Thanks,
    Jim

  • Jim Schumacher

    On another topic I am really confused by the following:
    The getView method returns “row” which is a view containing an ImageView and TextView, per the XML layout. But where did row become associated with the proper text and icon? I can see the following associations :

    if (row==null) {…
    row=inflater.inflate(R.layout.row, null, null);
    wrapper=new ViewWrapper(row);
    row.setTag(wrapper);
    }
    else {
    wrapper=(ViewWrapper)row.getTag(); } ….

    However, if the ImageView and TextView which are to be used with this instance of row were already set in those lines of code listed above, then what is the purpose of the subsequent line of code :
    ” wrapper.getLabel().setText(getModel(position)); ” ??
    Why are we setting the TextView again if it is already set? If the TextView was not set, how does the line of code ” wrapper.getLabel().setText(getModel(position)); ” which has NO reference to “row” actually change row?

    Thanks again.
    Jim

  • http://commonsware.com Mark Murphy

    The FancyLists series builds on previous posts in the series, which is why you’re only seeing changes in the source in this specific post, not the full set.

    In the particular case of this tutorial, back in Part One, we set up the Adapter as an ArrayAdapter holding Strings. Hence, the model returned by getItem() is a String and only needs to be cast.

    You may wish to visit http://commonsware.com/Android/, click on the Version 1.1 tab, and download the source code for Version 1.1 of my book, where you can find a completely worked-out example based on this post. Look for FancyLists/ViewWrapper in the ZIP file for the full Android project (manifest, Ant script, layouts, etc.). However, that source is for the M5 SDK — Version 1.2, supporting the 0.9 SDK, will hopefully be released on September 7th, so you may wish to check the above page next week if you don’t want to make the 0.9 changes yourself.

  • http://commonsware.com Mark Murphy

    If you examine the code for getView() shown in this tutorial, you see the following flow:

    — If there is no row to recycle, we create a new row, create a new ViewWrapper on the row, and have the row hold onto its wrapper via setTag()
    — If there is a row to recycle, we just pull out the wrapper via getTag()

    Then, to fill in the data for this specific position in the list, we use the wrapper to update the text. The icon, in this example, does not change row-to-row, IIRC.

    Even though the row Views may exist, due to recycling, the *text* in the row’s TextView is *not set*, or, more accurately, it is set to whatever text this row had been displaying before it scrolled off. That’s why we need to reset the row text. We use the ViewWrapper to avoid the comparatively-expensive findViewById() lookup to get our TextView out of our row View.

  • Jim Schumacher

    I think I understand better.
    Would it be true then, that the number of “rows” created would only be equal to the number of rows displayable on the screen at one time, since once they scroll off they are recycled?

    Thanks.

  • http://commonsware.com Mark Murphy

    Theoretically, yes.

    However, that’s really dependent upon how Android handles that sort of thing. I can imagine they might hold onto a view or two on either end in case somebody does a quick reversal of the scroll direction or something.

    So, it’s safer to say that, for a list of 100 data items, of which only 10 can be seen at any one time, the number of row Views that will be inflated should be much closer to 10 than 100.

  • Jim Schumacher

    Actually, I don’t really understand. (Let’s ignore the icon part).
    I see that the code changes the text of the ViewWrapper. But so what? For instance in this code:

    a=1
    b=a
    a=2

    b still equals 1, even though we changed a.

    That is what I don’t understand here. We change the text of ViewWrapper, but why does that change what is in the row. We never make a subsequent call to say, OK now make the text in the row equal to the text in the ViewWrapper.

    Does that make sense?

    Jim

  • http://commonsware.com Mark Murphy

    The ViewWrapper has no text.

    It has a TextView, the same TextView that is in the row. Look at the lazy-fetch done by ViewWrapper#getLabel().

    Hence, you never change the “text of ViewWrapper”. You change the TextView of the row…just by means of the ViewWrapper.

    In this trivial tutorial, we could dispense with the ViewWrapper, if the icon doesn’t change. We’d just put the TextView in the row’s tag (via setTag()) and retrieve the TextView when needed via the cheap getTag() vs. the expensive findViewById(). However, in a more complicated row, since setTag() only takes a single Object, we can’t store multiple child Views there without some sort of container. That could be a dumb container (e.g., HashMap). Or it could be a smart container, like ViewWrapper, that avoids even the first findViewById() per child view until it is absolutely needed.

  • Jim Schumacher

    Sorry, usually I am not so dense but this just doesn’t connect. Thank you for your explanations.

    I was using “text” to indicate the text which is displayed in the TextView (since that is the crux of the matter) .

    So the fundamental question is: what part of the code forever binds the TextView of “row” to be equal to the TextView of the wrapper, such that updating the wrapper updates the row?

    In any case, I am not familiar with “lazy-fetch” . I have seen “lazy-find” in your book and didn’t understand that either. Can you point me in a direction to learn about that, so I can further understand your comments above?

    Thanks again

  • http://commonsware.com Mark Murphy

    “what part of the code forever binds the TextView of “row” to be equal to the TextView of the wrapper, such that updating the wrapper updates the row?”

    That would be ViewWrapper#getLabel(), or getIcon() for the ImageView.

    “In any case, I am not familiar with “lazy-fetch” . I have seen “lazy-find” in your book and didn’t understand that either. Can you point me in a direction to learn about that, so I can further understand your comments above?”

    Short form: if you’re familiar with the memoize pattern, that’s what I mean by “lazy-*”.

    Look at ViewWrapper in the above code listing.

    When the ViewWrapper is first instantiated, its TextView label is null, and ImageView icon is null. Even the constructor does not change that — they are still null even when the constructor is complete.

    If this were a permanent state, of course, the class name would be OneCompletelyMoronicViewWrapper.

    However, there is an, um, method to the madness.

    Let’s focus on the ImageView. The ImageView is set, in the layout, to have some image. We only need to change this if the row is displaying a word whose length is greater than 4 — otherwise, we can leave the ImageView alone.

    Given that findViewById() is a comparatively expensive method to invoke, the notion of “lazy-fetch”, “lazy-find”, and similar concepts is that you try to delay the expensive stuff as long as possible, in hopes that, perhaps, you’ll never need it, then cache the result. I suppose a more modern term is “memoize”, though I tend to think of that more with mathematical calculations than a lookup like this — probably a mental block on my part.

    So, let’s suppose the user installs this app and fires up the activity. One of the rows that is displayed shows the word “amet”. Since “amet” does not have length greater than 4, we don’t call ViewWrapper#getIcon() when we create that row — we don’t need to change the default icon. This means we have saved one findViewById() call during the initial display of the list, making the list appear microseconds faster and use microamp-hours less battery life.

    In fact, if the user abandons the activity as being utterly pointless, we will *never* need to change the icon on that row, meaning we never need to call findViewById() to get the icon out of the row View.

    If, on the other hand, the user scrolls, odds are that, when the row is recycled, we’ll wind up with a word with five or more letters, and at that point — and only at that point — will we bother going through the expense of the findViewById() for that row’s icon.

    What the ViewWrapper pattern does is bundle up both Romain Guy’s cache-the-findViewById()-results trick from the ViewHolder he mentioned in a comment to the previous post, along with the notion of avoiding the findViewById() lookup at all if, it turns out, we never need it.

    And that’s the thinking behind that six-line getIcon() method.

    Now, for this particular example, we don’t gain a similar benefit in getLabel(), only because we’re always updating the label. One could argue, therefore, that perhaps we should just call findViewById() for the label in the constructor and save the is-null test on each getLabel() call. And, truth be told, that *would* be more efficient. However:

    1. Testing a variable for null-ness has *got* to be about as cheap an operation as there is in Dalvik.

    2. For the purposes of this example, I didn’t want to get into all those nuances

    3. You never know when the “business rules” of the GUI might change and, suddenly, you’re not updating the label every time either

    The first four paragraphs of the Overview to the Memoize entry in Wikipedia are fairly good, before the prose gets really dry:

    http://en.wikipedia.org/wiki/Memoize

  • Francisco

    Im having an issue with my implementation of the fancy list view. Im supposed to have some Separator in between the list view, this is a Tasks list view taht separates thje tasks by days so some of the Items should not be selectables, like the Today Header and the Day Header. I have almost the same implementation as you have in the Part three of this ListViews posts. My implementation Changes or removes the Icon depending on some rules and changes the lines of the textview and the color of the row, what i find really weird is that whenever i scroll the list down more items get removed. and color changed, like if it was starting to check the items[] array from the beggining but the rows were the same.

    For example, if we have 20 items in the array. item 1 and 7 gets painted, if you scroll down item 10 gets painted and item 17 gets painted. when we only are supposed to paint 1 and 7.
    Here’s my implementation of the adapter

    class IconicAdapter extends ArrayAdapter {
    Activity context;

    IconicAdapter(Activity context) {
    super(context, R.layout.list_row, items);

    this.context=context;
    }

    public boolean areAllItemsEnabled() {
    return false;
    }
    public boolean isEnabled(int position) {
    if (getItem(position).toString() == “Today” || getItem(position).toString() == “Wednesday Sept 14″)
    {
    return false;
    }
    else{
    return true;
    }

    }

    public View getView(int position, View convertView, ViewGroup parent) {

    View row = convertView;
    ViewWrapper wrapper=null;

    if (row == null)
    {

    LayoutInflater inflater = context.getLayoutInflater();
    row=inflater.inflate(R.layout.list_row, null);
    wrapper = new ViewWrapper(row);
    row.setTag(wrapper);
    }
    else{
    wrapper = (ViewWrapper)row.getTag();
    }

    TextView label=(TextView)row.findViewById(R.id.text1);
    TextView hour=(TextView)row.findViewById(R.id.text_hour);
    ImageView icon=(ImageView)row.findViewById(R.id.icon1);

    //Change the color and size of the Separator view
    if ((getItem(position).toString().compareTo(“Today”) == 0) || (getItem(position).toString().compareTo(“Wednesday Sept 14″) == 0))
    {
    row.setBackgroundColor(Color.parseColor(“#dde8f7″));
    label.setLines(1);

    icon.setVisibility(View.GONE);
    //wrapper.getHour().setText(“”);
    hour.setText(“”);
    }
    else{

    if (((String)getItem(position)).length()>4) {
    icon.setImageResource(R.drawable.today_checkmark);
    }
    hour.setText(“9:00″);
    }

    wrapper.getLabel().setText((String)getItem(position));

    return(row);
    }
    }

  • http://commonsware.com/Android/ Mark Murphy

    Ummmm…

    I’m afraid I don’t have a lot of time right now to review your code. Nothing leaps out at me as being the source of your trouble. I have two recommendations:

    1. Take a peek at Jeff Sharkey’s sample code for ListViews with separators, which sounds like it’s what you’re trying to do:

    http://www.jsharkey.org/blog/2008/08/18/separating-lists-with-headers-in-android-09/

    2. Post your question to the [android-developers] Google Group, where there are many more experts than follow the comments on this blog:

    http://groups.google.com/group/android-developers

  • tek

    Thank you thank you thank you! I was already recycling my views via convertView, however this _really_ sped things up!

    Happy coding everybody!

  • http://commonsware.com Mark Murphy

    A more up-to-date copy of the material from this blog post series can be found in this free excerpt from The Busy Coder’s Guide to Android Development.

  • Bob

    for God sake! please fix the source code on these ListViews posts!

  • http://www.jfseostudio.com get site traffic

    thanks for the post. this does help to reduce work on those pesky list views.

  • chaitanya

    Thanks for the wonderful article. It was well composed and really helpful. I wasn't aware about using ViewHolder to optimize the code. Just a question, in order to reuse the elements we find by findViewById, we store them in a ViewHolder by attaching a tag to them and then retrieving them by getting the tag associated with them

  • Alex

    Mark, thank you for this posts about ListViews!! It`s very usefull material for beginer like me))
    Firstly I`ve read all three aricles (part 1,2,3), but I couldn`t understand the last one. I`ve implemented ListView as discribed in th First Part, but I realized, that it works to slow!! And after using upgrades like ViewWrapper my ListView works much faster!! Thank YOU again!! =))

  • Shawn Mullen

    The Fancly List code does not seem to work. No matter what I try to do, I get an inflator error. Has anyone been able to get this to work?

  • Android

    Nice example, to get through with the listview, like this if we need to check with custom listview of need to go through with multiple listview in a activity means we can go with this link http://android-codes-examples.blogspot.com/2011/03/multiple-listview-and-custom-listview.html

  • http://www.chineselight.com/news/index.html Mini LED Light

    This website is awesome. I continuously encounter some thing new & different right here. Thank you for that data.

  • Pingback: URL

  • Pingback: туроператор по израилю

  • Pingback: http://www.youtube.com/watch?v=HFoxT7FWojc

  • Pingback: Creative Wordpress Themes: Photograpy and Portfolio

  • Pingback: Magento themes 2013

  • Pingback: http://answers.yahoo.com/question/index?qid=20130114012510AANz1V4

  • Pingback: Photography Wordpress Themes 2013

  • Pingback: bedene bathorse blackie

  • Pingback: mitsubishi klima,mitsubishi klima fiyatları,mitsubishi heavy

  • Pingback: free credit score online

  • Kumrat
  • Pingback: Lazy Load images on Listview in android(Beginner Level)? | Ask Programming & Technology