Android – How to create Expandable ListView

  • by
Android Expandable Listview 02

In this post we will see how we can create Android Expandable listview and get name of the selected child. We will create a simple Year and Movies LinkedHaspMap object and use it to fill our Android Exapandable listview. So let’s begin.

Android Expandable Listview Source code download.

First, please download following icons from IconFinder:

  1. Plus icon
  2. Minus icon

Project_Structure_Expandable_listview

Copy the icons as shown in the above image.

Create three layout files as following:

res>layout>mylist.xml:

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


    <ExpandableListView
        android:id="@+id/myExpandableList"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:divider="#000"
        android:dividerHeight="1dp"
        android:groupIndicator="@drawable/listselection"/>

</LinearLayout>

This will be our layout file. Create a new drawable file as below:

res>drawable>listselection.xml:

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:drawable="@drawable/minus" android:state_empty="false" />
    <item android:drawable="@drawable/minus" android:state_expanded="true" />
    <item android:drawable="@drawable/plus" />
</selector>

This will be our List indicator for whether its elements our expanded or collapsed.

Now we will create the Header file and the child file. Header file will be used for showing the expandable listview’s heading and child file will be used to show its data. We will use years (2019,2018) as our header and some of the best movies released in the following years (Parasite, Deadpool 2, Joker) as its child data.

res>layout>listparent.xml:

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

    <TextView
        android:id="@+id/txtHeader"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:padding="10dp"
        android:textSize="25sp"
        android:textColor="#000" />
</LinearLayout>

res>layout>listchilds.xml:

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

    <TextView
        android:id="@+id/txtChild"
        android:padding="20dp"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textColor="#4E4E4E"
        android:textSize="20sp"/>
</LinearLayout>
Android_Expandable_Listview_01

Android_Expandable_Listview_01

Now, let’s create a class files to make our Expandable list view work.

Movies.java:

package com.app.expandablelist;

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

public class Movies {

    public static LinkedHashMap<String, List<String>> getMovies()
    {
        LinkedHashMap<String, List<String>> myData = new LinkedHashMap<String,List<String>>();

        List<String> movies2019 = new ArrayList<String>();

        movies2019.add("Parasite");
        movies2019.add("Avengers Endgame");
        movies2019.add("Joker");
        movies2019.add("Ford vs Ferrari");


        List<String> movies2018 = new ArrayList<String>();

        movies2018.add("Black Panther");
        movies2018.add("A Quiet Place");
        movies2018.add("Incredibles 2");
        movies2018.add("Deadpool 2");


        List<String> movies2017 = new ArrayList<String>();

        movies2017.add("Get Out");
        movies2017.add("Dunkrik");
        movies2017.add("Logan");
        movies2017.add("Baby Driver");

        myData.put("2017", movies2017);
        myData.put("2018", movies2018);
        myData.put("2019", movies2019);

        return  myData;
    }
}

This will return a LinkedHashMap<String,List<String>> object containing movies with its year released.

listadapter.java:

package com.app.expandablelist;

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

import java.util.LinkedHashMap;
import java.util.List;

public class listadapter extends BaseExpandableListAdapter {

    private Context context;
    private List<String> year;
    private LinkedHashMap<String, List<String>> movies;

    public listadapter(Context context, List<String> year,
                                       LinkedHashMap<String, List<String>> movieName) {
        this.context = context;
        this.year = year;
        this.movies = movieName;
    }

    @Override
    public Object getChild(int listPosition, int expandedListPosition) {
        return this.movies.get(this.year.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.listchilds, null);
        }
        TextView expandedListTextView = convertView.findViewById(R.id.txtChild);
        expandedListTextView.setText(expandedListText);
        return convertView;
    }

    @Override
    public int getChildrenCount(int listPosition) {
        return this.movies.get(this.year.get(listPosition))
                .size();
    }

    @Override
    public Object getGroup(int listPosition) {
        return this.year.get(listPosition);
    }

    @Override
    public int getGroupCount() {
        return this.year.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.listparent, null);
        }
        TextView listTitleTextView = convertView.findViewById(R.id.txtHeader);

        listTitleTextView.setText(listTitle);
        return convertView;
    }

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

    @Override
    public boolean isChildSelectable(int listPosition, int expandedListPosition) {
        return true;
    }
}

This will be our Listview adapter. We will pass List<String> (years) and LinkedHashMap<String, List<String>> (years and movies).

MyList.java:

package com.app.expandablelist;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.DisplayMetrics;
import android.view.View;
import android.widget.ExpandableListView;
import android.widget.Toast;

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

public class MyList extends AppCompatActivity {

    ExpandableListView myExpandableList;
    listadapter expandableListAdapter;
    List<String> moviesTitle;
    LinkedHashMap<String, List<String>> moviesDetail;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.mylist);
        myExpandableList = findViewById(R.id.myExpandableList);

        DisplayMetrics metrics = new DisplayMetrics();

        getWindowManager().getDefaultDisplay().getMetrics(metrics);

        int width = metrics.widthPixels;

        myExpandableList.setIndicatorBounds(width - GetPixelFromDips(50),
                width - GetPixelFromDips(5));

        moviesDetail = Movies.getMovies();

        moviesTitle = new ArrayList<String>(moviesDetail.keySet());

        expandableListAdapter = new listadapter(this, moviesTitle, moviesDetail);

        myExpandableList.setAdapter(expandableListAdapter);

        myExpandableList.setOnGroupExpandListener(new ExpandableListView.OnGroupExpandListener() {

            @Override
            public void onGroupExpand(int groupPosition) {
                Toast.makeText(getApplicationContext(),
                        moviesTitle.get(groupPosition)+" expanded.",
                        Toast.LENGTH_SHORT).show();
            }
        });

        myExpandableList.setOnGroupCollapseListener(new ExpandableListView.OnGroupCollapseListener() {

            @Override
            public void onGroupCollapse(int groupPosition) {
                Toast.makeText(getApplicationContext(),
                        moviesTitle.get(groupPosition) + " collapsed.",
                        Toast.LENGTH_SHORT).show();

            }
        });

        myExpandableList.setOnChildClickListener(new ExpandableListView.OnChildClickListener() {
            @Override
            public boolean onChildClick(ExpandableListView parent, View v,
                                        int groupPosition, int childPosition, long id) {
                Toast.makeText(
                        getApplicationContext(),
                         moviesDetail.get(
                                moviesTitle.get(groupPosition)).get(
                                childPosition), Toast.LENGTH_SHORT
                ).show();
                return false;
            }
        });
    }

    public int GetPixelFromDips(float pixels) {
        // Get the screen's density scale
        final float scale = getResources().getDisplayMetrics().density;
        // Convert the dps to pixels, based on density scale
        return (int) (pixels * scale + 0.5f);
    }

}

This class will be our Activity class.

Android Expandable Listview 02

Android Expandable Listview 02

Create a new string object in strings.xml for name of our activity:

res > values > strings.xml:

<resources>
    <string name="app_name">ExpandableList</string>
    <string name="action_settings">Settings</string>
    <string name="movies_header">Movies by Year</string>
</resources>

res > values > colors.xml:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color name="colorPrimary">#3949AB</color>
    <color name="colorPrimaryDark">#1F2966</color>
    <color name="colorAccent">#000</color>
</resources>

Edit your AndroidManifest.xml as below:

AndroidManifest.xml:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.app.expandablelist">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity
            android:name=".MyList"
            android:label="@string/movies_header"
            android:theme="@style/AppTheme">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

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

</manifest>

Download source code using the link below:

Android Expandable Listview Source code download.

Also see:

Create Android Listview which scrolls Horizontally and Vertically.

Android Listview binding from MySQL Database table

Android Listview binding from SQLite database