HowTo: ListView, Adapter, getView and different list items’ layouts in one ListView
I was surprised that getViewTypeCount() is so rarely overrided (codesearch). If you are an expert in this – this post is not for you:-)
ListView and Adapter Basics
How it works:
- ListView asks adapter “give me a view” (getView) for each item of the list
- A new View is returned and displayed
Next question – what if we have one billion items? Create new view for each item? The answer is NO:-) Android caches views for you.
There’s a component in Android called “Recycler”. I drawed a picture based on Romain Guy presentation at Google IO ’09.
- If you have 1 billion items – there are only visible items in the memory + view in recycler.
- ListView asks for a view type1 first time (getView) x visible items. convertView is null in getView – you create new view of type1 and return it.
- ListView asks for a view type1 when one item1 is outside of the window and new item the same type is comming from the bottom. convertView is not null = item1. You should just set new data and return convertView back. No need to create view again.
Let’s write a simple code and put System.out to the getView:
public class MultipleItemsList extends ListActivity { private MyCustomAdapter mAdapter; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mAdapter = new MyCustomAdapter(); for (int i = 0; i < 50; i++) { mAdapter.addItem("item " + i); } setListAdapter(mAdapter); } private class MyCustomAdapter extends BaseAdapter { private ArrayList mData = new ArrayList(); private LayoutInflater mInflater; public MyCustomAdapter() { mInflater = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE); } public void addItem(final String item) { mData.add(item); notifyDataSetChanged(); } @Override public int getCount() { return mData.size(); } @Override public String getItem(int position) { return mData.get(position); } @Override public long getItemId(int position) { return position; } @Override public View getView(int position, View convertView, ViewGroup parent) { System.out.println("getView " + position + " " + convertView); ViewHolder holder = null; if (convertView == null) { convertView = mInflater.inflate(R.layout.item1, null); holder = new ViewHolder(); holder.textView = (TextView)convertView.findViewById(R.id.text); convertView.setTag(holder); } else { holder = (ViewHolder)convertView.getTag(); } holder.textView.setText(mData.get(position)); return convertView; } } public static class ViewHolder { public TextView textView; } }
Run the program and see what happens:
getView was called 9 times. convertView is null for all visible items
02-05 13:47:32.559: INFO/System.out(947): getView 0 null 02-05 13:47:32.570: INFO/System.out(947): getView 1 null 02-05 13:47:32.589: INFO/System.out(947): getView 2 null 02-05 13:47:32.599: INFO/System.out(947): getView 3 null 02-05 13:47:32.619: INFO/System.out(947): getView 4 null 02-05 13:47:32.629: INFO/System.out(947): getView 5 null 02-05 13:47:32.708: INFO/System.out(947): getView 6 null 02-05 13:47:32.719: INFO/System.out(947): getView 7 null 02-05 13:47:32.729: INFO/System.out(947): getView 8 null
Then scroll the list slightly down (until item 10 appears):
convertView is still null because there is still no view in the recycler (border of item1 is still visible at the top:))
02-05 13:48:25.169: INFO/System.out(947): getView 9 null
Let’s scroll list a little more:
Bingo! convertView is not null: item1 goes off the screen directly to the Recycler and item11 is created based on item1.
02-05 13:48:42.879: INFO/System.out(947): getView 10 android.widget.LinearLayout@437430f8
scroll more just to check what hapens:
02-05 14:01:31.069: INFO/System.out(947): getView 11 android.widget.LinearLayout@437447d0 02-05 14:01:31.142: INFO/System.out(947): getView 12 android.widget.LinearLayout@43744ff8 02-05 14:01:31.279: INFO/System.out(947): getView 13 android.widget.LinearLayout@43743fa8 02-05 14:01:31.350: INFO/System.out(947): getView 14 android.widget.LinearLayout@43745820 02-05 14:01:31.429: INFO/System.out(947): getView 15 android.widget.LinearLayout@43746048 02-05 14:01:31.550: INFO/System.out(947): getView 16 android.widget.LinearLayout@43746870 02-05 14:01:31.669: INFO/System.out(947): getView 17 android.widget.LinearLayout@43747098 02-05 14:01:31.839: INFO/System.out(947): getView 18 android.widget.LinearLayout@437478c0 02-05 14:03:30.900: INFO/System.out(947): getView 19 android.widget.LinearLayout@43748df0 02-05 14:03:32.069: INFO/System.out(947): getView 20 android.widget.LinearLayout@437430f8
convertView is not null as we expected. After item11 goes off the screen, it view (@437430f8) comes as convertView for item 21. simple.
Different list items’ layouts
Let’s move to the “more complicated” example. How about to add separator somewhere to the list.
You should do the following:
- Override getViewTypeCount() – return how many different view layouts you have
- Override getItemViewType(int) – return correct view type id by position
- Create correct convertView (depending on view item type) in getView
Simple, isn’t it? Code snippet:
public class MultipleItemsList extends ListActivity { private MyCustomAdapter mAdapter; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mAdapter = new MyCustomAdapter(); for (int i = 1; i < 50; i++) { mAdapter.addItem("item " + i); if (i % 4 == 0) { mAdapter.addSeparatorItem("separator " + i); } } setListAdapter(mAdapter); } private class MyCustomAdapter extends BaseAdapter { private static final int TYPE_ITEM = 0; private static final int TYPE_SEPARATOR = 1; private static final int TYPE_MAX_COUNT = TYPE_SEPARATOR + 1; private ArrayList mData = new ArrayList(); private LayoutInflater mInflater; private TreeSet mSeparatorsSet = new TreeSet(); public MyCustomAdapter() { mInflater = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE); } public void addItem(final String item) { mData.add(item); notifyDataSetChanged(); } public void addSeparatorItem(final String item) { mData.add(item); // save separator position mSeparatorsSet.add(mData.size() - 1); notifyDataSetChanged(); } @Override public int getItemViewType(int position) { return mSeparatorsSet.contains(position) ? TYPE_SEPARATOR : TYPE_ITEM; } @Override public int getViewTypeCount() { return TYPE_MAX_COUNT; } @Override public int getCount() { return mData.size(); } @Override public String getItem(int position) { return mData.get(position); } @Override public long getItemId(int position) { return position; } @Override public View getView(int position, View convertView, ViewGroup parent) { ViewHolder holder = null; int type = getItemViewType(position); System.out.println("getView " + position + " " + convertView + " type = " + type); if (convertView == null) { holder = new ViewHolder(); switch (type) { case TYPE_ITEM: convertView = mInflater.inflate(R.layout.item1, null); holder.textView = (TextView)convertView.findViewById(R.id.text); break; case TYPE_SEPARATOR: convertView = mInflater.inflate(R.layout.item2, null); holder.textView = (TextView)convertView.findViewById(R.id.textSeparator); break; } convertView.setTag(holder); } else { holder = (ViewHolder)convertView.getTag(); } holder.textView.setText(mData.get(position)); return convertView; } } public static class ViewHolder { public TextView textView; } }
Let’s run what we wrote. Yo will see separators after each 4-th item in the list.
In the log – nothing exceptional – all convertView is null for visible items both types.
02-05 15:19:03.080: INFO/System.out(1035): getView 0 null type = 0 02-05 15:19:03.112: INFO/System.out(1035): getView 1 null type = 0 02-05 15:19:03.130: INFO/System.out(1035): getView 2 null type = 0 02-05 15:19:03.141: INFO/System.out(1035): getView 3 null type = 0 02-05 15:19:03.160: INFO/System.out(1035): getView 4 null type = 1 02-05 15:19:03.170: INFO/System.out(1035): getView 5 null type = 0 02-05 15:19:03.180: INFO/System.out(1035): getView 6 null type = 0 02-05 15:19:03.190: INFO/System.out(1035): getView 7 null type = 0 02-05 15:19:03.210: INFO/System.out(1035): getView 8 null type = 0 02-05 15:19:03.210: INFO/System.out(1035): getView 9 null type = 1
Scroll list and see what happens:
02-05 15:19:54.160: INFO/System.out(1035): getView 10 null type = 0 02-05 15:19:57.440: INFO/System.out(1035): getView 11 android.widget.LinearLayout@43744528 type = 0 02-05 15:20:01.310: INFO/System.out(1035): getView 12 android.widget.LinearLayout@43744eb0 type = 0 02-05 15:20:01.880: INFO/System.out(1035): getView 13 android.widget.LinearLayout@437456d8 type = 0 02-05 15:20:02.869: INFO/System.out(1035): getView 14 null type = 1 02-05 15:20:06.489: INFO/System.out(1035): getView 15 android.widget.LinearLayout@43745f00 type = 0 02-05 15:20:07.749: INFO/System.out(1035): getView 16 android.widget.LinearLayout@43747170 type = 0 02-05 15:20:10.250: INFO/System.out(1035): getView 17 android.widget.LinearLayout@43747998 type = 0 02-05 15:20:11.661: INFO/System.out(1035): getView 18 android.widget.LinearLayout@437481c0 type = 0 02-05 15:20:13.180: INFO/System.out(1035): getView 19 android.widget.LinearLayout@437468a0 type = 1 02-05 15:20:16.900: INFO/System.out(1035): getView 20 android.widget.LinearLayout@437489e8 type = 0 02-05 15:20:25.690: INFO/System.out(1035): getView 21 android.widget.LinearLayout@4374a8d8 type = 0
convertView is null for separator view type until first separator is visible. When it goes off the screen – view also comes to the Recycler and convertView comes to play.
MultipleItemsList.zip – source code
enjoy)
//DL







You rock! Thanks for the tutorial.
great tutorial
The separator item is still clickable, right ? did you success to make it act as a true separator like in a PreferenceScreen ?
override isEnabled(int position) to make it not clickable
Thanks for the tutorial, I need a slightly different version. I so hope you will help me out. Day three here on this and my head is flat in the front from pounding it on my desk. So, here goes…
I need the simplist way to achieve a multicolumn ListView where I have the linearlayout file with two TextViews. The first is file name, the second is file creation date, time. The second should be disabled so as not to to be able to click on it.
Now, I cannot for the life of me figure out how to get my product to consume (without error) the following:
At best I get the “no [type of file string here] to show” message back, thanks to the way the xml for the activity UI was setup.
So, how can I achieve this. I’m using ArrayList now. One of the important lines is as follows:
ArrayAdapter fileList = new ArrayAdapter(this, R.layout.row, item);
Thanks for any help the blog author can offer, or anyone else that sees this and takes a minute to dig me out of my coding hole.
OMG! I just changed one line of code, transposing the row.xml arg with my other xml (that is the row’s structure)!
ArrayAdapter fileList = new ArrayAdapter(this, R.layout.row, R.id.rowsetup, item);
So, all is well. Thanks again for anyone about to help or would have!
How do you go about add an item click listener to this. 7 days and I can’t figured it out.
I appreciate any help.
Thanks for the code
Great explications, and great tutorial !
nice tutorial…
keep it up..
It was really wonderful tutorial. Can you help me I have an issue regarding this….
I was implementing listview completely through code(no inflation through .xml). My list view has 6 visible items, so when my application launches in android 2.0, getView should be called 6 times. But when i try to print the position, it is giving as
0
1
2
3
4
5
6
7
0
1
2
3
4
5
6
7
0
1
2
3
4
5
0
1
2
3
4
5
6
7
0
1
2
3
4
5
6
7
I dont know why, you can check my code:
public View getView(int position, View convertView, ViewGroup parent)
{
System.out.println(position);
ListItemViewHolder holder;
if (convertView == null)
{
System.out.println(“new”);
holder = new ListItemViewHolder();
holder.listImageView = getImageView();
holder.textView = getTitle(position);
holder.listIconView = getAppLogoImage(holder.textView, position);
RelativeLayout listRelativeLayout = new RelativeLayout(mContext);
listRelativeLayout.addView(holder.listImageView,0);
listRelativeLayout.addView(holder.textView,1);
listRelativeLayout.addView(holder.listIconView,2);
convertView = (View) listRelativeLayout;
convertView.setTag(holder);
}
else
{
holder = (ListItemViewHolder) convertView.getTag();
}
holder.textView.setText(mAddApplications.get(position).getAppName());
if (position == 0 && getViewFlag == false)
{
lastChild = convertView;
currentChild = convertView;
holder.textView.setTextColor(Color.BLACK);
holder.listImageView.setBackgroundResource(R.drawable.widget_selected);
getViewFlag = true;
}
else
{
holder.textView.setTextColor(Color.WHITE);
holder.listImageView.setBackgroundResource(R.drawable.widget_default);
}
holder.listIconView.setImageDrawable(mAddApplications.get(position)
.getAppIconDrawable());
return convertView;
}
Note: It is printing
0
1
2
3
4
5
as expected in android 1.5
Is there any difference in implementation in android 2.0?
nice post, thanks!