Contents
I recently worked on a project, where I found it challenging initially to add a header and footer to the ExpandableListView childView. Here I document the solution I found to overcome the challenge.
Ho Ngoc Trang, trangho214@gmail.com, is the author of this article and she contributes to RobustTechHouse Blog.
XML layout files
In order to create an expandable list view with header and footer to the child view, we will need five xml layout files. The first one is for displaying the main layout containing the ExpandableListview, the second one is for group item layout, the third is for the child item layout and the other two are for header and footer of the childview.
activity_main.xml
Open activity_main.xml layout in res/layout and add the ExpandableListView element:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <ExpandableListView android:id="@+id/elv" android:layout_width="match_parent" android:layout_height="match_parent"></ExpandableListView> </RelativeLayout>
parent_row.xml
Create another xml file for list view group header and name it as parent_row.xml and copy the following content.
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:background="@color/parent_background" android:paddingLeft="@dimen/padding_left" android:layout_margin="5dp" android:layout_height="match_parent"> <TextView android:id="@+id/txtMother" android:layout_marginTop="10dp" android:textStyle="bold" android:layout_marginBottom="5dp" android:layout_width="match_parent" android:layout_height="wrap_content" /> <TextView android:id="@+id/txtFather" android:layout_marginBottom="10dp" android:textStyle="bold" android:layout_width="match_parent" android:layout_height="wrap_content" /> </LinearLayout>
child_row.xml
Create a new layout file child_row.xml in res/layout and copy the following content.
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:background="@color/child_background" android:paddingLeft="@dimen/padding_left" android:layout_height="match_parent"> <TextView android:id="@+id/txtChildName" android:textColor="@android:color/holo_orange_dark" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="10dp"/> <TextView android:id="@+id/txtChildAge" android:textColor="@android:color/holo_orange_dark" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginBottom="10dp"/> </LinearLayout>
child_header.xml
Create a new layout file child_header.xml in res/layout and copy the following content.
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="50dp" android:background="@color/hf_background" android:paddingLeft="@dimen/padding_left"> <TextView android:id="@+id/txtHeader" android:textColor="@android:color/holo_green_light" android:layout_width="match_parent" android:gravity="center_vertical" android:textStyle="bold" android:text="Header" android:layout_height="50dp" /> </LinearLayout>
child_footer.xml
Create a new layout file child_footer.xml in res/layout and copy the following content.
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="50dp" android:background="@color/hf_background" android:paddingLeft="@dimen/padding_left"> <TextView android:id="@+id/txtFooter" android:textColor="@android:color/holo_green_light" android:layout_width="match_parent" android:layout_height="50dp" android:gravity="center_vertical" android:textStyle="bold" android:text="Footer" /> </LinearLayout>
Model Classes
According to the structure of an expandable list view, we need to have at least two classes, one for the child and another one for group/parent.
ChildObject.java
ChildObject class will hold all fields displayed in the child row view.
public class ChildObject { public String childName; public int age; public ChildObject(String childName, int age) { this.childName = childName; this.age = age; } }
ParentObject.java
ParentObject will hold all fields displayed in the group/parent row view including fields for header and footer.
public class ParentObject { public String mother; public String father; //Header and footer don’t need to be a String, //they can be a class or whatever depend on your need. public String textToHeader; public String textToFooter; //Parent should have a list of their child public List<ChildObject> childObjects; public ParentObject(String mother, String father, String textToHeader, String textToFooter, List<ChildObject> childObjects) { this.mother = mother; this.father = father; this.textToFooter = textToFooter; this.textToHeader = textToHeader; this.childObjects = childObjects; } }
Custom adapter for ExpandableListView
This is the important part of this tutorial where we actually create the header and footer for the ExpandableListView ChildView.
We are going to create a custom adapter named MyExpandableAdapter. This class is extended from BaseExpandableListAdapter, which provides required methods to render the ExpandableListView.
Create a class and name it as MyExpandableListView, then add the following code.
public class MyExpandableAdapter extends BaseExpandableListAdapter { Context context; List<ParentObject> parentObjects; public MyExpandableAdapter(Context context, List<ParentObject> parentObjects) { this.context = context; this.parentObjects = parentObjects; } @Override public int getGroupCount() { return parentObjects.size(); } //Add 2 to childcount. The first row and the last row are used as header and footer to childview @Override public int getChildrenCount(int i) { return parentObjects.get(i).childObjects.size() +2; } @Override public ParentObject getGroup(int i) { return parentObjects.get(i); } @Override public ChildObject getChild(int i, int i2) { return parentObjects.get(i).childObjects.get(i2); } @Override public long getGroupId(int i) { return i; } @Override public long getChildId(int i, int i2) { return 0; } @Override public boolean hasStableIds() { return false; } @Override public View getGroupView(int i, boolean b, View view, ViewGroup viewGroup) { ParentObject currentParent = parentObjects.get(i); if(view ==null) { LayoutInflater inflater =(LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); view = inflater.inflate(R.layout.parent_row,null); } TextView txtMother = (TextView)view.findViewById(R.id.txtMother); TextView txtFather = (TextView)view.findViewById(R.id.txtFather); txtMother.setText(currentParent.mother); txtFather.setText(currentParent.father); return view; } @Override public View getChildView(int groupPosition, int childPosition, boolean b, View view, ViewGroup viewGroup) { ParentObject currentParent = getGroup(groupPosition); LayoutInflater inflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); //the first row is used as header if(childPosition ==0) { view = inflater.inflate(R.layout.child_header, null); TextView txtHeader = (TextView)view.findViewById(R.id.txtHeader); txtHeader.setText(currentParent.textToHeader); } //Here is the ListView of the ChildView if(childPosition>0 && childPosition<getChildrenCount(groupPosition)-1) { ChildObject currentChild = getChild(groupPosition,childPosition-1); view = inflater.inflate(R.layout.child_row,null); TextView txtChildName = (TextView)view.findViewById(R.id.txtChildName); TextView txtChildAge = (TextView)view.findViewById(R.id.txtChildAge); txtChildName.setText(currentChild.childName); txtChildAge.setText("Age: " + String.valueOf(currentChild.age)); } //the last row is used as footer if(childPosition == getChildrenCount(groupPosition)-1) { view = inflater.inflate(R.layout.child_footer,null); TextView txtFooter = (TextView)view.findViewById(R.id.txtFooter); txtFooter.setText(currentParent.textToFooter); } return view; } @Override public boolean isChildSelectable(int i, int i2) { return false; } }
There are many methods in this class but there are only two of them we need to focus on:
getChildrenCount: return the count of row of child item in a specific group. Here I’ve added two extra rows to the return method. The first row will be used for header and the second row will be used for footer.
getChildView: render the child list of each group. Here we will check the child position to inflate the appropriate layout to each row. The layout of header and footer will be inflated to the first and last of childPostion and the rest will show all items of the child list.
Main activity and sample data for expandable list view
Once you are done with creating a custom adapter for your expandable list view, open the MainActivity class and copy the following code. In this activity I have also created sample data to display in the expandable list view.
public class MainActivity extends Activity { ExpandableListView elv; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); LayoutInflater inflater = getLayoutInflater(); elv = (ExpandableListView)findViewById(R.id.elv); elv.setOnGroupExpandListener(onGroupExpandListenser); MyExpandableAdapter adapter = new MyExpandableAdapter(this, getData()); elv.setAdapter(adapter); } ExpandableListView.OnGroupExpandListener onGroupExpandListenser = new ExpandableListView.OnGroupExpandListener() { int previousGroup =-1; @Override public void onGroupExpand(int groupPosition) { if(groupPosition!= previousGroup) elv.collapseGroup(previousGroup); previousGroup = groupPosition; } }; //Sample data for expandable list view. public List<ParentObject> getData() { List<ParentObject> parentObjects = new ArrayList<ParentObject>(); for (int i = 0; i<20; i++) { parentObjects.add(new ParentObject("Mother " +i, "Father " +i, "Header " + i, "Footer " +i, getChildren(i))); } return parentObjects; } private List<ChildObject> getChildren(int childCount) { List<ChildObject> childObjects = new ArrayList<ChildObject>(); for (int i =0; i<childCount; i++) { childObjects.add(new ChildObject("Child " + (i+1), 10 +i )); } return childObjects; } }
Output
Conclusion
I have just showed you how I added a header and footer to the ExpandableListView ChildView. Hope you find this post useful.
The completed project can be downloaded at GitHub HeaderAndFooterForExpandableListview
If you like our articles, please follow and like our Facebook page where we regularly share interesting posts and check out our other blog articles.
RobustTechHouse is a leading tech company focusing on mobile app development in Singapore. If you are interested to have us work with you on your projects, you can contact us here.