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.
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; } }
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 ; } }); } }
Comments are closed.