ExpandableListView on Android
ListView is pretty widely used. There are situations when you would like to group/categorize your list items. To achieve such a thing on Android, you would probably use theExpandableListView. The data to the ExpandableListView is supplied by a special kind of adapter called the SimpleExpandableListAdapter which extends theBaseExpandableListAdapter.
As with any other widget on Android, you are free to customize the widgets as per your needs. Here, I will show how to create such a custom list adapter for the ExpandableListView.
For this example, I want to show a list of vehicles with their names. Also, I want to group them according to their category.
I have 4 classes.
1. Vehicle : The parent class for the rest.
2. Car: Extends the Vehicle class.
3. Bus: Extends the Vehicle class.
4. Bike: Extends the Vehicle class.
As with any other widget on Android, you are free to customize the widgets as per your needs. Here, I will show how to create such a custom list adapter for the ExpandableListView.
For this example, I want to show a list of vehicles with their names. Also, I want to group them according to their category.
I have 4 classes.
1. Vehicle : The parent class for the rest.
2. Car: Extends the Vehicle class.
3. Bus: Extends the Vehicle class.
4. Bike: Extends the Vehicle class.
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; | ||||||||||||||||||||||||
} | ||||||||||||||||||||||||
}
|
I have a method called getRandomVehicle(String name) which returns a random vehicle instance setting the name that I pass. The vehicle can be a Bus, Car or a Bike. Its completely random.
In the ExpandableListAdapter, (the custom adapter), there's a method called addItem(Vehicle vehicle), which manages the groups and their children.
import java.util.ArrayList; |
import com.beanie.example.list.R; |
import com.beanie.example.list.classes.Bike; |
import com.beanie.example.list.classes.Bus; |
import com.beanie.example.list.classes.Car; |
import com.beanie.example.list.classes.Vehicle; |
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; |
} |
} |
In the SampleActivity, I have initialized a blank ExpandableListAdapter and set it to the list view. Now, I start a thread, which gets a random vehicle after every 2 seconds, and adds it to the adapter, and calls the adapter to notify that the data has changed.
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.AdapterView; |
import android.widget.ExpandableListView; |
import android.widget.Toast; |
import android.widget.AdapterView.OnItemClickListener; |
import android.widget.ExpandableListView.OnChildClickListener; |
import android.widget.ExpandableListView.OnGroupClickListener; |
import com.beanie.example.list.adapter.ExpandableListAdapter; |
import com.beanie.example.list.classes.Vehicle; |
import com.beanie.example.list.data.MockDataProvider; |
public class SampleActivity 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 arg0, View arg1, int arg2, long arg3) |
{ |
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); |
// This thread randomly generates some vehicle types |
// At an interval of every 2 seconds |
Thread thread = new Thread(this); |
thread.start(); |
} |
@Override |
public void run() |
{ |
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); |
} |
}; |
} |
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; |
} |
} |
There are a few methods, in the ExpandableListAdapter, which you should go through carefully. I have two layout files, group_layout.xml and child_layout.xml which are used as the layout for the group views and the child views of the ExpandableListView.
child_layout.xml |
<LinearLayout android:id="@+id/LinearLayout01" | |||||||||||||||||||||
android:layout_width="fill_parent" android:layout_height="45dip" | |||||||||||||||||||||
xmlns:android="http://schemas.android.com/apk/res/android"> | |||||||||||||||||||||
<ImageView android:id="@+id/ImageView01" android:src="@drawable/icon" | |||||||||||||||||||||
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>
|
There are some more methods that you might be interested in, like, how to change the "arrow icon" for the group views(official doc link), or how to expand or collapse a group at will, or how to handle specific events like group collapsed or group expanded. Read the docs onExpandableListView.
No comments:
Post a Comment