Skip to content

Lots of Lists: Part 2, List with custom objects and adapter

NewsEntryAdapter

Create a new class that extends ArrayAdapter<NewsEntry>. You’ll need to provide a constructor; we’ve selected the (Context, int) signature for our purposes. Additionally we want to override the getView() method to customize how we’ll build each view.

Here is the final class we built. Don’t worry, we’ll dissect this class to understand everything it’s doing.

The constructor we’ve specified below receives a Context and an int.

public NewsEntryAdapter(final Context context, final int newsItemLayoutResource) {
	super(context, 0);
	this.newsItemLayoutResource = newsItemLayoutResource;
}

We must specify the Context to our parent class, ArrayAdapter, and the newsItemLayoutResource tells us which layout we should use for each news item. We’ve already created one at res/layout/news_entry_list_item.xml, so the resource we’ll pass into this constructor will be R.layout.news_entry_list_item.

getView()

Here we’re overriding the getView() method of ArrayAdapter.

@Override
public View getView(final int position, final View convertView, final ViewGroup parent) {
 
	// We need to get the best view (re-used if possible) and then
	// retrieve its corresponding ViewHolder, which optimizes lookup efficiency
	final View view = getWorkingView(convertView);
	final ViewHolder viewHolder = getViewHolder(view);
	final NewsEntry entry = getItem(position);
 
	// Setting the title view is straightforward
	viewHolder.titleView.setText(entry.getTitle());
 
	// Setting the subTitle view requires a tiny bit of formatting
	final String formattedSubTitle = String.format("By %s on %s",
		entry.getAuthor(),
		DateFormat.getDateInstance(DateFormat.SHORT).format(entry.getPostDate())
	);
 
	viewHolder.subTitleView.setText(formattedSubTitle);
 
	// Setting image view is also simple
	viewHolder.imageView.setImageResource(entry.getIcon());
 
	return view;
}

There’s a lot going on here, so let’s break it down. First we’ll talk about the method’s arguments (specifically convertView), the workingView, and then viewHolder.

The primary functionality of the adapter starts at getView. This is what is called each time the ListView needs something to display.

The position argument tells us which item in the array of objects we added is being rendered (which can be very useful to know.)

The convertView argument is our possibly-recycled view. If this is non-null, then this is a view that’s no longer visible and should be used to conserve view objects. If it is null, then we’ll go ahead and create a new view. The resulting view (either reused or newly created) is what we call the working view.

The parent argument is a reference to the parent view containing ours (ListView.) It’s not necessary for our purposes here.

The following lines determine our working view and retrieve a ViewHolder plus the corresponding NewsEntry object.

	// We need to get the best view (re-used if possible) and then
	// retrieve its corresponding ViewHolder, which optimizes lookup efficiency
	final View view = getWorkingView(convertView);
	final ViewHolder viewHolder = getViewHolder(view);
	final NewsEntry entry = getItem(position);

We’ll take a look at getWorkingView() and getViewHolder() soon enough. The getItem() simply returns to us the NewsEntry we’re currently trying to render for the ListView to display.

The remaining lines perform the actual decoration of the view:

	// Setting the title view is straightforward
	viewHolder.titleView.setText(entry.getTitle());
 
	// Setting the subTitle view requires a tiny bit of formatting
	final String formattedSubTitle = String.format("By %s on %s",
		entry.getAuthor(),
		DateFormat.getDateInstance(DateFormat.SHORT).format(entry.getPostDate())
	);
 
	viewHolder.subTitleView.setText(formattedSubTitle);
 
	// Setting image view is also simple
	viewHolder.imageView.setImageResource(entry.getIcon());
 
	return view;

The title is set simply to the NewsEntry title. The subtitle requires a little bit of date formatting, but isn’t altogether hard. Like the title, the image is set simply to the NewsEntryIcon.

Finally we return the view at the end of getView(), which will cause it to be recycled later on when possible.

getWorkingView()

Here we define our own method getWorkingView().

private View getWorkingView(final View convertView) {
	// The workingView is basically just the convertView re-used if possible
	// or inflated new if not possible
	View workingView = null;
 
	if(null == convertView) {
		final Context context = getContext();
		final LayoutInflater inflater = (LayoutInflater)context.getSystemService
	      (Context.LAYOUT_INFLATER_SERVICE);
 
		workingView = inflater.inflate(newsItemLayoutResource, null);
	} else {
		workingView = convertView;
	}
 
	return workingView;
}

The working view is the result of whether we have a recycled view or forced to create a new one. This should be pretty straightforward.

getViewHolder()

Here we define our own method getViewHolder().

private ViewHolder getViewHolder(final View workingView) {
	// The viewHolder allows us to avoid re-looking up view references
	// Since views are recycled, these references will never change
	final Object tag = workingView.getTag();
	ViewHolder viewHolder = null;
 
	if(null == tag || !(tag instanceof ViewHolder)) {
		viewHolder = new ViewHolder();
 
		viewHolder.titleView = (TextView) workingView.findViewById(R.id.news_entry_title);
		viewHolder.subTitleView = (TextView) workingView.findViewById(R.id.news_entry_subtitle);
		viewHolder.imageView = (ImageView) workingView.findViewById(R.id.news_entry_icon);
 
		workingView.setTag(viewHolder);
 
	} else {
		viewHolder = (ViewHolder) tag;
	}
 
	return viewHolder;
}
 
/**
 * ViewHolder allows us to avoid re-looking up view references
 * Since views are recycled, these references will never change
 */
private static class ViewHolder {
	public TextView titleView;
	public TextView subTitleView;
	public ImageView imageView;
}

The ViewHolder is a custom class defined out of convenience, efficiency, and some type safety. The primary benefit of the ViewHolder relies on the fact that, when stored as a view’s tag, it is recycled along with the view itself. A view can store any object, known as its tag, for convenient referencing. In our case, we will store an object that already has direct references to the subviews within the item layout. This avoids having to re-lookup (through findViewById()) each subview each time it’s re-drawn. As the number of getView() calls increase, the number of times findViewById() is invoked remains the same at a certain point. We can also cast it to our specific types of views (TextView, ImageView) and never have to cast it again.

Layouts, Objects, Adapter Complete

Now that we’re done with our layouts, objects, and adapter, it’s time to make use of the new tool we have. On the next and final page, we’ll put it all together and conclude the tutorial.

{ 5 } Comments

  1. zAo | July 1, 2012 at 9:02 am | Permalink

    Hi, should
    android:text=”@string/hello” />
    Not be
    android:text=”@string/hello_world” />

    Thanks

  2. Gav Newalkar | July 2, 2012 at 3:25 am | Permalink

    Excellent tutorial. Thanks for the explanations, this is awesome.

  3. Jonathan | February 18, 2014 at 10:07 am | Permalink

    Google discourages the use of getter/setters (as you used in the NewsEntry class) for performance reasons.

    http://developer.android.com/training/articles/perf-tips.html#GettersSetters

  4. Oscar | May 6, 2014 at 4:19 am | Permalink

    Thank you!!

  5. Sheridan | September 27, 2014 at 8:16 am | Permalink

    Thanks for the info!

{ 1 } Trackback

  1. […] managed to implement a great listview that I found here http://www.learn-android.com/2011/11/22/lots-of-lists-custom-adapter/comment-page-1/ but I can’t seem to add an onclicklistener I just want to be able to do an action when I […]

Post a Comment

Your email is never published nor shared. Required fields are marked *