You may have seen implementations of a standard ListView. And you also may have seen implementations of a standard ExpandableListView. However, recently my project required a screen to use aspects of both types of lists. The new screen was to have a collapsible list (ExpandableListview with 1 group) and a standard ListView beneath it. Complicating matters, these lists were required to deal with different sets of data displayed in different layouts.

It seems like there should be an easy solution to this problem, but there are a lot of moving pieces that need to align for this to work correctly. To set the tone for this blog, we will not be using an ExpandableListView and a ListView together. If these two views are used together, it can cause many issues, such as scrolling conflicts and header/footer problems. Both of these views are by default scrollable. Another thought may be to add one list to the other as a header/footer, so they can scroll as one view. This will cause drastic layout issues, that will prevent your lists from populating correctly.

Our solution is to modify the "out-of-the-box" ExpandableListView to fit our needs. To clearly explain everything, we'll work through an example app that displays travel information. The sample project is available at the end of this post. We will have two categories: past travel and future travel. Future travel plans matter more than past travel, so this information will always be shown in a non-collapsible list. Past travel information is mainly used for reference, so this information will be displayed in a collapsible list.

First, let's walk through the expandable list's adapter. The ExpandableListView has the ability to split its data set into categories, or "groups". Since we have different data sets, we will think of these as different groups. Each group contains children, which are the elements of the list. Our groups require different layouts for their children. This will be achieved in the getChildView method. The current child views layout is based on its view type, as seen below.

public View getChildView(int groupPosition, int childPosition,
			boolean isLastChild, View convertView, ViewGroup parent) {
 
		Type viewType = groups.get(groupPosition).type;
 
		if(viewType == Type.FUTURE) {
			//setup view for future travel plans
		} else {
			//setup view for past travel plans
		}
 return view;
}

Now let's take a look at our activity. The "out-of-the-box" version of the ExpandableListView will show each groups heading by default, meaning that the list elements will be hidden until the header is tapped to expand the view. If we want one of these groups to act as a ListView and always show its elements, we will have to change some things. Our first order of business is to extend the group that is meant to be a ListView; in this case it will be our future travel plans. We can do this with the following bit of code:

expList.expandGroup(0);

In this bit of code, the "0" parameter refers to the group that is to be expanded. In this case it is the first group.

Next we will need to disable the collapsing feature. The code to accomplish this task is as follows:

expList.setOnGroupClickListener(new ExpandableListView.OnGroupClickListener() {
 
		@Override
		public boolean onGroupClick(ExpandableListView parent, View v,
				int groupPosition, long id) {
			if(groupPosition == 0){
				return true;
			} else {
				return false;
			}
		}
});

In the snippet above, we override the onGroupClick method. If true, the ExpandableListView thinks that the ability to expand and collapse has been handled. If false, the ExpandableListView thinks expanding/collapsing has not been handled, and will take care of the action. We must be careful here because we only want to disable the collapsing and expanding of one of our groups. A simple conditional will do the trick. So in our conditional, if our future travel plans group is tapped (group 0), then we will return true, meaning the expansion/collapsing will not occur. And if our past travel plans group is tapped, then we will return false, letting the view handle the expansion/collapsing of the group.

Our final order of business is the group indicators. The standard ExpandableListView comes with a "group indicator" located on the left side of the group header. This is of course referring to the arrow that indicates the state of the group (collapsed or expanded). If we were to publish our app now, our users would think that the future travel plans group is collapsible. Even though we have disabled that feature, the arrow is still there, suggesting that this is possible. We need to remove that arrow from that group as our final task. This will make our users think that it is a standard non-collapsible ListView. To do this requires a few steps. First, we must remove our standard group indicator:

expList.setGroupIndicator(null);

The snippet above removes the group indicator for all the groups, but that is ok, because this allows us to customize the group header a bit more. Next we need to add an ImageView to our group header layout that acts as our new group indicator, like so:

<> 
 android:id="@+id/expandableIcon"
 android:layout_alignParentRight="true"
 android:layout_centerVertical="true"
 android:src="@android:drawable/arrow_down_float"
 android:visibility="invisible"/>

These are the relevant attributes of the view. This view can be placed anywhere in our group header layout. In this case, I am aligning the image to the right side of the layout and centered vertically. We must also specify a default image for the view by assigning a value to the "src" attribute. I am using an android standard drawable. And lastly, set the visibility of the view to invisible.

After adding the ImageView to our group header layout, we must handle the image in our adapter. The getGroupView method is where we'll be handling this image. We have two things to complete here. One is the visibility of the ImageView, and the other being the state of the image (arrow pointing up or down). The following code shows how to accomplish this:

if(groupPosition != 0){
	int imageResourceId = isExpanded ? android.R.drawable.arrow_up_float : android.R.drawable.arrow_down_float;
	image.setImageResource(imageResourceId);
 
	image.setVisibility(View.VISIBLE);
} else {
	image.setVisibility(View.INVISIBLE);
}

As you can see, if we are not in our future travel plans group (group 0), we will change the image to reflect the state of the group and then set the visibility to visible. If we are in our future travel plans group, we set the image to invisible. Once this is put into place, our screen is complete.

This fulfills our requirements. We've successfully created a screen with, what appears to be, a ListView and an ExpandableListView working together. Since we are using a single list, the layout scrolls as one. Additionally, if we needed to add more information to this screen, we can always add that as a header or footer to the single expandable list. Through customization of a single ExpandableListView we were able to create this:

In summary, using ListViews and ExpandableListViews in the same layout can cause issues and is not a very wise choice. Instead, an easier approach is to customize a single ExpandableListView to fit your needs.