Android ExpandableListView inside NavigationView

Filed Under: Android

In this tutorial, we’ll be implementing an application in which we display an ExpandableListView of items inside a NavigationView.

Android ExpandableListView inside NavigationView

As we’d seen in the NavigationView tutorial, we can have submenus but those ones can’t be expanded/collapsed like we do in ExpandableListViews. So let’s try to implement the ExpandableListView inside a NavigationView.

Let’s start by creating a new Android Studio Project. Select the activity template as Navigation Drawer Activity as shown below.

Android ExpandableListView inside NavigationView

This encloses our activity class in a DrawerLayout and adds a NavigationView inside it by default. In our xml editor we see the following design of the activity:

android navigation expandable view xml editor

Now, all we need to do is replace the menus with an ExpandableListView.

Android ExpandableListView inside NavigationView Project Structure

Android ExpandableListView inside NavigationView Project Structure

In this application each of our menus while open a url in a WebView. Some menus can be expanded while others cannot. We’ll populate the data using the class MenuModel.

Android ExpandableListView NavigationView Code

Let’s look at the layout file activity_main.xml


<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.DrawerLayout 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:id="@+id/drawer_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true"
    tools:openDrawer="start">

    <include
        layout="@layout/app_bar_main"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

    <android.support.design.widget.NavigationView
        android:id="@+id/nav_view"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:layout_gravity="start"
        android:fitsSystemWindows="true"
        app:headerLayout="@layout/nav_header_main">

        <ExpandableListView
            android:id="@+id/expandableListView"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="@dimen/nav_header_height"
            android:background="@android:color/white"
            android:dividerHeight="0dp"
            android:groupIndicator="@null" />

    </android.support.design.widget.NavigationView>

</android.support.v4.widget.DrawerLayout>

The code for the content_main.xml class is given below.


<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout 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"
    app:layout_behavior="@string/appbar_scrolling_view_behavior"
    tools:context="com.journaldev.navigationviewexpandablelistview.MainActivity"
    tools:showIn="@layout/app_bar_main">

    <WebView
        android:id="@+id/webView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</android.support.constraint.ConstraintLayout>

The code for the layouts list_group_header.xml and list_group_child.xml which will be inflated in the Adapter class is given below.

list_group_header.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/lblListHeader"
        android:layout_width="match_parent"
        android:layout_height="?attr/listPreferredItemHeightSmall"
        android:gravity="center_vertical"
        android:paddingLeft="?android:attr/expandableListPreferredItemPaddingLeft"
        android:paddingRight="?attr/listPreferredItemPaddingRight"
        android:textColor="#1f2124"
        android:textSize="16sp" />
</LinearLayout>

list_group_child.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"
    android:padding="8dp">

    <TextView
        android:id="@+id/lblListItem"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:paddingLeft="?android:attr/expandableListPreferredChildPaddingLeft" />
</LinearLayout>

The code for the MenuModel.java is given below.


package com.journaldev.navigationviewexpandablelistview;

public class MenuModel {

    public String menuName, url;
    public boolean hasChildren, isGroup;

    public MenuModel(String menuName, boolean isGroup, boolean hasChildren, String url) {

        this.menuName = menuName;
        this.url = url;
        this.isGroup = isGroup;
        this.hasChildren = hasChildren;
    }
}

The same class can be used for both the header and child rows of the ExpandableListView in this tutorial.

The code for the MainActivity.java class is given below.


package com.journaldev.navigationviewexpandablelistview;

import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.design.widget.FloatingActionButton;
import android.support.design.widget.Snackbar;
import android.util.Log;
import android.view.View;
import android.support.design.widget.NavigationView;
import android.support.v4.view.GravityCompat;
import android.support.v4.widget.DrawerLayout;
import android.support.v7.app.ActionBarDrawerToggle;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.view.Menu;
import android.view.MenuItem;
import android.webkit.WebView;
import android.widget.ExpandableListView;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

public class MainActivity extends AppCompatActivity
        implements NavigationView.OnNavigationItemSelectedListener {


    ExpandableListAdapter expandableListAdapter;
    ExpandableListView expandableListView;
    List<MenuModel> headerList = new ArrayList<>();
    HashMap<MenuModel, List<MenuModel>> childList = new HashMap<>();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Toolbar toolbar = findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        FloatingActionButton fab = findViewById(R.id.fab);
        fab.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
                        .setAction("Action", null).show();
            }
        });

        expandableListView = findViewById(R.id.expandableListView);
        prepareMenuData();
        populateExpandableList();

        DrawerLayout drawer = findViewById(R.id.drawer_layout);
        ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(
                this, drawer, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close);
        drawer.addDrawerListener(toggle);
        toggle.syncState();

        NavigationView navigationView = findViewById(R.id.nav_view);
        navigationView.setNavigationItemSelectedListener(this);
    }

    @Override
    public void onBackPressed() {
        DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
        if (drawer.isDrawerOpen(GravityCompat.START)) {
            drawer.closeDrawer(GravityCompat.START);
        } else {
            super.onBackPressed();
        }
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.main, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();

        //noinspection SimplifiableIfStatement
        if (id == R.id.action_settings) {
            return true;
        }

        return super.onOptionsItemSelected(item);
    }

    @SuppressWarnings("StatementWithEmptyBody")
    @Override
    public boolean onNavigationItemSelected(MenuItem item) {
        // Handle navigation view item clicks here.
        int id = item.getItemId();

        if (id == R.id.nav_camera) {
            // Handle the camera action
        } else if (id == R.id.nav_gallery) {

        } else if (id == R.id.nav_slideshow) {

        } else if (id == R.id.nav_manage) {

        } else if (id == R.id.nav_share) {

        } else if (id == R.id.nav_send) {

        }

        DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
        drawer.closeDrawer(GravityCompat.START);
        return true;
    }

    private void prepareMenuData() {

        MenuModel menuModel = new MenuModel("Android WebView Tutorial", true, false, "https://www.journaldev.com/9333/android-webview-example-tutorial"); //Menu of Android Tutorial. No sub menus
        headerList.add(menuModel);

        if (!menuModel.hasChildren) {
            childList.put(menuModel, null);
        }

        menuModel = new MenuModel("Java Tutorials", true, true, ""); //Menu of Java Tutorials
        headerList.add(menuModel);
        List<MenuModel> childModelsList = new ArrayList<>();
        MenuModel childModel = new MenuModel("Core Java Tutorial", false, false, "https://www.journaldev.com/7153/core-java-tutorial");
        childModelsList.add(childModel);

        childModel = new MenuModel("Java FileInputStream", false, false, "https://www.journaldev.com/19187/java-fileinputstream");
        childModelsList.add(childModel);

        childModel = new MenuModel("Java FileReader", false, false, "https://www.journaldev.com/19115/java-filereader");
        childModelsList.add(childModel);


        if (menuModel.hasChildren) {
            Log.d("API123","here");
            childList.put(menuModel, childModelsList);
        }

        childModelsList = new ArrayList<>();
        menuModel = new MenuModel("Python Tutorials", true, true, ""); //Menu of Python Tutorials
        headerList.add(menuModel);
        childModel = new MenuModel("Python AST – Abstract Syntax Tree", false, false, "https://www.journaldev.com/19243/python-ast-abstract-syntax-tree");
        childModelsList.add(childModel);

        childModel = new MenuModel("Python Fractions", false, false, "https://www.journaldev.com/19226/python-fractions");
        childModelsList.add(childModel);

        if (menuModel.hasChildren) {
            childList.put(menuModel, childModelsList);
        }
    }

    private void populateExpandableList() {

        expandableListAdapter = new ExpandableListAdapter(this, headerList, childList);
        expandableListView.setAdapter(expandableListAdapter);

        expandableListView.setOnGroupClickListener(new ExpandableListView.OnGroupClickListener() {
            @Override
            public boolean onGroupClick(ExpandableListView parent, View v, int groupPosition, long id) {

                if (headerList.get(groupPosition).isGroup) {
                    if (!headerList.get(groupPosition).hasChildren) {
                        WebView webView = findViewById(R.id.webView);
                        webView.loadUrl(headerList.get(groupPosition).url);
                        onBackPressed();
                    }
                }

                return false;
            }
        });

        expandableListView.setOnChildClickListener(new ExpandableListView.OnChildClickListener() {
            @Override
            public boolean onChildClick(ExpandableListView parent, View v, int groupPosition, int childPosition, long id) {

                if (childList.get(headerList.get(groupPosition)) != null) {
                    MenuModel model = childList.get(headerList.get(groupPosition)).get(childPosition);
                    if (model.url.length() > 0) {
                        WebView webView = findViewById(R.id.webView);
                        webView.loadUrl(model.url);
                        onBackPressed();
                    }
                }

                return false;
            }
        });
    }
}

prepareMenuData() is where we are populating our data structures with dummy datas.

For the group headers, we use an ArrayList of MenuModels.

The child rows data is populated in a HashMap where the key is the header MenuModel and the values are the list of child MenuModels.

In the onGroupClick and onChildClick Listeners of the ExpandableListView, we retrieve the url for the current position and load them in the WebView of our Activity.

Let’s look at the ExpandableListAdapter.java class.


package com.journaldev.navigationviewexpandablelistview;

import android.content.Context;
import android.graphics.Typeface;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseExpandableListAdapter;
import android.widget.TextView;

import java.util.HashMap;
import java.util.List;

public class ExpandableListAdapter extends BaseExpandableListAdapter {
    private Context context;
    private List<MenuModel> listDataHeader;
    private HashMap<MenuModel, List<MenuModel>> listDataChild;

    public ExpandableListAdapter(Context context, List<MenuModel> listDataHeader,
                                 HashMap<MenuModel, List<MenuModel>> listChildData) {
        this.context = context;
        this.listDataHeader = listDataHeader;
        this.listDataChild = listChildData;
    }

    @Override
    public MenuModel getChild(int groupPosition, int childPosititon) {
        return this.listDataChild.get(this.listDataHeader.get(groupPosition))
                .get(childPosititon);
    }

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

    @Override
    public View getChildView(int groupPosition, final int childPosition,
                             boolean isLastChild, View convertView, ViewGroup parent) {

        final String childText = getChild(groupPosition, childPosition).menuName;

        if (convertView == null) {
            LayoutInflater infalInflater = (LayoutInflater) this.context
                    .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            convertView = infalInflater.inflate(R.layout.list_group_child, null);
        }

        TextView txtListChild = convertView
                .findViewById(R.id.lblListItem);

        txtListChild.setText(childText);
        return convertView;
    }

    @Override
    public int getChildrenCount(int groupPosition) {

        if (this.listDataChild.get(this.listDataHeader.get(groupPosition)) == null)
            return 0;
        else
            return this.listDataChild.get(this.listDataHeader.get(groupPosition))
                    .size();
    }

    @Override
    public MenuModel getGroup(int groupPosition) {
        return this.listDataHeader.get(groupPosition);
    }

    @Override
    public int getGroupCount() {
        return this.listDataHeader.size();

    }

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

    @Override
    public View getGroupView(int groupPosition, boolean isExpanded,
                             View convertView, ViewGroup parent) {
        String headerTitle = getGroup(groupPosition).menuName;
        if (convertView == null) {
            LayoutInflater infalInflater = (LayoutInflater) this.context
                    .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            convertView = infalInflater.inflate(R.layout.list_group_header, null);
        }

        TextView lblListHeader = convertView.findViewById(R.id.lblListHeader);
        lblListHeader.setTypeface(null, Typeface.BOLD);
        lblListHeader.setText(headerTitle);

        return convertView;
    }

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

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

Don’t forget to add the following permission in your AndroidManifest.xml file.


<uses-permission android:name="android.permission.INTERNET"/>

Android ExpandableListView NavigationView App Output

The output of the above application in action is given below.
android navigationview expandable view app output

This brings an end to this tutorial. You can download the final Android NavigationViewExpandableListView Tutorial from the link below.

Comments

  1. Akhil Patoliya says:

    Very useful tutorial. but i want to add child of child it means one parent have 2 child element so i need in first child element i have add another child element so how can i do?

    Could u please help?

    1. Akhil Patoliya says:

      Like that :

      JavaTutorial
      corejavatutorial
      corejava
      javafileinputstream

      1. akhil says:

        Like this:

        JavaTutorial mian root
        corejavatutorial parent
        corejava child
        javafileinputstream parent

    2. Akhil Patoliya says:

      I have to develop a 3 level expandable listview using your example. Please help me

  2. vaidehi shah says:

    hey your example is just perfect..but i have one question i also want to change title bar with each click ..how can i do that?

  3. Parth Patel says:

    Can i add logo in Expandable ListView inside NavigationView

  4. Fırat says:

    public boolean onNavigationItemSelected(MenuItem item){}

    what is this for? Inside elements makes no sense and no source for them also in your post. I cant delete it since it needs this method but dont know what to put in it. Can you please explain,

  5. dani says:

    hi,
    first thanks for the tutorial and the code, really enjoyed playing with it.
    i have an issue with the back button, when i press it the app closes, i’v looked online for a solution but what i could find was about fragments and as you mentioned in one of your answers the code uses WebView.
    i changed the code so that external web links will also open in the web view but i want to also be able to go back to the previous page.
    i would really appreciate your help.
    thanks,
    dani

  6. kadecha says:

    how to set icon on menu content of navigation drawer

  7. varsha says:

    how to create grandchild

  8. Nishant says:

    Sir, the turorial is amazing but I am facing some issues..

    In Main activity.java, Where we have provided Url to the Fragments like Android Tutorials.. how can we use another activity.

    I tried putting my Url at your place as well.. but yours open within the app and mine in a browser..

    Please help Last submission date is near sir.

    1. Anupam says:

      We are opening the URL in a WebView. Hence it’s inside Application.

      1. Nishant Raina says:

        Sir .. if we want to use fragments instead of Webview.. What code will we use?

        I Know to use intent but where exactly to put intent in the code, i couldn’t figure it out.

        Kindly Help,
        Regards

  9. sandeep singh says:

    how to add the icon to the parent menu

  10. jimmy says:

    Hi, How to put down/up arrow only in that which have childmenu?

    1. Md. Abrar Chowdhury says:

      @Anupam Chugh .. You may update your code & give credit if possible.. 🙂 i tested it..
      In list_group_header.xml ::

      In ExpandableListAdapter ::

      @Override
      public View getGroupView(int groupPosition, boolean isExpanded,
      View convertView, ViewGroup parent) {
      String headerTitle = getGroup(groupPosition).menuName;
      if (convertView == null) {
      LayoutInflater infalInflater = (LayoutInflater) this.context
      .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
      convertView = infalInflater.inflate(R.layout.list_group_header, null);
      }

      TextView lblListHeader = convertView.findViewById(R.id.lblListHeader);
      ImageView imageView = convertView.findViewById(R.id.imgView_icon_listGroupHeader);
      lblListHeader.setTypeface(null, Typeface.BOLD);
      lblListHeader.setText(headerTitle);

      // Checking if has child
      if (getChildrenCount(groupPosition)==0)
      {
      // set visibility invisible
      imageView.setVisibility(View.INVISIBLE);
      }
      else
      {
      // set visibility visible
      imageView.setVisibility(View.VISIBLE);
      }

      return convertView;
      }

      1. Md. Abrar Chowdhury says:

        In list_group_header.xml ::

      2. Md. Abrar Chowdhury says:

        this website has bugs.. can’t remove comments
        and html/xml tags are blocked for some reason ..

        have beeing trying to comment the xml file.. it was not working .. so here is the link ..
        if links are blocked I don’t know how i will give the xml ..
        https://textuploader.com/dvejp

        1. Pankaj says:

          You need to escape HTML tags (< and >) while posting, otherwise it gets treated as HTML content and gets eaten up.

      3. Dharesh jariwala says:

        @Md. Abrar its work nice..thanx
        how can change image view to downarrow when click on header

      4. Parth Patel says:

        NOT WORK GIVE ME PROPER SUGGETION

  11. jimmy says:

    Hi, Very good and useful tutorial., thank u for that, I Am New In ANDROID. I want to Open New Activity when Click On Menu Item. Please Reply ASAP

      1. jimy says:

        thank you for the reply. but how ? here in your code there directly url call(webview). so please describe in which place I use INTENT?

        1. IMNK says:

          I know its a bit late, but you can edit that Second WebView code above with this

          if (childList.get(headerList.get(groupPosition)) != null) {
          MenuModel model = childList.get(headerList.get(groupPosition)).get(childPosition);
          if (groupPosition == 1 && childPosition == 0) {
          FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
          ft.replace(R.id.content_frame, new ProfileActivity());
          ft.commit();
          onBackPressed();

          I used Fragment, so this is the explanation. first, you have to make sure which group and child you want click to go to another activity.

          If the group didnt have any child, you can use this in the first WebView Code

          if (childList.get(headerList.get(groupPosition)) != null) {
          MenuModel model = childList.get(headerList.get(groupPosition));
          if (groupPosition == 0) {
          FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
          ft.replace(R.id.content_frame, new ProfileActivity());
          ft.commit();
          onBackPressed();

    1. Selva says:

      use intents.
      Intent intent=new Intent(context,sampleactivity.class);
      startActivity(intent);;

  12. Aparna says:

    Very useful tutorial. Instead of loading webview I want to open a new java class extended as fragment. Could u please help?

Leave a Reply

Your email address will not be published. Required fields are marked *

close
Generic selectors
Exact matches only
Search in title
Search in content
Search in posts
Search in pages