Working with the ExpandableListView – Part 1

The ExpandableListView is a complex beast. While it’s pretty similar to ListView, there are plenty of little things about it and ListView worth writing about.

I’ll walk through the requirements I had to meet for one of my projects and I’ll explain things step by step. Part 1 will be about getting the list off the ground and Part 2 will be about wiring the pieces for interactivity / ui updates. I hope you find this useful! Let’s dig in!

My list items were pretty complex. They needed to display a progress bar when expanded, toggle an ImageView and update a TextView while music was playing. They also needed to look a certain way.  They needed to have a transparent background and customized dividers.
I solved all these problems by using the ExpandableListView, extending the BaseExpandableListAdapter and providing my own custom views. The views themselves simply inflate an xml layout and provide convenience methods for easy updating.

Wish i could show a screenshot, but until it releases, i cant :(

To get one to work you need to have these:
An ExpandableListView either defined in code or in XML. Mine is defined in XML and it looks like this


There’s a couple of things that warrant explanation in that definition. First is dealing with the background. To have a transparent background you need to do a couple more things than just saying android:background=”@null”. Doing this will drop the background but it will leave you with some shadowing at the top and bottom of the list. Google says they use this to increase performance. I don’t know all the details but I believe them. Fortunately you can turn those off by setting the cacheColorHint to transparent. android:cacheColorHint=”#00000000″. Presumably i’m making my list slower by turning this off. We’re gonna pretend that’s ok for now.

The last thing you’re left to deal with are the list item states. When you press an item android displays some default states. If you wish to change the states in any way, it’s easy to do by assigning your own selector to the listSelector attribute. Here’s what that looks like:

Here’s what my xml selector looks like. More info on xml selectors here.



	
	
	

Here my selector just points to a drawable shape with a transparent background.
The last thing to mention is that I do not want the child dividers in my list, so i get rid of them with this.
android:childDivider=”#00000000″. That was easy!

Now on to the good stuff!
Let’s get down and dirty with our Expandable list view.
Here’s what happens onCreate.. It’s simple, really.

protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.music_player_layout);
		mAdapter = new ExpadableAdapter();
		list = (ExpandableListView) findViewById(R.id.tracklist);
		list.setAdapter(mAdapter);
		list.setGroupIndicator(null);
	}

After creating a new instance of my ExpandableAdapter (more on that later) and getting a reference to the ExpandableListView, all we need to do is set the ExpandableAdapter to the list and let android do it’s thing. By default, android places a little icon in each GroupView it creates. I didn’t want that so i turned it off with list.setGroupIndicator(null);.

Before I move on to talking about the adapter, I want to cover another default behavior that I needed to override. By default the ExpandableListView will keep all the items you click on expanded. I needed only the Item I just clicked on to be expanded. My first attempt failed, and I still don’t understand why.

My first attempt was to try to collapse all groups after a group was clicked.

list.setOnGroupClickListener(new OnGroupClickListener() {

			public boolean onGroupClick(ExpandableListView parent, View v,
					int groupPosition, long id) {

				int len = mAdapter.getGroupCount();
				for (int i = 0; i < len; i++) {
					if (i != groupPosition) {
						parent.collapseGroup(i);
					}
				}

				return false;
			}

		});

	}

For some reason this would throw the error below when I wanted to expand an item with a higher index than the currently expanded item. It was weird. This sort of thing has worked hundreds of times before. Why not now? I’m still fairly new to Android so I don’t have the appetite to find out.
The error:
03-15 17:18:13.090: ERROR/AndroidRuntime(16202): FATAL EXCEPTION: main

03-15 17:18:13.090: ERROR/AndroidRuntime(16202): java.lang.IndexOutOfBoundsException: Invalid index 1, size is 0

After some head-scratching and some time in stackoverflow, I thought hey! maybe it’s some execution thing where the collapse happens… umm i actually don’t know why. But I saw that you can listen for when Groups are expanded so I decided to try that. and………..

list.setOnGroupExpandListener(new OnGroupExpandListener() {

			public void onGroupExpand(int groupPosition) {
				int len = mAdapter.getGroupCount();
				for (int i = 0; i < len; i++) {
					if (i != groupPosition) {
						list.collapseGroup(i);
					}
				}
			}
		});

It worked! same code Listening at a different point. Interesting.

As a note, I opted to loop through rather than just remembering because it avoids problems with fast clicks.

Now for the last bit of fun. I create my custom views in my ExpadableAdapter class by making use of the

getChildView() and getGroupView() methods.
Before you go further, If you’re not familiar with how Adapters work I recommend you take a look at the android documentation.
Also check out the API demo. Don’t worry, i’ll wait…. … … … … …

Moving on then.

The two methods that create the views for the groups and the children views, are set to simply return a new instance of my custom view classes.

public View getGroupView(int groupPosition, boolean isExpanded,
				View convertView, ViewGroup parent) {
			MusicTrack track = new MusicTrack(getBaseContext());
			return track;
		}

And similarly for my childViews:

public View getChildView(int groupPosition, int childPosition,
				boolean isLastChild, View convertView, ViewGroup parent) {
			MusicTrackBar bar = new MusicTrackBar(getBaseContext());
			return bar;
		}

The custom views are very very simple. They both simply extend RelativeLayout and inflate an XML layout. I know there’s many ways to do this.

Here’s what my MusicTrack view looks like:

package com.vzw.anim;

import android.content.Context;
import android.view.LayoutInflater;
import android.widget.RelativeLayout;

public class MusicTrack extends RelativeLayout {

	public MusicTrack(Context context) {
		super(context);
		LayoutInflater vi = (LayoutInflater) context
				.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
		RelativeLayout rel = (RelativeLayout) vi.inflate(R.layout.list_item,
				this, true);
	}

}

Simple!
The layout XML is just as simple but i’ll show it just to be thorough.


Here’s all the code for the test activity: I cannot provide source at this time. Sorry.

package com.vzw.anim;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseExpandableListAdapter;
import android.widget.ExpandableListView;
import android.widget.ExpandableListView.OnGroupClickListener;
import android.widget.ExpandableListView.OnGroupExpandListener;

public class MusicActivity extends Activity {
	protected static final String TAG = "AnimTest";
	private ExpadableAdapter mAdapter;
	private ExpandableListView list;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.music_player_layout);
		mAdapter = new ExpadableAdapter();

		list = (ExpandableListView) findViewById(R.id.tracklist);
		list.setAdapter(mAdapter);
		list.setGroupIndicator(null);

		list.setOnGroupExpandListener(new OnGroupExpandListener() {

			public void onGroupExpand(int groupPosition) {
				int len = mAdapter.getGroupCount();
				for (int i = 0; i < len; i++) {
					if (i != groupPosition) {
						list.collapseGroup(i);
					}
				}
			}
		});

		list.setOnGroupClickListener(new OnGroupClickListener() {

			public boolean onGroupClick(ExpandableListView parent, View v,
					int groupPosition, long id) {
                                 //causes out of bounds exception. has to be done on Collapse for some reason.

				// int len = mAdapter.getGroupCount();
				// for (int i = 0; i < len; i++) {
				// if (i != groupPosition) {
				// parent.collapseGroup(i);
				// }
				// }

				return false;
			}

		});

	}

	public class ExpadableAdapter extends BaseExpandableListAdapter {

		private String[] groups = { "People Names", "Dog Names", "Cat Names",
				"Fish Names" };
		private String[][] children = { { "Arnold" }, { "Ace" }, { "Fluffy" },
				{ "Goldy" } };

		public Object getChild(int groupPosition, int childPosition) {
			return children[groupPosition][childPosition];
		}

		public long getChildId(int groupPosition, int childPosition) {
			return childPosition;
		}

		public View getChildView(int groupPosition, int childPosition,
				boolean isLastChild, View convertView, ViewGroup parent) {
			MusicTrackBar bar = new MusicTrackBar(getBaseContext());
			return bar;
		}

		public int getChildrenCount(int groupPosition) {
			return children[groupPosition].length;
		}

		public Object getGroup(int groupPosition) {
			return groups[groupPosition];
		}

		public int getGroupCount() {
			return groups.length;
		}

		public long getGroupId(int groupPosition) {
			return groupPosition;
		}

		public View getGroupView(int groupPosition, boolean isExpanded,
				View convertView, ViewGroup parent) {
			MusicTrack track = new MusicTrack(getBaseContext());
			return track;
		}

		public boolean hasStableIds() {
			return true;
		}

		public boolean isChildSelectable(int groupPosition, int childPosition) {
			return true;
		}
	}

}

Stay tuned for Part 2, where I wire this up for interactivity.
I hope this is helpful.

17 thoughts on “Working with the ExpandableListView – Part 1

  1. Great! I’m using (trying to implement) ExpandableList too, but for a different project.
    Anyway, it’s been about 2 months since this post. How goes the project associated with this?
    I just wanted to know, would it be more efficient to inflate the layout from xml (for each group/child) than create it programmatically?
    And the layout xml for your isn’t showing here.
    Thanks!

  2. Hi Moji,
    Thanks for posting. Your comment just motivated me to finally post part2 of this. Be on the lookout for this.
    You are correct, it would be more efficient to simply inflate the xml. You’ll notice that my custom view does that as well. I needed additional functionality associated with that view so that’s why I opted to create my own custom view.
    Thanks!

  3. I’m looking forward to part two! After hours of searching the web for a decent example of a custom expandable list view, I find this! Well done!

  4. The only post on the internet that actually helped me get an ExpandableListView working. Cheers! :)

  5. In fact, about the ‘ERROR/AndroidRuntime(16202): FATAL EXCEPTION: main’, it has a strange behavior.
    I’ve inspected the source code of ExpandableListView until to ExpandableListConnector and I’ve seen as unknown source code as it requires too much time to be grapsed.
    So I used OnGroupExpandListener. Just move your code from OnGroupClickListener to that.
    It works with no crashes.

  6. Hi,

    This sample code was very useful for me.
    But I’m struggle with one more thing i,e. I couldn’t disable the particular group view, when it has no child within this. Is their any possibilities to resolve this problem?. Please help me.

    Thanks,

    ThiloG.

  7. Hi, good work here. But where and what is this layout “R.layout.list_item”, your are using it on class MusicTrack, but I can’t see any code here, about that layout.

  8. Hi, good work here. But where and what is this layout “R.layout.list_item”, your are using it on class MusicTrack, but I can’t find any code here, about that layout.

  9. it is very good but how can we add checkbox with child of expandablelist view

  10. I need sourcecode of this can you provide the link to download to update in my expandible list view

  11. hi . i just wanna ask if what will be the code if i wanted to click the ‘child’ and will display text

  12. Great tutorial. I started with Objective C and now I am trying to do some stuff on android. I made a custom collapsable UITableView in iOS (equivalent to the ExpandableListView in android) and I needed help with the OnGroupCollapse bit. I did not realize that there was a callback method for this.

    Thanks!

Leave a Reply

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

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>