Android

Expandable ListView – Android Tutorial

 

ExpandableListView is used to display data in groups. These groups are expanded to show their children. For this kind of listview, we use ExpandableAdapter. We can expand and collapse groups to show and hide child views. It’s the quickest way to display multilevel data.

Let’s get started with a new project using empty activity.

Create ExpandableListView New Projects

1. activity_main.xml

We need to add ExpandableListView in activity_main.xml like we add listview in our previous tutorials of the simple listview and custom listview.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.tutorialscache.expandablelistview.MainActivity">
    <ExpandableListView
        android:id="@+id/expandAbleListView"
        android:animateLayoutChanges="true"
        android:transcriptMode="alwaysScroll"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</LinearLayout>

1.1 settings_selector.xml

Create a settings_selector.xml under drawable folders for selection of the selected group arrow.

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android" >
    <item
        android:width="8dp" android:height="8dp"
        android:drawable="@drawable/arrow_down"
        android:state_expanded="true" />
    <item
        android:width="8dp" android:height="8dp"
        android:drawable="@drawable/arrow_right" />
</selector>

2. list_header.xml

We need a list_header.xml file to create group headings.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="#fd0c34"
    android:padding="6dp">
    <ImageView
        android:id="@+id/expandable_icon"
        android:layout_width="25dp"
        android:layout_height="25dp"
        android:layout_gravity="center_vertical"
        android:src="@drawable/settings_selector" />
    <TextView
        android:id="@+id/headerTv"
        android:text="Heading"
        android:textSize="20dp"
        android:textColor="#FFF"
        android:layout_gravity="center_vertical"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
</LinearLayout>

3. child_item.xml

The last XML file which we needed is to display child items under heading groups.

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">
    <ImageView
        android:id="@+id/childImg"
        android:layout_width="80dp"
        android:padding="1dp"
        android:layout_centerVertical="true"
        android:layout_height="60dp" />
    <TextView
        android:id="@+id/childTv"
        android:layout_toRightOf="@+id/childImg"
        android:layout_centerVertical="true"
        android:textSize="20dp"
        android:textColor="#000"
        android:text="Child Item"
        android:padding="5dp"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
</RelativeLayout>

Related: Custom ListView using BaseAdapter – Android Example.

4. ChildDataModel.java

Create a new java file name ChildDataModel.java and create getters and setters.

package com.tutorialscache.expandablelistview;
import android.util.Log;
public class ChildDataModel {
    long id;
    int image;
    String title;
    public ChildDataModel(int id, String country, int image) {
         this.setId(id);
         this.setTitle(country);
         this.setImage(image);
    }
    public int getImage() {
        return image;
    }
    public void setImage(int image) {
        this.image = image;
    }
    public long getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getTitle() {
        return title;
    }
    public void setTitle(String title) {
        this.title = title;
    }
    @Override
    public String toString() {
        Log.d("response ","ID: "+getId()+" Title: "+getTitle());
        return super.toString();
    }
}

5. ExpandableCustomAdapter.java

An expandable custom adapter is one of the key components of an expandable listview. It is used to render data on views. We will extend our ExpandableCustomAdapter with BaseExpandableListAdapter which provides us required callbacks like getChild, getChildId, getChildView, getChildrenCount, getGroup, getGroupCount, getGroupId and getGroupView.

package com.tutorialscache.expandablelistview;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseExpandableListAdapter;
import android.widget.ImageView;
import android.widget.TextView;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
public class ExpandableCustomAdapter extends BaseExpandableListAdapter{
    //Initializing variables
    private List<String> headerData;
    private HashMap<String, ArrayList<ChildDataModel>> childData;
    private Context mContext;
    private LayoutInflater layoutInflater;
    // constructor
    public ExpandableCustomAdapter(Context mContext, List<String> headerData,
                                 HashMap<String, ArrayList<ChildDataModel>> childData) {
        this.mContext = mContext;
        this.headerData = headerData;
        this.childData = childData;
        this.layoutInflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    }
    @Override
    public int getGroupCount() {
        return this.headerData.size();
    }
    @Override
    public int getChildrenCount(int headPosition) {
        return this.childData.get(this.headerData.get(headPosition)).size();
    }
    @Override
    public Object getGroup(int headPosition) {
        return this.headerData.get(headPosition);
    }
    @Override
    public Object getChild(int headPosition, int childPosition) {
        return this.childData.get(this.headerData.get(headPosition))
                .get(childPosition);
    }
    @Override
    public long getGroupId(int headPosition) {
        return headPosition;
    }
    @Override
    public long getChildId(int headPosition, int childPosition) {
        return this.childData.get(this.headerData.get(headPosition))
                .get(childPosition).getId();
    }
    @Override
    public boolean hasStableIds() {
        return false;
    }
    @Override
    public View getGroupView(int headPosition, boolean is_expanded, View view, ViewGroup headGroup) {
        // Heading of each group
        String heading = (String) getGroup(headPosition);
        if (view==null){
            view = layoutInflater.inflate(R.layout.list_header,null);
        }
        TextView headerTv = view.findViewById(R.id.headerTv);
        headerTv.setText(heading+"");
        return view;
    }
    @Override
    public View getChildView(int headPosition, int childPosition, boolean islastChild, View view, ViewGroup viewGroup) {
         ChildDataModel child = (ChildDataModel) getChild(headPosition, childPosition);
        if (view == null) {
            view = layoutInflater.inflate(R.layout.child_item, null);
        }
        TextView childTv = (TextView) view.findViewById(R.id.childTv);
        ImageView childImg = (ImageView) view.findViewById(R.id.childImg);
        childTv.setText(child.getTitle());
        childImg.setImageResource(child.getImage());
        return view;
    }
    @Override
    public boolean isChildSelectable(int headPosition, int childPosition) {
        return true;
    }
}
Expandable listview
Project Structure

6. MainActivity.java

MainActivity groups together all components. We will add data to Lists and pass them to an expandable custom adapter. It has also the functionality of closing previously expanded groups when clicking on the currently not expanded group. Both expandable icons (arrow right, arrow down) are also managed on the expansion and closing of groups.

package com.tutorialscache.expandablelistview;
import android.content.Context;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.ExpandableListView;
import android.widget.ImageView;
import android.widget.Toast;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
public class MainActivity extends AppCompatActivity {
    ExpandableCustomAdapter expandableCustomAdapter;
    ExpandableListView expandableListView;
    List<String> headerData;
    HashMap<String,ArrayList<ChildDataModel>> childData;
    ChildDataModel childDataModel;
    Context mContext;
    ArrayList<ChildDataModel> asianCountries,africanCountries,nAmericanCountries,sAmericanCountries;
    private int lastExpandedPosition = -1;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mContext = this;
        //initializing arraylists
        headerData = new ArrayList<>();
        childData = new HashMap<String,ArrayList<ChildDataModel>>();
        asianCountries = new ArrayList<>();
        africanCountries = new ArrayList<>();
        nAmericanCountries = new ArrayList<>();
        sAmericanCountries = new ArrayList<>();
        // link listview from activity_main.xml
        expandableListView = findViewById(R.id.expandAbleListView);
        //populating data of world continents and their countries.
        headerData.add("ASIA");
        //adding countries to Asian continent
        childDataModel = new ChildDataModel(1,"Afghanistan",R.drawable.afghanistan);
        asianCountries.add(childDataModel);
        childDataModel = new ChildDataModel(2,"China",R.drawable.china);
        asianCountries.add(childDataModel);
        childDataModel = new ChildDataModel(3,"India",R.drawable.india);
        asianCountries.add(childDataModel);
        childDataModel = new ChildDataModel(4,"Pakistan",R.drawable.pakistan);
        asianCountries.add(childDataModel);
        childData.put(headerData.get(0),asianCountries);
        headerData.add("AFRICA");
        //adding countries to African continent
        childDataModel = new ChildDataModel(1,"South Africa",R.drawable.southafrica);
        africanCountries.add(childDataModel);
        childDataModel = new ChildDataModel(2,"Zimbabwe",R.drawable.zimbabwe);
        childData.put(headerData.get(1),africanCountries);
        headerData.add("NORTH AMERICA");
        //adding countries to NORTH AMERICA continent
        childDataModel = new ChildDataModel(1,"Canada",R.drawable.canada);
        nAmericanCountries.add(childDataModel);
        childData.put(headerData.get(2),nAmericanCountries);
        headerData.add("SOUTH AMERICA");
        //adding countries to SOUTH AMERICA continent
        childDataModel = new ChildDataModel(1,"Argentina",R.drawable.argentena);
        sAmericanCountries.add(childDataModel);
        childData.put(headerData.get(3),sAmericanCountries);
        //set adapter to list view
        expandableCustomAdapter = new ExpandableCustomAdapter(mContext,headerData,childData);
        expandableListView.setAdapter(expandableCustomAdapter);
        //child click listener
        expandableListView.setOnChildClickListener(new ExpandableListView.OnChildClickListener() {
            @Override
            public boolean onChildClick(ExpandableListView expandableListView, View view, int headPosition, int childPosition, long id) {
                Toast.makeText(mContext,
                        headerData.get(headPosition)
                                + " has country "
                                + childData.get(
                                headerData.get(headPosition)).get(
                                childPosition).getTitle(), Toast.LENGTH_SHORT)
                        .show();
                return false;
            }
        });
        //group expanded
        expandableListView.setOnGroupExpandListener(new ExpandableListView.OnGroupExpandListener() {
            @Override
            public void onGroupExpand(int headPosition) {
                if (lastExpandedPosition != -1
                        && headPosition != lastExpandedPosition) {
                    expandableListView.collapseGroup(lastExpandedPosition);
                }
                lastExpandedPosition = headPosition;
                Toast.makeText(mContext,
                        headerData.get(headPosition) + " continent expanded",
                        Toast.LENGTH_SHORT).show();
            }
        });
        //group collapsed
         expandableListView.setOnGroupCollapseListener(new ExpandableListView.OnGroupCollapseListener() {
             @Override
             public void onGroupCollapse(int headPosition) {
                 Toast.makeText(mContext,
                         headerData.get(headPosition) + " continent collapsed",
                         Toast.LENGTH_SHORT).show();
             }
         });
        //Group Indicator
        expandableListView.setOnGroupClickListener(new ExpandableListView.OnGroupClickListener() {
            @Override
            public boolean onGroupClick(ExpandableListView parent, View v, int groupPosition, long id) {
                parent.smoothScrollToPosition(groupPosition);
                if (parent.isGroupExpanded(groupPosition)) {
                    ImageView imageView = v.findViewById(R.id.expandable_icon);
                    imageView.setImageDrawable(getResources().getDrawable(R.drawable.arrow_right));
                } else {
                    ImageView imageView = v.findViewById(R.id.expandable_icon);
                    imageView.setImageDrawable(getResources().getDrawable(R.drawable.arrow_down));
                }
                return false    ;
            }
        });
    }
}
custom listview android
Final App

Comments are closed.