Friday, January 27, 2012

How to use ExpandableListView

Working with ExpandableListView is very complex, so to make things simpler I will explain you how to use this ListView in few simple steps. This example will display 3 category of vehicles in a ExpandableList.

Firstly we will create the Layout files for Main Activity, GroupView and ChildView Layout.
MainActivity: main.xml


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



GroupView group_layout.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" >
     <ImageView android:id="@+id/ImageView01" 
                android:layout_height="40dip" android:layout_width="40dip" android:layout_marginLeft="40dip"></ImageView>
        <TextView android:id="@+id/tvGroup" android:layout_width="fill_parent"
                android:layout_height="45dip" android:text="Groups" android:gravity="center_vertical|right"
                android:paddingLeft="5dip" android:paddingRight="5dip"
                android:textColor="#ffffffff" android:textStyle="bold"
                android:textSize="17dip"></TextView>
</LinearLayout>

ChildView: child_layout.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" >
    <ImageView android:id="@+id/ImageView01" 
                android:layout_height="40dip" android:layout_width="40dip" android:layout_marginLeft="40dip"></ImageView>
        <TextView android:layout_width="fill_parent"
                android:layout_height="45dip" android:paddingLeft="5dip"
                android:paddingRight="5dip" android:textStyle="bold" android:textSize="17dip"
                android:gravity="center_vertical" android:id="@+id/tvChild"
                android:text="Children" android:textColor="#ffCCCC22"></TextView>
</LinearLayout>


MainActivity: ExpandableListViewAppActivity.java


import java.util.ArrayList;
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.View;
import android.widget.ExpandableListView;
import android.widget.ExpandableListView.OnChildClickListener;
import android.widget.ExpandableListView.OnGroupClickListener;
import android.widget.Toast;

public class ExpandableListViewAppActivity extends Activity implements Runnable {
private ExpandableListAdapter adapter;
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
     // Retrive the ExpandableListView from the layout
        ExpandableListView listView = (ExpandableListView) findViewById(R.id.listView);
        
        listView.setOnChildClickListener(new OnChildClickListener()
        {
            
            @Override
            public boolean onChildClick(ExpandableListView arg0, View arg1, int arg2, int arg3, long arg4)
            {
                Toast.makeText(getBaseContext(), "Child clicked", Toast.LENGTH_LONG).show();
                return false;
            }
        });
        
        listView.setOnGroupClickListener(new OnGroupClickListener()
        {
            
           
@Override
public boolean onGroupClick(ExpandableListView parent, View v,
int groupPosition, long id) {
// TODO Auto-generated method stub
Toast.makeText(getBaseContext(), "Group clicked", Toast.LENGTH_LONG).show();
return false;
}
        });

        // Initialize the adapter with blank groups and children
        // We will be adding children on a thread, and then update the ListView
        adapter = new ExpandableListAdapter(this, new ArrayList<String>(),
                new ArrayList<ArrayList<Vehicle>>());

        // Set this blank adapter to the list view
        listView.setAdapter(adapter);
        Thread thread = new Thread(this);
        thread.start();
    }
@Override
public void run() {
// TODO Auto-generated method stub
final int ITEMS = 15;
       int count = 0;
       while (count != ITEMS)
       {
           count++;
           adapter.addItem(MockDataProvider.getRandomVehicle("Vehicle no. " + count));

           // Notify the adapter
           handler.sendEmptyMessage(1);
           try
           {
               // Sleep for two seconds
               Thread.sleep(2000);
           }
           catch (InterruptedException e)
           {
               e.printStackTrace();
           }
       }
}
private Handler handler = new Handler()
   {
       @Override
       public void handleMessage(Message msg)
       {
           adapter.notifyDataSetChanged();
           super.handleMessage(msg);
       }
   };
}

BaseExpandableListAdapter: ExpandableListAdapter.java



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


public class ExpandableListAdapter extends BaseExpandableListAdapter{
@Override
    public boolean areAllItemsEnabled()
    {
        return true;
    }


    private Context context;
    private ArrayList<String> groups;
    private ArrayList<ArrayList<Vehicle>> children;
    public ExpandableListAdapter(Context context, ArrayList<String> groups,
            ArrayList<ArrayList<Vehicle>> children) {
        this.context = context;
        this.groups = groups;
        this.children = children;
    }


    /**
     * A general add method, that allows you to add a Vehicle to this list
     * 
     * Depending on if the category opf the vehicle is present or not,
     * the corresponding item will either be added to an existing group if it 
     * exists, else the group will be created and then the item will be added
     * @param vehicle
     */
    public void addItem(Vehicle vehicle) {
        if (!groups.contains(vehicle.getGroup())) {
            groups.add(vehicle.getGroup());
        }
        int index = groups.indexOf(vehicle.getGroup());
        if (children.size() < index + 1) {
            children.add(new ArrayList<Vehicle>());
        }
        children.get(index).add(vehicle);
    }


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


    @Override
    public long getChildId(int groupPosition, int childPosition) {
        return childPosition;
    }
    
    // Return a child view. You can load your custom layout here.
    @Override
    public View getChildView(int groupPosition, int childPosition, boolean isLastChild,
            View convertView, ViewGroup parent) {
        Vehicle vehicle = (Vehicle) getChild(groupPosition, childPosition);
        if (convertView == null) {
            LayoutInflater infalInflater = (LayoutInflater) context
                    .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            convertView = infalInflater.inflate(R.layout.child_layout, null);
        }
        TextView tv = (TextView) convertView.findViewById(R.id.tvChild);
        tv.setText("   " + vehicle.getName());


        // Depending upon the child type, set the imageTextView01
        tv.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0);
        /*if (vehicle instanceof Car) {
            tv.setCompoundDrawablesWithIntrinsicBounds(R.drawable.car, 0, 0, 0);
        } else if (vehicle instanceof Bus) {
            tv.setCompoundDrawablesWithIntrinsicBounds(R.drawable.bus, 0, 0, 0);
        } else if (vehicle instanceof Bike) {
            tv.setCompoundDrawablesWithIntrinsicBounds(R.drawable.bike, 0, 0, 0);
        }*/
        return convertView;
    }


    @Override
    public int getChildrenCount(int groupPosition) {
        return children.get(groupPosition).size();
    }


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


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


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


    // Return a group view. You can load your custom layout here.
    @Override
    public View getGroupView(int groupPosition, boolean isExpanded, View convertView,
            ViewGroup parent) {
        String group = (String) getGroup(groupPosition);
        if (convertView == null) {
            LayoutInflater infalInflater = (LayoutInflater) context
                    .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            convertView = infalInflater.inflate(R.layout.group_layout, null);
        }
        TextView tv = (TextView) convertView.findViewById(R.id.tvGroup);
        tv.setText(group);
        return convertView;
    }


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


    @Override
    public boolean isChildSelectable(int arg0, int arg1) {
        return true;
    }
}

Vehicle.java

public class Vehicle {
private String name;
    private String group;
    public String getGroup() {
        return group;
    }
    public void setGroup(String group) {
        this.group = group;
    }
    public Vehicle(String name) {
        this.name = name;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}

MockDataProvider.java

import java.util.Random;
public class MockDataProvider {
// A utility method that generates random Vehicles
    public static Vehicle getRandomVehicle(String name) {
        Vehicle vehicle = null;
        Random random = new Random();
        int type = random.nextInt(3);
        switch (type) {
            case 0:
                vehicle = new Car(name);
                break;
            case 1:
                vehicle = new Bus(name);
                break;
            case 2:
                vehicle = new Bike(name);
                break;
        }
        return vehicle;
    }
}

Car.java,Bus.java, Bike.java

public class Bike extends Vehicle{
public Bike(String name) {
        super(name);
        setGroup("Bikes");
    }
}