Android UI/X – don’t go overboard with your list views
Instead, I’m going to focus on the User Experience and ways to improve your lists.
To see a really well implemented ListView, take a look at the contact list on your phone. It is consistent, generic, and responsive. You can fling the list, and it glides smoothly. You can easily follow down the left side until you see the name you’re looking for. And you aren’t distracted by any rows that stand out like a sore thumb.
In this post, I want to talk about ways that you can make your ListView as good as the contact list.
Patterns for responsiveness:
First, let’s talk about some of the “crunchy stuff” – the code. There is a lot you can do to make your Java more efficient, but that’s not where the bang-for-the-buck is at. You really need to take advantage of some of the tools that Android has put in place. Namely, the convert view and the tags. Below are 2 patterns that you can use to improve the responsiveness of your list.
The recycling pattern makes use of the convert view. Inside your ListAdapter, there is a method called “getView()”. You may have noticed that one of the parameters in that method is called “convertView”. Making use of the convertView can significantly reduce the number of calls that are made to the LayoutInflater. The way that Android wants you to use the convertView, is to recycle the views contained in each row.
This is important because as the user scrolls, many rows are coming in and out of the screen. Because this is a list, the views should largely be the same components, just with different data.
Going back to our reference from above, the contact list simply has an image, name, and description. That translates to one ImageView component, and 2 TextView components. Each row is identical in structure.
Below is an example of how to use the convertView in your ListAdapter.
In this example, we only inflate the view if convertView is null. Otherwise, we simply set the text to “foo” without inflating.
Biggest bang for your buck, right there. A simple null check before inflating, and that’s it.
The holder pattern uses a class to hold references to your view. Just as inflating a view is expensive, so is calling “findViewById()” each time you want to access a widget in your view. This pattern is a solution to a common problem in most UI Toolkits…getting references to a widget.
The normal way that you would get/set data in a widget is by finding the view by ID, and then calling the appropriate method on that object – e.g. when we called setText(“foo”) in the above example. As you can imagine, in a list this gets very expensive.
Below is an example of the Holder pattern.
convertView = mInflater.inflate(R.layout.list_item_icon_text, null);
holder =new ViewHolder();
// Get the ViewHolder back to get fast access to the TextView
// and the ImageView.
holder =(ViewHolder) convertView.getTag();
// Bind the data efficiently with the holder.
So, in this pattern we are using tags, instead of IDs. The holder is responsible for holding the tag so that you only ever have to make 1 call to findViewById(). We still use the convertView as we were previously. And finally, you bind your data through the holder, instead of directly to a specific view object.
There’s always an exception…
Now, these patterns are great…but these patterns don’t fix everything. They only help address the responsive aspect to your list. In fact, they lose a lot of their impact if your view is not generic. If different rows of your list have different items in the view, then both of these patterns will only have minimal impacts because you won’t be able to recycle views (convertView will be null each time the view changes) and the holders will get updated regularly.
The biggest improvement you can do is to actually make your list generic and consistent. Below are some less crunchy ways to improve your design.
Generic views – defer secondary content
Use the fewest pieces of information to identify the item, and use a dialog to display more information if the user is interested. Defer all of the secondary content and additional actions to another screen or dialog. The more content you put in a view, the more work you are doing with binding and updating data.
Again, the contact list simply has an image, name, and description…that’s it.
Consistent – don’t conditionally modify the view
Unless you absolutely need to uniquely identify a row…don’t! Don’t worry about modifying the views in each row. It actually causes more problems for the user if items are not consistent in a list.
Adding small tweaks here and there add up quickly and become noisy distractions (e.g. color-coding backgrounds, re-arranging items, or adding/hiding items). The more inconsistent your views are, the more conditional display logic you’ll have to write. And the more conditional display logic you write, the slower your scroll speeds…not to mention the more confused your users will be.
The biggest increases you can get are actually in making your view hierarchy as simple as possible. It increases usability, and decreases complexity of your code. The next time you’re implementing a list, give it the “fling” test, and see if it is smooth and consistent as it scrolls by.
The above patterns and snippets are based off of the sample List14.java from the SDK samples.