This is going to be a quick hit blog post about the Android
"ViewHolder" pattern. This is a pattern that many people seem to be at
least vaguely familiar with, but not very many actually use (based on
open source Android applications and examples/books, etc.).
First some background. Larry, Moe, and Curly were characters from . .
. ok, not that much background, but to really make use of the
ViewHolder with Android you do need to know what the ListView widget is (a helper for managing views of lists), and that ListViews and generally backed by Adapters
(adapters provide data for lists and build views for said data, they
can be made from lists of stuff in files, or from in memory arrays, or
from databases, and so on).
An example of a ListView in action is seen in the screen shot below.
This screen is taken from the DevNexus 2010
demo application I wrote for a conference I attended and spoke at this
year. This particular ListView is basically a navigable list of the
presentation abstracts and speakers. We will look at the code behind
this screen, complete with ViewHolder, coming up. First a bit more
background.
You are probably at least vaguely familiar with ListView/Adapter and
the related concepts if you have done any Android development at all.
ListView is *very* powerful and helpful widget. Nevertheless, ListView's
are often misused and abused because there are so many options and
settings and patterns that surround them. I don't have time to go into
great depth on ListView itself, but for the quick hit I promised I want
to address 2 key things to keep in mind when working with ListViews:
- Creating views is expensive, you don't want to inflate or manually
create new views for every view on the list. ListView can re-use views,
if you let it.
- Even if you are re-using views, you also don't want to find child
views by id (findViewById) every time, because that is expensive too.
I would say that 90% of Android examples or open source apps I see
(or developers I talk to) do the first item there properly. Depending on
the adapter in play they are re-using views. It's pretty simple, use
the "convertView" on simple adapters like ArrayAdapter (which we will
see coming up) and use the "newView" on more involved adapters like
CursorAdapter. I would also say that the same high percentage of
examples and open source apps *do not* use the ViewHolder to avoid trips
to findViewById. If your list is small, this might not be a big deal,
but if the list is large it can make a difference in terms of frame
rate, resource usage, and performance (which equals better experience
for the user, longer battery life, etc.). Also, it's a good habit to be
in even with small lists, more efficient code is better, especially on a
mobile platform.
So how do you do this stuff? Well, here is the Adapter from the aforementioned DevNexus 2010 application to demonstrate:
01 |
static class ViewHolder { |
02 |
private TextView view1; |
03 |
private TextView view2; |
06 |
private class PresentationAdapter extends ArrayAdapter<Presentation&g<wbr>t; { |
08 |
LayoutInflater vi = (LayoutInflater) PresentationList. this .getSyste<wbr>mService(Context.LAYOUT_INFLAT<wbr>ER_SERVICE); |
09 |
private final ArrayList<Presentation> presentations; |
11 |
public PresentationAdapter( final Context context, final int resId, final ArrayList<Presentation> presentations) { |
12 |
super (context, resId, presentations); |
13 |
this .presentations = presentations; |
17 |
public View getView( final int position, final View convertView, final ViewGroup parent) { |
18 |
// re-use the convertView, try not to recreate objects here or inflate every time (expensive) |
19 |
// also use the "tag" with the "view holder" pattern to avoid findViewById every time |
22 |
v = vi.inflate(R.layout.list_items<wbr>_item, null ); |
23 |
ViewHolder viewHolder = new ViewHolder(); |
24 |
viewHolder.view1 = (TextView) v.findViewById(R.id.list_item_<wbr>above); |
25 |
viewHolder.view2 = (TextView) v.findViewById(R.id.list_item_<wbr>below); |
29 |
Presentation p = presentations.get(position); |
31 |
ViewHolder viewHolder = (ViewHolder) v.getTag(); |
32 |
viewHolder.view1.setText(p.nam<wbr>e); |
33 |
viewHolder.view2.setText(p.spe<wbr>aker); |
The complete code for the DevNexus application (for context), is here.
What we are focusing on above is the Adapter to the ListView. Within it
we see that we are making use of the convertView if present to grab our
subsequent views, and to populate our own internal ViewHolder
representation. If the convertView isn't present, then we do the extra
work of inflating our views and building our ViewHolder.
The "convertView" is a convenient helper object provided to you by
the framework specifically for re-use (if the framework is ready to
provide it, the first item in the list will have a null convertView,
hence the check, and the work only if it is null). The framework knows
the views that are passing by, and as it recycles them it hands you one
that you can "convert."
So what is the ViewHolder? Well, that's really just a simple object
we have created (using a package scoped inner class) that stores child
views (though you could store anything there, that is all we need for
this example). View objects have a "Tag" property that you can use to
store any object. If you are getting recycled views anyway, it's very
easy and helpful to just stick child views you need *there* and retrieve
later, as opposed to calling "findViewById" to pull views from
resources for every view in the list. Really this just saves us the
findViewById calls. That might not seem like much, but again, in large
lists it can cut down on overhead.
A little further down in the code, after we get past creating the
convertView in the case that it's null, we see where we use the
ViewHolder to get a reference to our subsequent child views, and set
stuff (in this case just Strings, but could be anything).
This is a simple example, and I certainly won't promise that it's
perfect, but hopefully it does help to explain what ViewHolder is, and
why it's useful (and a little about re-cycling views in general).
Comments Alternative to the ViewHolder Submitted by Uncle Code Monkey (not verified) on Tue, 12/21/2010 - 10:54.
Thanks for writing on this subject. In the past, I did not bother
with caching the find results and after reading this blog, I decided to
give it a try and see if it improves the speed of my app any. I noticed
my app become more responsive and that's all the convincing I need to
keep it.
I do, however, have a different approach to caching than how you
wrote this article. Instead of keeping a separate class in which to
store the result of the various finds, I just store it directly into the
View.tag mechanism like so:
1 |
protected Object getViewHandle(View aParentView, int aViewToFind) { |
2 |
Object v = aParentView.getTag(aViewToFind<wbr>); |
4 |
v = aParentView.findViewById(aView<wbr>ToFind); |
5 |
aParentView.setTag(aViewToFind<wbr>,v); |
and then call it in my code by
1 |
TextView tv = (TextView)getViewHandle(itemVi<wbr>ew,R.id.some_text_view); |
This way I don't need to define classes for each kind of item view in
the list and makes the technique easily adaptable for different lists
without introducing another class that needs to be passed around to all
the code that works with such views.
Thanks for the inspiration!