October 20, 2014

Return of the Fancy ListViews

BuildingDroidsLong-time followers of this column may recall a blog post series I did back in the summer of 2008, covering “fancy ListViews“. This referred to being able to take control over what rows look like in a ListView, so you can tailor each to appear exactly as you want. Today, let’s revisit the topic, in light of some changes in recent Android SDK releases.

You may recall that one of the techniques those earlier posts covered was the use of a “holder” or “wrapper”. This cached the results of findViewById() lookups in a custom class, an instance of which was stored in each row View’s “tag” via setTag(). When a row is recycled, you can grab the wrapper via getTag(), then use the wrapper to access the individual widgets inside of the row (e.g., ImageView, TextView). On a list that may be extensively scrolled, this saves hundreds, if not thousands, of findViewById() calls, which are not exactly cheap.

Android 1.6 quietly added a pair of new methods to the View class which can simplify the creation of fancy lists like the ones shown in this chapter. Specifically, there are two new versions of getTag() and setTag() that take an identifier along with their object. These let you eliminate the wrapper class while still reaping much of its benefits.

Below, you will see some code from a sample project that uses these new getTag()/setTag() methods to cache findViewById() lookups:

[java]
package com.commonsware.android.fancylists.eight;

import android.app.Activity;
import android.os.Bundle;
import android.app.ListActivity;
import android.view.View;
import android.view.ViewGroup;
import android.view.LayoutInflater;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;

public class SelfWrapperDemo extends ListActivity {
TextView selection;
String[] items={"lorem", "ipsum", "dolor", "sit", "amet",
"consectetuer", "adipiscing", "elit", "morbi", "vel",
"ligula", "vitae", "arcu", "aliquet", "mollis",
"etiam", "vel", "erat", "placerat", "ante",
"porttitor", "sodales", "pellentesque", "augue",
"purus"};

@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
setContentView(R.layout.main);
setListAdapter(new IconicAdapter());
selection=(TextView)findViewById(R.id.selection);
}

private String getModel(int position) {
return(((IconicAdapter)getListAdapter()).getItem(position));
}

public void onListItemClick(ListView parent, View v,
int position, long id) {
selection.setText(getModel(position));
}

class IconicAdapter extends ArrayAdapter<String> {
IconicAdapter() {
super(SelfWrapperDemo.this, R.layout.row, items);
}

public View getView(int position, View convertView,
ViewGroup parent) {
View row=convertView;
if (row==null) {
LayoutInflater inflater=getLayoutInflater();
row=inflater.inflate(R.layout.row, parent, false);
row.setTag(R.id.label, row.findViewById(R.id.label));
row.setTag(R.id.icon, row.findViewById(R.id.icon));
}

TextView label=(TextView)row.getTag(R.id.label);
ImageView icon=(ImageView)row.getTag(R.id.icon);
label.setText(getModel(position));

if (getModel(position).length()>4) {
icon.setImageResource(R.drawable.delete);
}
else {
icon.setImageResource(R.drawable.ok);
}
return(row);
}
}
}
[/java]

When we inflate our row, we also pull out all widgets we might need to modify at runtime (R.id.label and R.id.icon) and stash those widgets as “tags” under their respective IDs. Then, regardless of row source, we pull out the widgets from those tags and update them. We gain the widget-level caching that a wrapper would provide us, but we do not need an actual wrapper class.

There are three caveats:

  • This technique does not offer any of the other potential benefits of a wrapper class, such as lazy-fetching
  • The index provided to getTag() and setTag() must be an identifier — you cannot just use 0, 1, etc. like you might in an ArrayList
  • This only works on Android 1.6…though there are tricks to get this same sort of logic working on Android 1.5 via reflection, a topic for another time

NOTE: this blog post is derived from a section in The Busy Coder’s Guide to Android Development, Version 2.8. A free excerpt from this book, covering all of the “fancy ListView” topics, can be downloaded as a PDF from the CommonsWare site.