Mastering Custom ExpandableListView in Android

Snippet of programming code in IDE
Published on

Mastering Custom ExpandableListView in Android

The ExpandableListView in Android is a powerful UI component that allows you to create a list where items can be expanded or collapsed. It is particularly useful when you need to display hierarchical data. In this blog post, we will explore how to implement a custom ExpandableListView in Android, utilizing best practices and optimized code snippets.

What is ExpandableListView?

An ExpandableListView groups elements into parent-child relationships. When a parent item is clicked, its child items expand, revealing nested information. This is ideal for displaying categories and subcategories, much like a file directory structure.

Setting Up Your Project

Before diving into coding, make sure you have a basic Android project set up. You can create a new project using Android Studio with an empty activity.

Dependencies

No additional dependencies are necessary for the standard ExpandableListView because it is part of the Android framework. However, if you plan on using libraries for better visualization or data handling (like Retrofit for networking), make sure to include them in your build.gradle file.

Creating the Layout

Let's start with the layout for our ExpandableListView. In your res/layout folder, create an XML file named activity_main.xml and add the following code:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ExpandableListView
        android:id="@+id/expandableListView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

</RelativeLayout>

This layout contains only the ExpandableListView which will fill the entire screen.

Defining Data Structure

Before we start coding, we need to define the data we want to display. Create a HashMap with the data structure that contains parent and child items.

import android.os.Bundle;
import android.widget.ExpandableListView;

import androidx.appcompat.app.AppCompatActivity;

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

public class MainActivity extends AppCompatActivity {
    private ExpandableListView expandableListView;
    private ExpandableListAdapter expandableListAdapter;
    private List<String> listDataHeader;
    private HashMap<String, List<String>> listDataChild;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        expandableListView = findViewById(R.id.expandableListView);
        
        prepareListData();
        expandableListAdapter = new ExpandableListAdapter(this, listDataHeader, listDataChild);
        expandableListView.setAdapter(expandableListAdapter);
    }

    private void prepareListData() {
        listDataHeader = new ArrayList<>();
        listDataChild = new HashMap<>();

        listDataHeader.add("Fruit");
        listDataHeader.add("Vegetables");
        listDataHeader.add("Dairy");

        List<String> fruits = new ArrayList<>();
        fruits.add("Apple");
        fruits.add("Banana");
        fruits.add("Orange");

        List<String> vegetables = new ArrayList<>();
        vegetables.add("Carrot");
        vegetables.add("Broccoli");
        vegetables.add("Peas");

        List<String> dairy = new ArrayList<>();
        dairy.add("Milk");
        dairy.add("Cheese");
        dairy.add("Yogurt");

        listDataChild.put(listDataHeader.get(0), fruits);
        listDataChild.put(listDataHeader.get(1), vegetables);
        listDataChild.put(listDataHeader.get(2), dairy);
    }
}

Explanation:

  1. HashMap and List: This code creates a mapping from parent headers to their corresponding child items.
  2. Prepare Data: The prepareListData() method initializes the headers and children for the ExpandableListView.

Creating the Adapter

For the ExpandableListView to work, it requires an adapter. Create a new Java class named ExpandableListAdapter that extends BaseExpandableListAdapter.

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.HashMap;
import java.util.List;

public class ExpandableListAdapter extends BaseExpandableListAdapter {
    private Context context;
    private List<String> expandableListTitle;
    private HashMap<String, List<String>> expandableListDetail;

    public ExpandableListAdapter(Context context, List<String> expandableListTitle,
                                  HashMap<String, List<String>> expandableListDetail) {
        this.context = context;
        this.expandableListTitle = expandableListTitle;
        this.expandableListDetail = expandableListDetail;
    }

    @Override
    public Object getChild(int groupPosition, int childPosition) {
        return this.expandableListDetail.get(this.expandableListTitle.get(groupPosition)).get(childPosition);
    }

    @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 = (String) getChild(groupPosition, childPosition);

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

        TextView txtListChild = convertView.findViewById(android.R.id.text1);
        txtListChild.setText(childText);
        return convertView;
    }

    @Override
    public int getChildrenCount(int groupPosition) {
        return this.expandableListDetail.get(this.expandableListTitle.get(groupPosition)).size();
    }

    @Override
    public Object getGroup(int groupPosition) {
        return this.expandableListTitle.get(groupPosition);
    }

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

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

    @Override
    public View getGroupView(int groupPosition, boolean isExpanded,
                             View convertView, ViewGroup parent) {
        String headerTitle = (String) getGroup(groupPosition);

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

        TextView lblListHeader = convertView.findViewById(android.R.id.text1);
        lblListHeader.setText(headerTitle);
        return convertView;
    }

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

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

Explanation:

  1. View Holder Pattern: Instead of directly finding views each time, we are inflating the layout once and reusing it, enhancing performance.
  2. Methods: The adapter overrides necessary methods like getChild, getGroup, and getChildrenCount.

Running Your App

Now that you have set up your ExpandableListView and adapter, run your app. You should see a list with expandable children when the headers are clicked.

Customizing Your ExpandableListView

The above example uses Android's built-in layouts for simplicity. You can create custom layouts for your parent and child items to enhance your app's UI. For example, to customize the layout for parent items:

  1. Create a new layout XML file, list_group.xml:
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/lblListHeader"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:padding="8dp"
    android:textSize="18sp"
    android:textStyle="bold"
    android:background="#D6E9C6"/>
  1. Update getGroupView in the ExpandableListAdapter to use this layout.

The Bottom Line

The ExpandableListView is a highly useful UI component in Android that can display hierarchical data efficiently. By creating a custom adapter and managing your data with HashMap and Lists, you can tailor the component to fit your app's needs.

For more information on Android components, check out the official Android documentation. Experiment with colors, dimensions, and styles to make your expandable list fit nicely into your app's theme.

With this implementation, you can easily expand your app's functionality. The ExpandableListView can easily be integrated with other UI components, making it versatile and flexible. Happy coding!