Welcome to Android ExpandableListView Example Tutorial. In this tutorial we’ll implement an ExpandableListView which is used to group list data by categories. It’s sort of menu and submenus in a Android ListView.
Table of Contents
Android ExpandableListView
Android ExpandableListView is a view that shows items in a vertically scrolling two-level list. It differs from a ListView by allowing two levels which are groups that can be easily expanded and collapsed by touching to view and their respective children items.
ExpandableListViewAdapter in android loads the data into the items associated with this view.
Following are some important methods that are used by this class :
- setChildIndicator(Drawable) : This is used to show an indicator besides each item representing the current state. If the child is the last child for a group, the state
state_last
will be set - setGroupIndicator(Drawable) : An indicator is drawn besides the group representing its state i.e. expanded or collapsed. If the group is empty, the state
state_empty
will be set. If the group is expanded, the statestate_expanded
will be set - getGroupView() : It returns view for the list group header
- getChildView() : It returns view for list child item
The notable interfaces that are implemented by this class are given below :
- ExpandableListView.OnChildClickListener : This is overridden to implement the callback method that’s invoked when a child in the expanded list is clicked
- ExpandableListView.OnGroupClickListener : This is overridden to implement the callback method that’s invoked when a group header in the expanded list is clicked
- ExpandableListView.OnGroupCollapseListener : It is used for notifying when a group is collapsed
- ExpandableListView.OnGroupExpandListener : It is used to notify when a group is expanded
Android ExpandableListView Project Structure
This project consists of three classes.
- A MainActivity that shows the layout with the ExpandableListView
- An ExpandableListDataPump which represents a random data in a List and maps the child item data to the respective group headers using a HashMap
- A CustomExpandableListAdapter which provides the MainActivity with the data from the ExpandableListDataPump class/li>
Android ExpandableListView Code
The activity_main.xml layout consists of an ExpandableListView in a RelativeLayout as shown below :
activity_main.xml
<RelativeLayout xmlns:android="https://schemas.android.com/apk/res/android"
xmlns:tools="https://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingBottom="@dimen/activity_vertical_margin"
tools:context=".MainActivity">
<ExpandableListView
android:id="@+id/expandableListView"
android:layout_height="match_parent"
android:layout_width="match_parent"
android:indicatorLeft="?android:attr/expandableListPreferredItemIndicatorLeft"
android:divider="@android:color/darker_gray"
android:dividerHeight="0.5dp" />
</RelativeLayout>
The android:indicatorLeft is the left bound for an items indicator.
Note : We cannot use the value wrap_content for the android:layout_height attribute of the ExpandableListView
in XML unless the parent’s size is strictly specified
The layout of the group header of each individual list is given below :
list_group.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="https://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/listTitle"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:paddingLeft="?android:attr/expandableListPreferredItemPaddingLeft"
android:textColor="@android:color/black"
android:paddingTop="10dp"
android:paddingBottom="10dp" />
</LinearLayout>
The layout row of the child items is given below :
list_item.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="https://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/expandedListItem"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:paddingLeft="?android:attr/expandableListPreferredChildPaddingLeft"
android:paddingTop="10dp"
android:paddingBottom="10dp" />
</LinearLayout>
The ExpandableListDataPump
class is defined as below:
package com.journaldev.expandablelistview;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
public class ExpandableListDataPump {
public static HashMap<String, List<String>> getData() {
HashMap<String, List<String>> expandableListDetail = new HashMap<String, List<String>>();
List<String> cricket = new ArrayList<String>();
cricket.add("India");
cricket.add("Pakistan");
cricket.add("Australia");
cricket.add("England");
cricket.add("South Africa");
List<String> football = new ArrayList<String>();
football.add("Brazil");
football.add("Spain");
football.add("Germany");
football.add("Netherlands");
football.add("Italy");
List<String> basketball = new ArrayList<String>();
basketball.add("United States");
basketball.add("Spain");
basketball.add("Argentina");
basketball.add("France");
basketball.add("Russia");
expandableListDetail.put("CRICKET TEAMS", cricket);
expandableListDetail.put("FOOTBALL TEAMS", football);
expandableListDetail.put("BASKETBALL TEAMS", basketball);
return expandableListDetail;
}
}
In the above code the expandableListDetail
object is used to map the group header strings to their respective children using an ArrayList of Strings.
CustomExpandableListAdapter.java
package com.journaldev.expandablelistview;
import java.util.HashMap;
import java.util.List;
import android.content.Context;
import android.graphics.Typeface;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseExpandableListAdapter;
import android.widget.TextView;
public class CustomExpandableListAdapter extends BaseExpandableListAdapter {
private Context context;
private List<String> expandableListTitle;
private HashMap<String, List<String>> expandableListDetail;
public CustomExpandableListAdapter(Context context, List<String> expandableListTitle,
HashMap<String, List<String>> expandableListDetail) {
this.context = context;
this.expandableListTitle = expandableListTitle;
this.expandableListDetail = expandableListDetail;
}
@Override
public Object getChild(int listPosition, int expandedListPosition) {
return this.expandableListDetail.get(this.expandableListTitle.get(listPosition))
.get(expandedListPosition);
}
@Override
public long getChildId(int listPosition, int expandedListPosition) {
return expandedListPosition;
}
@Override
public View getChildView(int listPosition, final int expandedListPosition,
boolean isLastChild, View convertView, ViewGroup parent) {
final String expandedListText = (String) getChild(listPosition, expandedListPosition);
if (convertView == null) {
LayoutInflater layoutInflater = (LayoutInflater) this.context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = layoutInflater.inflate(R.layout.list_item, null);
}
TextView expandedListTextView = (TextView) convertView
.findViewById(R.id.expandedListItem);
expandedListTextView.setText(expandedListText);
return convertView;
}
@Override
public int getChildrenCount(int listPosition) {
return this.expandableListDetail.get(this.expandableListTitle.get(listPosition))
.size();
}
@Override
public Object getGroup(int listPosition) {
return this.expandableListTitle.get(listPosition);
}
@Override
public int getGroupCount() {
return this.expandableListTitle.size();
}
@Override
public long getGroupId(int listPosition) {
return listPosition;
}
@Override
public View getGroupView(int listPosition, boolean isExpanded,
View convertView, ViewGroup parent) {
String listTitle = (String) getGroup(listPosition);
if (convertView == null) {
LayoutInflater layoutInflater = (LayoutInflater) this.context.
getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = layoutInflater.inflate(R.layout.list_group, null);
}
TextView listTitleTextView = (TextView) convertView
.findViewById(R.id.listTitle);
listTitleTextView.setTypeface(null, Typeface.BOLD);
listTitleTextView.setText(listTitle);
return convertView;
}
@Override
public boolean hasStableIds() {
return false;
}
@Override
public boolean isChildSelectable(int listPosition, int expandedListPosition) {
return true;
}
}
This class extends BaseExpandableListAdapter and it overrides the methods in the base class to provide the view for the ExpandableListView. getView() fills in the data into the item’s view with the given index.
MainActivity.java
package com.journaldev.expandablelistview;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.ExpandableListAdapter;
import android.widget.ExpandableListView;
import android.widget.Toast;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
public class MainActivity extends AppCompatActivity {
ExpandableListView expandableListView;
ExpandableListAdapter expandableListAdapter;
List<String> expandableListTitle;
HashMap<String, List<String>> expandableListDetail;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
expandableListView = (ExpandableListView) findViewById(R.id.expandableListView);
expandableListDetail = ExpandableListDataPump.getData();
expandableListTitle = new ArrayList<String>(expandableListDetail.keySet());
expandableListAdapter = new CustomExpandableListAdapter(this, expandableListTitle, expandableListDetail);
expandableListView.setAdapter(expandableListAdapter);
expandableListView.setOnGroupExpandListener(new ExpandableListView.OnGroupExpandListener() {
@Override
public void onGroupExpand(int groupPosition) {
Toast.makeText(getApplicationContext(),
expandableListTitle.get(groupPosition) + " List Expanded.",
Toast.LENGTH_SHORT).show();
}
});
expandableListView.setOnGroupCollapseListener(new ExpandableListView.OnGroupCollapseListener() {
@Override
public void onGroupCollapse(int groupPosition) {
Toast.makeText(getApplicationContext(),
expandableListTitle.get(groupPosition) + " List Collapsed.",
Toast.LENGTH_SHORT).show();
}
});
expandableListView.setOnChildClickListener(new ExpandableListView.OnChildClickListener() {
@Override
public boolean onChildClick(ExpandableListView parent, View v,
int groupPosition, int childPosition, long id) {
Toast.makeText(
getApplicationContext(),
expandableListTitle.get(groupPosition)
+ " -> "
+ expandableListDetail.get(
expandableListTitle.get(groupPosition)).get(
childPosition), Toast.LENGTH_SHORT
).show();
return false;
}
});
}
}
In the above code we’ve implemented all the interfaces that were discussed before. For the sake of simplicity, we’ll only display a Toast with the name of the item or the state of the group for every click. But these can be easily modified to perform any other operations.
Below is our app with android expandable list view in action.
Note: ExpandableListViews are scrollable by default.
This brings an end to Android ExpandableListView tutorial. You can download the final Android ExpandableListView Project from the below link.
how can i get child view on click of expandable group
Can this be adapted to show in a Simple Dialog Builder model?
sir
Thanks for this tutorial. i have one doubt, how can i create dynamic expandable listview. the groups and childrens data from json data(webservices). in my code two arrays getting “category and items” dynamicaly view category and corresponding items ,how can done?
Hi,
is it possible to shift the position of items?
Like according to your example I want to change the team position as first to display basketball then wants to display cricket after I want to display football Team.
if we can do this then how could we do this? can you please explain?
This was very helpful and easy to understand. Thank you.
This is really great job. THANKS
Hi , how can i populate Expendable listView using Json from Volley and mysql (I have 2 tables Article and comments ), otherly how can i use dynamic data
thank you
Store your JSON response data into HashMap.
The response you get is key-value pair and HashMap stores the key-value data.
When you get respose pass that data to ExpandableListViewDataPump class.
So it will be dynamic my dear.
Hello Team,
Thank you for the detailed information.
A very nice example for a beginner.
How can i add an expandable list on top of another expandable list
I have implemented an ExpandableListView inside a ConstraintView, with some buttons above it.
If the ExpandabeListView expands off the bottom screen, it does not scroll!
Is there a solution to this?
Do any one know that how to paginate expandablelistview please help me.
SIr…Great work..I have got without any errors..I have a doubt,….can we do the same with fragment…eg,,….can we do these expandable listview completely in the fragment page
Thank you so much for this post… Keep blogging please don’t stop
Hi, This tutorial useful.
But i need this expandablelistview as expand and collapse.
That is if child layout expanded ,then all the parent layouts should collapse.
And need to add data from API.
Do this on the ExpandableListView instance:
Hi Anupam,
Great tutorial, thanks.
Please, could you let me know how I can add a search feature to this and when I select a child object I would like to open another activity using string.xml
how can i add another one team. its not working
Thank you very much!
Hey friend, thanks a lot by the great explanation and the sources.
You’ve saved a soul 🙂
Tks again!
Glad this helped you out Daniel.
Always happy to save fellow souls 🙂
it’s not working… please help in resolving below exception.
Returns error:
java.lang.RuntimeException: Unable to start activity ComponentInfo{com.bjobs.vvtcards/com.bjobs.vvtcards.MainActivity}: java.lang.NullPointerException: Attempt to invoke virtual method ‘void android.widget.ExpandableListView.setAdapter(android.widget.ExpandableListAdapter)’ on a null object reference
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2778)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2856)
at android.app.ActivityThread.-wrap11(Unknown Source:0)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1589)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loop(Looper.java:164)
at android.app.ActivityThread.main(ActivityThread.java:6494)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:438)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:807)
Caused by: java.lang.NullPointerException: Attempt to invoke virtual method ‘void android.widget.ExpandableListView.setAdapter(android.widget.ExpandableListAdapter)’ on a null object reference
Thanks in advance, you rock by the way. I like all your articles.
You’re trying to set the adapter on a null ExpandableListView widget. Please ensure that you have set the ExpandableListView in the xml layout and have initialised it in your activity properly using findViewById.
Thanks a lot for the example. It was a great help for my design.
Thanks a lot for your article. But I’ve a question.
How to add a child after last child position of group? This child was used to add a custom view?
Hi,
On the line “expandableListDetail = ExpandableListDataPump.getData();” the getData part returns an error saying
“non-static method “getData” cannot be referenced from a static context”. What happened here and how to resolve? Cheers.
I figured it out. You have a minor error (?) in your code.
In MainActivity, change:
ExpandableListAdapter expandableListAdapter;
to:
CustomExpandableListAdapter expandableListAdapter;
I believe the way you have it, the methods of the BaseExpandableListAdapter are unavailable.
Thanks!
Hi John,
The CustomExpandableListAdapter extends ExpandableListAdapter. So the notifyDataSetChanged method can be overridden in the CustomExpandableListAdapter.java class and would be available then.
Well thought though!
Thanks
Let’s say you wanted to call notifyDataSetChanged() in MainActivity. How would you go about doing that? The method isn’t available on expandableListAdapter in MainActivity.
Thanks.
Thanks! Very useful. Implemented in a fragment.
How to add searc functionality for the above list view
Add a SearchView widget in your layout.
You need to implement Filterable interface in your adapter class and create an inner class that extends Filter class and create it’s instance. Put your filtering logic in
performFiltering
method inside the inner class.For more details refer this:
https://www.journaldev.com/12478/android-searchview-example-tutorial
Great tutorial Anupam Chugh!
However, I advise the use of “LinkedHashMap” in stead of “HashMap” because using HashMap does not have the notion of order. When I ran my app that has an FAQ page, the order of the questions kept changing. Sometimes, I’d find the first question appearing last and vice-versa. Basically, the questions would be randomly placed within the list.
LinkedHashMap is an implementation of Map that guarantees iteration order, so now the questions are ordered according to the order I have set, with the first question appearing first, the second question next etc etc.
Thanks, Juliet. Yes, LinkedHashMap should be used when the insertion order needs to be preserved.
Hi Juliet,
I tried adding additional lists and was wondering how I could have them be in order as this was important for my project. This piece of advice helped me out a lot because I was struggling while trying to find a solution. I thought I had to change something in the way they were added to the expandablelistview. This solution worked perfectly and was easy to implement.
Thanks!
How can I turn the countries into buttons for another screen
Using Intents
Inspection ‘Mismatched query and update of collection’ options
how can I resolve this
Hi,
great tutorial I like it I want do similar thing with some modification like parent should be edit text and relevant child also should be edit text and toggle button how do implement this please suggest me in this regards.
Thanks & Regards,
Mangesh
thank you very much for the tutorial.
I needed something like this for my school project but by click on any child I want to navigate to the next page of my app – so to say -that does new set of tasks. how to add this feature?I tried “performItemClick” but no achievement!
onChildItemClickListener. That’s the method where you add your Intent for the next Activity.
Ultimately this is helpful; however, sample code is NOT a tutorial. The reader is left to try to reverse engineer why the API classes were chosen/required. It implies that the only way to fill an ExpandableListView is by using and extending BaseExpandableListAdapter and feeding it hashmaps and lists of strings; without teaching the readers the HOW and WHAT of the API classes, they are ill-equipped to create a more general solution on their own – which is what the point of a tutorial is.
Hi Jack,
To know more about the APIs you can always refer to the Google Documentation : https://developer.android.com/reference/android/widget/ExpandableListView.html
Cheers
Nobody should ever be using code this bad. A nine year old could have written this. You should be ashamed to publish this.
Doubt it. Being so judgmental does not earn you any kudos. You could offer suggestions for improvement to the code, if you are capable of that.
Thanks Bruce for fitting reply. Some guys don’t understand that we can’t put production quality code in a tutorial. We can do that but then the tutorial will be 5 times bigger and loose focus and it’s sole purpose.
Really nice it works Perfectly fine Thank You
Glad it did
Great tutorial Sir. Thank you
I need to set the text to the right .. how to that ?
Use the gravity attribute of the TextView
Thanks. Very helpful tutorial.
Thanks Andy
Hi Anupam,
Thanks, it’s a great tutorial, but I would like to implement this on Fragment instead of an Activity. There must be a slight change to this because I’ve tried and only one Group title appear and when it was expanded, no children titles were shown.
Use a FrameLayout in your MainActivity.java to instantiate the MainFragment(or whatever you name). Put the code for expandableListView in the layout for the MainFragment.
Hi, using this is it possible to get the position of a child item? Thank you!
Hi eleanor,
Position of child item can be determined from the onChildClick method.
It’s defined as :
public boolean onChildClick(ExpandableListView parent, View v, int groupPosition, int childPosition, long id)
Thanks
Excuse me, when I run my app, it crashes, the Exception I got when i run it is this: “java.lang.NullPointerException: Attempt to invoke virtual method ‘void android.widget.ExpandableListView.setAdapter(android.widget.ExpandableListAdapter)’ on a null object reference”
Make sure you’ve hooked the ExpandableListView instance to its xml counterpart before you write the method setAdapter.
Great Tutorial can you make a tutorial using expandable list view with edit text in child list
Hi Siva,
Why do you need that?
Hi,
Thanks for the tutorial!
I’m making a FAQ list for my app, i am wondering is it possible to load the parent (the question as title) and child (answer as content) string from string array list in the xml (which will be much easier to add new item without editing the java)? Maybe a array for question and another one for answer, and after loaded into the list, the answer will show in their respective question.
Like this in the xml:
Q1
Q2
…
A1
A2
…
Thanks.
Yes it is possible
Very easy and fine tutorial for beginners. Thank You..
Glad that you liked it
Hello,
Great Article and thanks for the help.
Well I implemented this in my project but am having the Expandable List inverted along with the child items. Can you suggest
Use LinkedListHashMap instead. It’ll preserve your order.
Hi,
Nice tutorial
How is the parent and child arranged or sorted out?
Hi Ryan,
The group header and child are sorted out in this case since an ArrayList displays data in chronological order. A HashMap is used to map the respective group header with its child.
Thanks
Anupam
Hi i would like to ask, how can i put the arrow down in the right position? Thank you in advance for the help.
Hi Gielany Rosal,
Add an ImageView to the right of the group title in the group header view and set the image resource. (Download it from google or use android:src=”@android:drawable/arrow_up_float”, android:src=”@android:drawable/arrow_down_float”). Toggle the image when the group is expanded or collapsed.
Hide the left indicator set it as null as shown below.
Alternative Solution:
Override the method onWindowFocusChanged as shown below.
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
if(android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.JELLY_BEAN_MR2) {
expandableListView.setIndicatorBounds(expandableListView.getWidth()-40,expandableListView.getWidth());
} else {
expandableListView.setIndicatorBoundsRelative(expandableListView.getWidth()-40,expandableListView.getWidth());
}
}
In this case the arrow would look distorted.
I'd say go for the first solution.
Thanks
Anupam
Hello freinds,
My quesry is can we add data in expandable list view dynamically through sqlite database, if yes, olease help me with the same. Thanks.
Store the ArrayLists as JSON Strings in the SQLite database. Convert them to ArrayList and vice-versa using Gson Library.
Thank you Very much, How can do this for populating array from JSON API, i don’t have idea how to populate array values from the JSON. Kindly give some references. Thanks in Advance Mohanraj.
Hi Mohanraj,
Try iterating over the JSONArray using a for loop. Use ArrayLists to populate data from JSONObjects. Essentially you can keep an ArrayList to store the header values, another to store child data for each header. Use a HashMap to store key value pairs. Ideally, the group header name should be the key and the child ArrayList should be it’s mapped value.
Thanks
Anupam
Great tutorial!
I am try these and encountered a problem.
exlView = (ExpandableListView) findViewById(R.id.pages_android);
exlDetail = ExlDataPump.getData();
exlTitle = new ArrayList(exlDetail.keySet());
exlAdapter = new ExlAdapter(this, exlTitle, exlDetail);
exlView.setAdapter(exlAdapter);
There brought me an error : invoke setAdapter on null object. What’s wrong???
Hi iamlazynic,
Seems like exlTitle is empty. Try debugging by printing the fields/nested objects of exlTitle and exlDetail.
Thanks
Great tutorial! Thank you!
Thanks Daniel
Thanks man, great details and greate code!
Thanks vetdam
Very good tutorial
Thanks Jaya
very neatly put! Thank you
Thanks elham
how to start an activity if group have no child items and when i clicked on group and if another group having childs if click on group open childs and based on child clik navigate to other views android
Hi Harsha,
I understand that you are looking for a way to start an activity by clicking a group that has no children. Right?
There is a setOnGroupClickListener() method that you can invoke and possibly define the intent to the next activity.
Thanks.