Android ListViews and adapters

The ListView is an interface element that displays a list of items. The items in the list can have a custom layout consisting of other controls such as text or images. Managing a ListView is a bit more complex than you might think so we’ll walk through a relatively simple example here to see how all the parts fit together.

The app we’ll develop displays a Fragment on the left that contains an EditText into which the user can type some text, and a couple of buttons. The addItemButton adds an item to the ListView (displayed in a Fragment on the right) that contains the entered text along with the date and time at which the item was added. The clearListButton deletes all items from the list.

We’ve seen how to add Fragments to an Activity, so we’ll use the same technique here. First we create an XML file called enter_item.xml in the res–>layout folder and use Eclipse’s Graphical Layout editor to add the EditText and the two Buttons. Then we create a class to manage this layout, which looks like this:

package com.example.ex05listview;

import android.app.Activity;
import android.app.Fragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.EditText;

public class AddItemFragment extends Fragment {
	AddItemListener activityCallback;

	public interface AddItemListener {
		public void onAddItemClick(ListItem item);
		public void onClearListClick();
	}

	@Override
	public View onCreateView(LayoutInflater inflater, ViewGroup container,
			Bundle savedInstanceState) {
		final View view = inflater.inflate(R.layout.enter_item, container, false);
		Button addItemButton = (Button) view.findViewById(R.id.addItemButton);
		addItemButton.setOnClickListener(new OnClickListener() {

			@Override
			public void onClick(View arg0) {
				EditText itemText = (EditText) view.findViewById(R.id.itemText);
				ListItem newItem = new ListItem(itemText.getText().toString());
				activityCallback.onAddItemClick(newItem);
			}
		});

		Button clearListButton = (Button) view.findViewById(R.id.clearListButton);
		clearListButton.setOnClickListener(new OnClickListener() {

			@Override
			public void onClick(View v) {
				activityCallback.onClearListClick();
			}
		});
		return view;
	}

	@Override
	public void onAttach(Activity activity) {
		super.onAttach(activity);
		try {
			activityCallback = (AddItemListener) activity;
		} catch (ClassCastException e) {
			throw new ClassCastException(activity.toString() +
					" must implement AddItemListener");
		}
	}

}

This Fragment needs to send the new item to the ListView on the right and it does so via the MainActivity in order to keep the Fragments independent of each other. As before, we do this by defining an interface (line 16) which MainActivity must implement. In AddItemFragment’s onAttach() method, we test that the calling Activity implements the interface and store a reference to the Activity for future use.

In onCreateView() we inflate the layout and add event handlers to the two Buttons. The addItemButton handler retrieves the entered text from the EditText control and constructs a ListItem object. ListItem is as follows:

package com.example.ex05listview;

import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.TimeZone;

public class ListItem {
	private String dateAdded;
	private String message;

	public ListItem(String message)
	{
		this.setMessage(message);
		SimpleDateFormat dateFormat = new SimpleDateFormat("dd MMM yyyy HH:mm:ss");
		dateFormat.setTimeZone(TimeZone.getTimeZone("Europe/London"));
		Calendar cal = Calendar.getInstance();
		setDateAdded(dateFormat.format(cal.getTime()));
	}

	String getDateAdded() {
		return dateAdded;
	}

	void setDateAdded(String dateAdded) {
		this.dateAdded = dateAdded;
	}

	String getMessage() {
		return message;
	}

	void setMessage(String message) {
		this.message = message;
	}
}

The constructor uses Java’s SimpleDateFormat and Calendar classes to get the current date and time and format it into a String (lines 15 to 18).

Back in AddItemFragment, we then send newItem to MainActivity (line 32). MainActivity just passes the ListItem along to ItemListFragment. Here’s the code for MainActivity:

package com.example.ex05listview;

import com.example.ex05listview.AddItemFragment.AddItemListener;

import android.os.Bundle;
import android.app.Activity;

public class MainActivity extends Activity
implements AddItemListener {
	ItemListFragment itemListFragment;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		itemListFragment = (ItemListFragment) getFragmentManager().findFragmentById(R.id.itemListFragment);
	}

	@Override
	public void onAddItemClick(ListItem item) {
		itemListFragment.addItem(item);
	}

	@Override
	public void onClearListClick() {
		itemListFragment.clearList();
	}
}

We’ll get to ItemListFragment in a minute. First, we add the event handler for clearListButton in AddItemFragment (line 37) which calls onClearListClick() in MainActivity, which again just passes this along to ItemListFragment.

To build the ItemListFragment, we create a new XML file in res–>layout, but all we need to do is set the initial layout to a ListView, with an ID of “@+id/list”. Here’s the complete XML file:

<?xml version="1.0" encoding="utf-8"?>
<ListView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/list"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >
</ListView>

All lines except the android:id should be created automatically in Eclipse. Here’s ItemListFragment, the Java class that manages the list fragment:

package com.example.ex05listview;

import android.app.Fragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;

public class ItemListFragment extends Fragment {
	ItemListAdapter adapter;

	@Override
	public View onCreateView(LayoutInflater inflater, ViewGroup container,
			Bundle savedInstanceState) {
		adapter = new ItemListAdapter(getActivity());
		adapter.addItem(new ListItem("Test item"));
		View view = inflater.inflate(R.layout.list_fragment, container, false);
		ListView listView = (ListView) view.findViewById(R.id.list);
		listView.setAdapter(adapter);
		listView.setOnItemClickListener(new OnItemClickListener() {

			@Override
			public void onItemClick(AdapterView<?> parent, View item, int position,
					long id) {
				TextView itemText = (TextView) item.findViewById(R.id.messageItem);
				Toast.makeText(getActivity(), itemText.getText().toString(),
						Toast.LENGTH_SHORT).show();
			}
		});
		return view;
	}

	public void addItem(ListItem item)
	{
		adapter.addItem(item);
	}

	public void clearList()
	{
		adapter.clearItems();
	}
}

On line 20, we create an ItemListAdapter. A list adapter is a class that manages the data behind a list. There are several pre-defined adapter classes, but it’s easy enough to write a custom one, so here’s ItemListAdapter:

package com.example.ex05listview;

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

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.TextView;

public class ItemListAdapter extends BaseAdapter {
	List<ListItem> itemList = new ArrayList<ListItem>();
	Context context;

	public ItemListAdapter(Context c) {
		context = c;
	}

	@Override
	public int getCount() {
		return itemList.size();
	}

	@Override
	public Object getItem(int position) {
		return itemList.get(position);
	}

	@Override
	public long getItemId(int position) {
		return position;
	}

	@Override
	public View getView(int position, View arg1, ViewGroup parent) {
		LayoutInflater inflater = (LayoutInflater) context.getSystemService(
				Context.LAYOUT_INFLATER_SERVICE);
		View itemView = inflater.inflate(R.layout.list_item, parent, false);
		TextView dateText = (TextView)itemView.findViewById(R.id.dateTimeAdded);
		TextView message = (TextView)itemView.findViewById(R.id.messageItem);
		dateText.setText(itemList.get(position).getDateAdded());
		message.setText(itemList.get(position).getMessage());
		return itemView;
	}

	public void addItem(ListItem item)
	{
		itemList.add(item);
		notifyDataSetChanged();
	}

	public void clearItems() {
		itemList.clear();
		notifyDataSetChanged();
	}
}

We’ve extended BaseAdapter, which is a built-in Android abstract class containing four methods we must implement which we’ll get to in a minute.

The adapter must manage the data that is displayed in the ListView as well as provide the View object for each individual element in the list. Here, we’ve stored the data in a generic List<ListItem> (line 14), though in a real world application, we’d use a more permanent location for the data. In this app, the data must be entered from scratch every time the app is run.

To add an item to the list, we use addItem() (line 48). After the item is added to the itemList object, we must call notifyDataSetChanged() to update the ListView display on screen. Similarly, to clear the list, we use clearItems() (line 54).

The three methods on lines 22 to 34 just implement methods defined in the abstract base class and should be self-explanatory. (getItemId() returns an ID number for a list item; it’s up to us how we define this ID, so we’ve just taken it to be the position of the item in the list.)

The main work in the adapter is done in creating the View (line 37). As usual, we use a LayoutInflater to inflate the layout from its XML file (which just defines two TextViews, one of the date and one for the message). We need a ‘context’ to get the inflater, so if you refer back to line 20 in the code for ItemListFragment above, you’ll see we pass in the Activity that contains the Fragment so it’s used as the context in the adapter.

Once we’ve created the View, we need to set the values of its two TextViews which we do by retrieving the data from itemList.

Finally, back in ItemListFragment (line 25) we add an event handler for a click on a list item. This pops up a Toast notification (a little black rectangle that appears for a couple of seconds on the display) containing the text of the item clicked.

Obviously there are a lot of other tweaks we could add to this program, but it should give you enough information to include a ListView in an app.

Post a comment or leave a trackback: Trackback URL.

Trackbacks

  • […] (line 10), its layout is based on a ListView, which we obtain in line 21. Recall that we need an adapter to manage a ListView. Rather than write our own, we can use Android’s SimpleCursorAdapter. The […]

Leave a comment