Android: Expandable ListView

In today’s tutorial I’d like to show you how to implement a ListView, that only displays a limited number of entries. With a button at the end of the list, the user can load more entries.
To achieve this goal, we first need to implement a basic Adapter that provides our ListView with the entries:

	private class ExpandableListAdapter extends BaseAdapter {
	    private List entries;
	    private Context context;
	    public ExpandableListAdapter(List entries) {
	        this.entries = entries;
	    }
	    @Override
	    public int getCount() {
	        return entries.size();
	    }
	    @Override
	    public Object getItem(int position) {
	        if (position >= 0 && position < entries.size())
	            return this.entries.get(position);
	        return null;
	    }
	    @Override
	    public long getItemId(int position) {
	        return position;
	    }
	    @Override
	    public View getView(int position, View convertView, ViewGroup parent) {
	        View view = null;
	        //reuse the convertView
	        if (convertView == null) {
	            view = getLayoutInflater().inflate(R.layout.row, null);
	        } else {
	            view = convertView;
	        }
	        String entry = entries.get(position);
	        view.setTag(position);
	        fillView(view, entry);
	        return view;
	    }
		private void fillView(View view, String entry) {
			TextView text = (TextView) view.findViewById(R.id.text);
			text.setText(entry);
		}
	}

For this example, I used a simple view for the rows, with just a TextView in it:

<?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" >
<TextView
android:id="@+id/text"
android:layout_width="match_parent"
android:layout_height="30dp"
android:gravity="center_vertical"
android:padding="5dp"/>
</LinearLayout>

Then we add the created adapter to a ListView (or a ListActivity like here):

public class ExpandableListActivity extends ListActivity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        List<String> entries = new ArrayList<String>();
        for (int i = 1; i <= 101; i++) {
            entries.add("Entry " + i);
        }
        setListAdapter(new ExpandableListAdapter(entries, this));
    }
    private class ExpandableListAdapter extends BaseAdapter{....}
}

The list as it is now shows all entries, so the next thing to do, is implementing the limiting function to the adapter.
Let’s start by giving our adapter a member variable for the limit:

private int limit = 20;

Next we need to modify some of the methods to get this working:
In getCount we need to either return the limit, or the size of the list, if the limit is greater than the list, because our list contains all entries and we only want to display a part of it.

 @Override
 public int getCount() {
     if (entries.size() <= limit) {
         return entries.size();
     }
     return limit;
 }

Next, we need to adjust the getItem method with an further clause in the if

if (position >= 0 && position < limit && position < entries.size())
                return entries.get(position);

Now for the biggest change for our new functionality: the implementation of the ‘more button’.

private LinearLayout getMoreView() {
		LinearLayout moreView = (LinearLayout) getLayoutInflater().inflate(R.layout.more_row, null);
		moreView.setOnClickListener(new View.OnClickListener() {
			public void onClick(View v) {
				listAdapter.increaseLimit();
			}
		});
		return moreView;
	}

Create another simple xml like this (disregarded i18n for this simple test case. Of course, Strings should normally be declared in the strings.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="wrap_content"
android:orientation="horizontal"
android:paddingLeft="20dp"
android:paddingRight="20dp" >
<TextView
android:id="@+id/MoreRowText"
android:layout_width="fill_parent"
android:layout_height="40dp"
android:text="Load more..."
android:gravity="center"/>
</LinearLayout>

And add a method to the adapter to increase the limit:

 public void increaseLimit() {
			limit+=20;
			//remove the button if we can no longer expand the list
			if (limit >= entries.size()) {
				getListView().removeFooterView(moreView);
			}
			//notify to redraw the list
			notifyDataSetChanged();
		}

At last, we simply add the button as a footer view to our list (After creation of the ListView, but before setting the ListAdapter to the ListView):

moreView = getMoreView();
listAdapter = new ExpandableListAdapter(entries);
getListView().addFooterView(moreView);
setListAdapter(listAdapter);

And that’s it for the simple case of  a static list 🙂
 
If you want to do this a bit more dynamic or you don’t want to load whole database table (or the whole internet) into your list, you have to do some extra work.

  1. Load the next X entries with an AsyncTask (display a ProgressDialog while it is getting the data!) and add a method to your adapter, that adds this entries to the list. Call this method from within the onPostExecute method of the AsyncTask
  2. You may want to keep track of the total number of entries you have to know when the user can’t load any more entries and to remove the button. (If the dataset is not bound to grow every few seconds. Otherwise, maybe let the user try to hit the button and see for himself, if theres new data)
  3. Keep in mind to notify your users if anything fails, like the next entries could not have been loaded, because they have no internet connection.

 
A Little homework for you: Combine it with the rounded corners example to make it look better 😛
 
Here’s the source, btw: ExpandableListview