How to properly use the viewHolder pattern on a listView adapter with multiple viewTypes.

The viewHolder pattern is great for making your listViews feel more responsive. Examples abound.
It seems to work great up until you start doing more complex things with it. Things can get a bit tricky once you have more than viewType that your adapter needs to provide.
Consider this code:



package com.oner.test;

import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ListView;
import android.widget.TextView;

public class Adapter_testActivity extends Activity {
	private ListView mListView;

	/** Called when the activity is first created. */
	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);

		mListView = (ListView) findViewById(android.R.id.list);
		mListView.setAdapter(new AdapterTest((Context) this));
	}

	private class AdapterTest extends BaseAdapter {
		String[] items = { "a", "b", "c", "d", "e", "a", "b", "c", "d", "e",
				"a", "b", "c", "d", "e", "a", "b", "c", "d", "e", "a", "b",
				"c", "d", "e", "a", "b", "c", "d", "e", "a", "b", "c", "d", "e" };
		private Context mContext;
		private LayoutInflater inflater;
		

		public AdapterTest(final Context context) {
			mContext = context;
			inflater = LayoutInflater.from(mContext);
		}

		@Override
		public int getCount() {
			return items.length;
		}

		@Override
		public Object getItem(int position) {
			return items[position];
		}

		@Override
		public long getItemId(int position) {
			return position;
		}

		@Override
		public View getView(int position, View convertView, ViewGroup parent) {
			View v = convertView;
			ViewHolder holder;

			int type = getItemViewType(position);
			Log.d("Adapter test","setting type:"+type);
			if (v == null) {

				holder = new ViewHolder();
				if (type == 0) {
					v = inflater.inflate(R.layout.item_type_one, null);
					holder.txtOne = (TextView) v.findViewById(R.id.txt_one);
				}else if(type ==1){
					v = inflater.inflate(R.layout.item_type_two, null);
					holder.txtTwo = (TextView) v.findViewById(R.id.txt_two);
				}
				else {
					v = inflater.inflate(R.layout.item_type_three, null);
					holder.txtThree = (TextView) v.findViewById(R.id.txt_three);
				}
				holder.type = type;
				v.setTag(holder);
			} else {
				holder = (ViewHolder) v.getTag();
				Log.d("Adapter test", " holder ::" + holder);
			}
                        String item = (String) getItem(position);
			if (item != null) {
				if (type == 0) {
					holder.txtOne.setText(item);

				} else if(type ==1 ) {
					holder.txtTwo.setText(item);
				}else{
					holder.txtThree.setText(item);
				}

			}
			return v;

		}

		

		@Override
		public int getItemViewType(int position) {
                        String item = (String) getItem(position);
                        if(item.equals("a"))return =0;
			else if(item.equals("b"))return=1;
			else   (item.equals("c"))return=2;
		}

		@Override
		public int getViewTypeCount() {
			return 3;
		}

	}

	static class ViewHolder {
		public int type;
		TextView txtOne;
		TextView txtTwo;
		TextView txtThree;
		@Override
		public String toString() {
			return "ViewHolder [type=" + type + ", txtOne=" + txtOne
					+ ", txtTwo=" + txtTwo + ", txtThree=" + txtThree + "]";
		}

	}
}

For the purposes of this example the list items being inflated all contain the same TextView with just a different id. I’m sure you get the idea.
Here’s what the layout for the items looks like in case you’re feeling lazy.





The listView will keep a cache of every view type you give it. The trick to avoid problems is to make sure that you properly implement getViewItemType().

                @Override
		public int getItemViewType(int position) {
                        String item = (String) getItem(position);
                        if(item.equals("a"))return =0;
			else if(item.equals("b"))return=1;
			else   (item.equals("c"))return=2;
		}

A common mistake (my first mistake) was to implement that logic inside of getView instead of getItemViewType. Prior to getting one of the view types out of it’s cache the listView will call getItemViewType for its cache lookup, so you need to make sure it knows how to do so. Also make sure that your getItemId return something other than just 0. This will also make sure things work smoothly.

In my example I determine the item type by looking at the data.
After that it’s a simply matter of looking at the type to re-populate the viewHolder.

I hope this helps. Thanks to Simon and Noah for helping me work through the problems.

1 comment on “How to properly use the viewHolder pattern on a listView adapter with multiple viewTypes.”

  1. AndroidGameDeveloper

    Hi, i wanna ask a question. i am new on android. i can not find the listview decleration in xml file. where is it? pls answer me to the mail when u reply my comment.

Leave a Reply

Your email address will not be published. Required fields are marked *