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.
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 :
state_last
will be setstate_empty
will be set. If the group is expanded, the state state_expanded
will be setThe notable interfaces that are implemented by this class are given below :
This project consists of three classes.
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.
Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.
While we believe that this content benefits our community, we have not yet thoroughly reviewed it. If you have any suggestions for improvements, please let us know by clicking the “report an issue“ button at the bottom of the tutorial.
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
- harsha
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.
- Anupam Chugh
very neatly put! Thank you
- elham
Thanks elham
- Anupam Chugh
Very good tutorial
- jaya
Thanks Jaya
- Anupam Chugh
Thanks man, great details and greate code!
- vetdam
Thanks vetdam
- Anupam Chugh
Great tutorial! Thank you!
- Daniel Silva
Thanks Daniel
- Anupam Chugh
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???
- iamlazynic
Hi iamlazynic, Seems like exlTitle is empty. Try debugging by printing the fields/nested objects of exlTitle and exlDetail. Thanks
- Anupam Chugh
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.
- 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
- Anupam Chugh
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.
- sanyog
Store the ArrayLists as JSON Strings in the SQLite database. Convert them to ArrayList and vice-versa using Gson Library.
- Anupam Chugh
Hi i would like to ask, how can i put the arrow down in the right position? Thank you in advance for the help.
- Gielany Rosal
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
- Anupam Chugh
Hi, Nice tutorial How is the parent and child arranged or sorted out?
- Ryan
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
- Anupam Chugh