Blog Archive

Android ExpandableListView Example

ExpandableListView is a type of view very much similar to a ListView but allows two levels. Basically items are grouped inside the list much like giving a category heading to a bunch of similar items and then group them by the category headings. Each group can be expanded or collapsed individually to show or hide its children. You can attach listeners to the ExpandableListView to listen for OnClick events on the Group or the individual children.

In this tutorial we learn to create an ExpandableListView simulating a Department Store where products belong to a certain department. We initially load our list with some data, then dynamically add items to the list by checking for new department and add a group otherwise just add the item to the existing department. We also associate listeners to alert which Department or Product was clicked on. We also learn how to programmatically expand and collapse all groups or just one at a time.

If you have worked with ListView then it's not that complicated either. All you have to do is extend the BaseExpandableListAdapter and then attach that to the ExpandableListView. In the BaseExpandableListAdapter we have to custom code 2 methods getChildView() and getGroupView() to display our group information and the child row information. You can create custom layouts for each one and then inflate them using LayoutInflater. Here is how ...

Android ExpandableListView Example
Android ExpandableListView Groups
Android Dynamic ExpandableListView

Source for Android Manifest

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.as400samplecode"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="14"
        android:targetSdkVersion="15" />

    <application
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name=".MainActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

Source for strings.xml

<resources>

    <string name="app_name">Expandable List View</string>
    <string name="add">Add</string>
    <string name="menu_settings">Settings</string>
    <string name="title_activity_main">MainActivity</string>
    <string name="product_hint">Enter Products for the Store</string>
 <string name="department_store">My Department Store &#8230;</string>
 
 <string-array name="dept_array">
        <item>Apparel</item>
        <item>Beauty</item>
        <item>Electronics</item>
        <item>Grocery</item>
        <item>Home Improvement</item>
        <item>Jewelry</item>
        <item>Pharmacy</item>
    </string-array>
    
 <color name="snow">#eee9e9</color>
 
</resources>

Source for styles.xml

<resources>

    <style name="AppTheme" parent="android:Theme.Holo.Light" />

</resources>

Main activity layout activity_main.xml

<?xml version="1.0" encoding="UTF-8"?>
<RelativeLayout xmlns:tools="http://schemas.android.com/tools"
 xmlns:android="http://schemas.android.com/apk/res/android"
 android:layout_width="match_parent" android:layout_height="match_parent"
 android:orientation="vertical">

 <Spinner android:id="@+id/department" android:layout_width="match_parent"
  android:layout_height="wrap_content" android:layout_alignParentLeft="true"
  android:layout_alignParentTop="true" android:textStyle="bold" />

 <Button android:id="@+id/add" android:layout_width="wrap_content"
  android:layout_height="wrap_content" android:layout_alignParentRight="true"
  android:layout_below="@id/department" android:text="@string/add" />

 <EditText android:id="@+id/product" android:layout_width="fill_parent"
  android:layout_height="wrap_content" android:layout_alignBaseline="@id/add"
  android:layout_alignParentLeft="true" android:layout_below="@id/department"
  android:layout_toLeftOf="@id/add" android:ems="10" android:hint="@string/product_hint"
  android:inputType="text" />

 <TextView android:id="@+id/textView1" android:layout_width="match_parent"
  android:layout_height="wrap_content" android:layout_alignParentLeft="true"
  android:layout_below="@id/product" android:layout_margin="5dp"
  android:background="@color/snow" android:padding="5dp"
  android:text="@string/department_store" android:textAppearance="?android:attr/textAppearanceMedium"
  android:textStyle="bold" />

 <ExpandableListView android:id="@+id/myList"
  android:layout_width="match_parent" android:layout_height="fill_parent"
  android:layout_below="@id/textView1" />

</RelativeLayout>

Group header layout group_heading.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="55dip"
    android:orientation="vertical" >

    <TextView
        android:id="@+id/heading"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:paddingLeft="35sp"
        android:textAppearance="?android:attr/textAppearanceLarge"
        android:textStyle="bold" />

</LinearLayout>

Child row layout child_row.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout android:layout_width="match_parent" android:layout_height="match_parent"
    android:orientation="vertical" xmlns:android="http://schemas.android.com/apk/res/android">

        <TextView
            android:id="@+id/sequence"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentLeft="true"
            android:layout_alignParentTop="true"
            android:paddingLeft="35sp"
            android:textAppearance="?android:attr/textAppearanceMedium" />

        <TextView
            android:id="@+id/childItem"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentTop="true"
            android:layout_toRightOf="@id/sequence"
            android:textAppearance="?android:attr/textAppearanceMedium" />
        
</RelativeLayout>

Source for Child object DetailInfo.java

package com.as400samplecode;

public class DetailInfo {
 
 private String sequence = "";
 private String name = "";
 
 public String getSequence() {
  return sequence;
 }
 public void setSequence(String sequence) {
  this.sequence = sequence;
 }
 public String getName() {
  return name;
 }
 public void setName(String name) {
  this.name = name;
 }
 
}

Source for Group object HeaderInfo.java

package com.as400samplecode;

import java.util.ArrayList;

public class HeaderInfo {
 
 private String name;
 private ArrayList<DetailInfo> productList = new ArrayList<DetailInfo>();;
 
 public String getName() {
  return name;
 }
 public void setName(String name) {
  this.name = name;
 }
 public ArrayList<DetailInfo> getProductList() {
  return productList;
 }
 public void setProductList(ArrayList<DetailInfo> productList) {
  this.productList = productList;
 }

}

Source for BaseExpandableListAdapter MyListAdapter.java

package com.as400samplecode;

import java.util.ArrayList;

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseExpandableListAdapter;
import android.widget.TextView;

public class MyListAdapter extends BaseExpandableListAdapter {

 private Context context;
 private ArrayList<HeaderInfo> deptList;
 
 public MyListAdapter(Context context, ArrayList<HeaderInfo> deptList) {
  this.context = context;
  this.deptList = deptList;
 }
 
 @Override
 public Object getChild(int groupPosition, int childPosition) {
  ArrayList<DetailInfo> productList = deptList.get(groupPosition).getProductList();
  return productList.get(childPosition);
 }

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

 @Override
 public View getChildView(int groupPosition, int childPosition, boolean isLastChild, 
   View view, ViewGroup parent) {
  
  DetailInfo detailInfo = (DetailInfo) getChild(groupPosition, childPosition);
  if (view == null) {
   LayoutInflater infalInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
   view = infalInflater.inflate(R.layout.child_row, null);
  }
  
  TextView sequence = (TextView) view.findViewById(R.id.sequence);
  sequence.setText(detailInfo.getSequence().trim() + ") ");
  TextView childItem = (TextView) view.findViewById(R.id.childItem);
  childItem.setText(detailInfo.getName().trim());
  
  return view;
 }

 @Override
 public int getChildrenCount(int groupPosition) {
  
  ArrayList<DetailInfo> productList = deptList.get(groupPosition).getProductList();
  return productList.size();

 }

 @Override
 public Object getGroup(int groupPosition) {
  return deptList.get(groupPosition);
 }

 @Override
 public int getGroupCount() {
  return deptList.size();
 }

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

 @Override
 public View getGroupView(int groupPosition, boolean isLastChild, View view,
   ViewGroup parent) {
  
  HeaderInfo headerInfo = (HeaderInfo) getGroup(groupPosition);
  if (view == null) {
   LayoutInflater inf = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
   view = inf.inflate(R.layout.group_heading, null);
  }
  
  TextView heading = (TextView) view.findViewById(R.id.heading);
  heading.setText(headerInfo.getName().trim());
  
  return view;
 }

 @Override
 public boolean hasStableIds() {
  return true;
 }

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

}

Source for the main Android activity MainActivity.java

package com.as400samplecode;

import java.util.ArrayList;
import java.util.LinkedHashMap;

import android.os.Bundle;
import android.app.Activity;
import android.view.Menu;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ExpandableListView;
import android.widget.Spinner;
import android.widget.Toast;
import android.widget.ExpandableListView.OnChildClickListener;
import android.widget.ExpandableListView.OnGroupClickListener;

public class MainActivity extends Activity implements OnClickListener{

 private LinkedHashMap<String, HeaderInfo> myDepartments = new LinkedHashMap<String, HeaderInfo>();
 private ArrayList<HeaderInfo> deptList = new ArrayList<HeaderInfo>();

 private MyListAdapter listAdapter;
 private ExpandableListView myList;

 @Override
 public void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);

  Spinner spinner = (Spinner) findViewById(R.id.department);
  // Create an ArrayAdapter using the string array and a default spinner layout
  ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(this,
    R.array.dept_array, android.R.layout.simple_spinner_item);
  // Specify the layout to use when the list of choices appears
  adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
  // Apply the adapter to the spinner
  spinner.setAdapter(adapter);

  //Just add some data to start with
  loadData();

  //get reference to the ExpandableListView
  myList = (ExpandableListView) findViewById(R.id.myList);
  //create the adapter by passing your ArrayList data
  listAdapter = new MyListAdapter(MainActivity.this, deptList);
  //attach the adapter to the list
  myList.setAdapter(listAdapter);

  //expand all Groups
  expandAll();

  //add new item to the List
  Button add = (Button) findViewById(R.id.add);
  add.setOnClickListener(this);
  
  //listener for child row click
  myList.setOnChildClickListener(myListItemClicked);
  //listener for group heading click
        myList.setOnGroupClickListener(myListGroupClicked);
        
        
 }

 public void onClick(View v) {

  switch (v.getId()) {

  //add entry to the List
  case R.id.add:

   Spinner spinner = (Spinner) findViewById(R.id.department);
   String department = spinner.getSelectedItem().toString();
   EditText editText = (EditText) findViewById(R.id.product);
   String product = editText.getText().toString();
   editText.setText("");
   
   //add a new item to the list
   int groupPosition = addProduct(department,product);
   //notify the list so that changes can take effect
   listAdapter.notifyDataSetChanged();
      
   //collapse all groups
   collapseAll();
   //expand the group where item was just added
   myList.expandGroup(groupPosition);
   //set the current group to be selected so that it becomes visible
   myList.setSelectedGroup(groupPosition);
   
   break;

   // More buttons go here (if any) ...

  }
 }


 //method to expand all groups
 private void expandAll() {
  int count = listAdapter.getGroupCount();
  for (int i = 0; i < count; i++){
   myList.expandGroup(i);
  }
 }
 
 //method to collapse all groups
 private void collapseAll() {
  int count = listAdapter.getGroupCount();
  for (int i = 0; i < count; i++){
   myList.collapseGroup(i);
  }
 }

 //load some initial data into out list 
 private void loadData(){

  addProduct("Apparel","Activewear");
  addProduct("Apparel","Jackets");
  addProduct("Apparel","Shorts");

  addProduct("Beauty","Fragrances");
  addProduct("Beauty","Makeup");

 }
 
 //our child listener
 private OnChildClickListener myListItemClicked =  new OnChildClickListener() {

  public boolean onChildClick(ExpandableListView parent, View v,
    int groupPosition, int childPosition, long id) {
   
   //get the group header
   HeaderInfo headerInfo = deptList.get(groupPosition);
   //get the child info
   DetailInfo detailInfo =  headerInfo.getProductList().get(childPosition);
   //display it or do something with it
   Toast.makeText(getBaseContext(), "Clicked on Detail " + headerInfo.getName() 
     + "/" + detailInfo.getName(), Toast.LENGTH_LONG).show();
   return false;
  }
  
 };
 
 //our group listener
 private OnGroupClickListener myListGroupClicked =  new OnGroupClickListener() {

  public boolean onGroupClick(ExpandableListView parent, View v,
    int groupPosition, long id) {
   
   //get the group header
   HeaderInfo headerInfo = deptList.get(groupPosition);
   //display it or do something with it
   Toast.makeText(getBaseContext(), "Child on Header " + headerInfo.getName(), 
     Toast.LENGTH_LONG).show();
    
   return false;
  }
  
 };

 //here we maintain our products in various departments
 private int addProduct(String department, String product){

  int groupPosition = 0;
  
  //check the hash map if the group already exists
  HeaderInfo headerInfo = myDepartments.get(department); 
  //add the group if doesn't exists
  if(headerInfo == null){
   headerInfo = new HeaderInfo();
   headerInfo.setName(department);
   myDepartments.put(department, headerInfo);
   deptList.add(headerInfo);
  }

  //get the children for the group
  ArrayList<DetailInfo> productList = headerInfo.getProductList();
  //size of the children list
  int listSize = productList.size();
  //add to the counter
  listSize++;

  //create a new child and add that to the group
  DetailInfo detailInfo = new DetailInfo();
  detailInfo.setSequence(String.valueOf(listSize));
  detailInfo.setName(product);
  productList.add(detailInfo);
  headerInfo.setProductList(productList);

  //find the group position inside the list
  groupPosition = deptList.indexOf(headerInfo);
  return groupPosition;
 }

 @Override
 public boolean onCreateOptionsMenu(Menu menu) {
  getMenuInflater().inflate(R.menu.activity_main, menu);
  return true;
 }
}

References


15 comments :

  1. How i can delete a item on this example?

    ReplyDelete
    Replies
    1. Similar to the add function when someone clicks on the detail item remove that item from the arrayList and then call listAdapter.notifyDataSetChanged()

      Delete
  2. Great article! I am new to Android and have one question, how can I click on child item and open a new Activity?

    ReplyDelete
    Replies
    1. repeat the process. its logical not about new or old to android.

      Delete
  3. Great article! Although I am having a bit of trouble trying and getting a NullPointerException in the main activity class on the line myList.setAdapter(listAdapter)

    Any suggestions?

    ReplyDelete
    Replies
    1. I would also like to know this. Intent ... startActivity doesn't work inside the MyListAdapter class. Also when i select a child, it shows in the logcat that I pressed it twice???

      Any help would be appreciated.

      Delete
  4. can you provide sample project for the same

    ReplyDelete
  5. Thanks for the example.

    One question, would you know like putting a header to each sublist?


    Thanks!

    ReplyDelete
  6. How to change color of ListView items on focus and on click???

    ReplyDelete
  7. how to close base after click on particular child item?

    ReplyDelete
  8. Excellent! Can you add a sorting option for this?

    Thanks.

    ReplyDelete
  9. I have a small doubt, I want to put a CheckBox on the group_heading.
    Then I'd like to use this CheckBox to collapseGroup or expandGroup when the CheckBox is checked or not.
    Is it possible? How can I do that?
    I'll try to put a setOnClickListener inside the OnGroupClickListener, but that only works if you press first the layout instead the CheckBox.

    private OnGroupClickListener myListGroupClicked = new OnGroupClickListener() {
    public boolean onGroupClick(ExpandableListView parent, View v, final int groupPosition, long id) {
    //get the group header
    HeaderInfo headerInfo = deptList.get(groupPosition);
    //display it or do something with it
    Toast.makeText(getBaseContext(), "Child on Header " + headerInfo.getName(),Toast.LENGTH_LONG).show();


    CheckBox cb_aplica = (CheckBox) v.findViewById(R.id.head_cb);
    cb_aplica.setOnClickListener(new OnClickListener() {
    @Override
    public void onClick(View v1) {
    if (((CheckBox) v1).isChecked()) {
    myList.expandGroup(groupPosition);
    }
    else{
    myList.collapseGroup(groupPosition);
    }
    }
    });


    return false;
    }
    };

    Help please!!

    ReplyDelete
  10. The data added by user is not save after existing and then returning. Is there a fix for this or is it supposed to be like this?

    ReplyDelete
  11. Overall Tutorial is nice. But I want to remove icon like (up_icon, and down_icon) on left side of the listview. How can I do this. Please reply me.

    ReplyDelete