Back to top

vogella training Training Books

Android ListView - Tutorial

Lars Vogel

Version 4/2

05.04.2013

Revision History
Revision 0.1 01.07.2010 Lars
Vogel
Created
Revision 0.2 - 4.1 03.07.2010 - 05.04.2013 Lars
Vogel
bug fixes and enhancements

Using Android ListView, ListActivity and ListFragment

This tutorial describes how to use the ListView view together with Activities and Fragments in in Android. The tutorial is based on Eclipse 4.2, Java 1.6 and Android 4.2.


Table of Contents

1. Android and Lists
1.1. ListView
1.2. Adapters
1.3. ListView example
1.4. Input for ArrayAdapter
1.5. Add and remove data
1.6. Listener
1.7. Filtering and Sorting
2. Adapter implementations
2.1. Developing a custom Adapter
2.2. Example Adapter
2.3. Possible input to the adapter and interaction
2.4. Data updates in the Adapter
3. ListActivity
3.1. Overview
3.2. ListActivity and Layout
4. ListFragment
5. ListViews and performance
5.1. Time consuming operations
5.2. ConvertView
5.3. Holder Pattern
5.4. Example
6. Contextual action mode for ListViews
7. Implementing undo for an action
8. Exercise: Using ListView and ListActivity
9. Tutorial: ListActivity with own layout
10. Tutorial: Implementing your own adapter
11. Performance Optimization
12. Tutorial: How to display two items in a ListView
13. Selecting multiple items in the ListView
13.1. Interaction between the model and Listview
13.2. Tutorial: Domain Model and Rows interaction
14. Tutorial: Miscellaneous
14.1. Adding a longclick listener to the list items
14.2. Multi vs. single Selection
14.3. ExpandableListView
14.4. Header and Footer
15. SimpleCursorAdapter
16. Additional Open Source libraries
17. Thank you
18. Questions and Discussion
19. Links and Literature
19.1. Source Code
19.2. ListView Resources
19.3. vogella Resources

1. Android and Lists

1.1. ListView

The display of elements in a lists is a very common pattern in mobile applications. The user sees a list of items and can scroll through them. Typically the user interacts with the list via the ActionBar, for example via a refresh button.

Listview example

Individual list item can be selected, this selection can update the ActionBar or can trigger a detailed screen for the selection.

Listview example

Android provides the ListView class which is capable of displaying a scrollable list of items. These items can be of any type.

1.2. Adapters

Adapters are used to provide the data to the ListView object. The adapter also defines how each row is the ListView is displayed.

The adapter is assigned to the ListView via the setAdapter method on the ListView object.

An adapter extend the BaseAdapter class. Android provides some standard adapters; the most important are ArrayAdapter and CursorAdapter.

ArrayAdapter can handle data based on Arrays or java.util.List.

SimpleCursorAdapter can handle database related data. This description focuses on the non database case.

1.3. ListView example

The following listing shows a layout file with includes a ListView.

<ListView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/listview"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content" /> 

This layout file, called activity_listviewexampleactivity.xml is used in the following code. This example shows the usage of the ListView view. It includes the removal of list items and uses animations for the removal.

package com.vogella.android.listview.withanimation;

public class ListViewExampleActivity extends Activity {

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

    final ListView listview = (ListView) findViewById(R.id.listview);
    String[] values = new String[] { "Android", "iPhone", "WindowsMobile",
        "Blackberry", "WebOS", "Ubuntu", "Windows7", "Max OS X",
        "Linux", "OS/2", "Ubuntu", "Windows7", "Max OS X", "Linux",
        "OS/2", "Ubuntu", "Windows7", "Max OS X", "Linux", "OS/2",
        "Android", "iPhone", "WindowsMobile" };

    final ArrayList<String> list = new ArrayList<String>();
    for (int i = 0; i < values.length; ++i) {
      list.add(values[i]);
    }
    final StableArrayAdapter adapter = new StableArrayAdapter(this,
        android.R.layout.simple_list_item_1, list);
    listview.setAdapter(adapter);

    listview.setOnItemClickListener(new AdapterView.OnItemClickListener() {

      @Override
      public void onItemClick(AdapterView<?> parent, final View view,
          int position, long id) {
        final String item = (String) parent.getItemAtPosition(position);
        view.animate().setDuration(2000).alpha(0)
            .withEndAction(new Runnable() {
              @Override
              public void run() {
                list.remove(item);
                adapter.notifyDataSetChanged();
                view.setAlpha(1);
              }
            });
      }

    });
  }

  private class StableArrayAdapter extends ArrayAdapter<String> {

    HashMap<String, Integer> mIdMap = new HashMap<String, Integer>();

    public StableArrayAdapter(Context context, int textViewResourceId,
        List<String> objects) {
      super(context, textViewResourceId, objects);
      for (int i = 0; i < objects.size(); ++i) {
        mIdMap.put(objects.get(i), i);
      }
    }

    @Override
    public long getItemId(int position) {
      String item = getItem(position);
      return mIdMap.get(item);
    }

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

  }

} 

1.4. Input for ArrayAdapter

The ArrayAdapter class can handle any Java object as input. It maps the data of this input to a TextView in the layout. You can define one in the constructor otherwise the android.R.id.text1 ID will be used.

ArrayAdapter uses the toString() method of the data input object to determine the String which should be displayed.

1.5. Add and remove data

The ArrayAdapter class allows to remove all elements in its underlying data structure with the clear() method call. You can then add new elements via the add() method or a Collection via the addAll() method.

You can also directly modify the underlying data structure and call the notifyDataSetChanged() method on the adapter to notify him about the changes in data.

Warning

If you want to change the data in your adapter the underlying data structure must support this operation. This is for example the case for the ArrayList class but not for arrays.

1.6. Listener

To react to selections in the list set an OnItemClickListener to your ListView.

listView.setOnItemClickListener(new OnItemClickListener() {
  @Override
  public void onItemClick(AdapterView<?> parent, View view,
    int position, long id) {
    Toast.makeText(getApplicationContext(),
      "Click ListItem Number " + position, Toast.LENGTH_LONG)
      .show();
  }
}); 

1.7. Filtering and Sorting

Filtering and sorting is handled by the Adapter. You need to implement the logic in your custom Adapter implementation.

2. Adapter implementations

2.1. Developing a custom Adapter

To control the data assignment and to support this assignment to several Views, you create your own Adapter implementation. For this you would extend an existing adapter implementations or by sub-classing the BaseAdapter class directly.

ListView calls the getView() method on the adapter for each data element. In this method the adapter determines the layout of the row and how the data is mapped to the Views in this layout.

This root of the layout is typically a ViewGroup (LayoutManager) and contains several other Views, e.g. an ImageView and a TextView.

Adapter provides data and defines the layout per row

Within the getView() method you would inflate an XML based layout and then set the values of the individual Views in the layout. For inflating an XML layout you can use the system service LayoutInflator. This service can get accessed via the activity or via the context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) method call.

The individual elements in the layout can be found via the findViewById() method call.

2.2. Example Adapter

The following shows an implementation of an own adapter. This adapter assumes that you have two png files (no.png and yes.png) in one of your res/drawable folders. The coding inflates an XML layout file, finds the relevant Views in the layout and sets their content based on the input data.

package de.vogella.android.listactivity;

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

public class MySimpleArrayAdapter extends ArrayAdapter<String> {
  private final Context context;
  private final String[] values;

  public MySimpleArrayAdapter(Context context, String[] values) {
    super(context, R.layout.rowlayout, values);
    this.context = context;
    this.values = values;
  }

  @Override
  public View getView(int position, View convertView, ViewGroup parent) {
    LayoutInflater inflater = (LayoutInflater) context
        .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    View rowView = inflater.inflate(R.layout.rowlayout, parent, false);
    TextView textView = (TextView) rowView.findViewById(R.id.label);
    ImageView imageView = (ImageView) rowView.findViewById(R.id.icon);
    textView.setText(values[position]);
    // Change the icon for Windows and iPhone
    String s = values[position];
    if (s.startsWith("iPhone")) {
      imageView.setImageResource(R.drawable.no);
    } else {
      imageView.setImageResource(R.drawable.ok);
    }

    return rowView;
  }
} 

2.3. Possible input to the adapter and interaction

An adapter can receive any Java object as input. In its getView() method it extracts the correct data from the data object and assigns this data to the views in the row which is representing the data.

The row can also contain views which interact with the underlying data model via the adapter. For example you can have a Checkbox in your row layout and if the Checkbox is selected, the underlying data is changed.

2.4. Data updates in the Adapter

The notifyDataSetChanged() method on the adapter is called if the data has changed or if new data is available.

The notifyDataSetInvalidated() method is called if the data is not available anymore.

3. ListActivity

3.1. Overview

The ListActivity class which extends the activity class was designed to simplify the handling of ListViews.

It you do not assign a layout to a ListActivity it contains a default ListView and defines the onListItemClick() method for handling selection of list items. Internally the ListActivity registers an OnItemClickListener on the ListView.

ListActivity allows to set the adapter to the ListView via the setListAdapter() method.

package de.vogella.android.listactivity;

import android.app.ListActivity;
import android.os.Bundle;
import android.widget.ArrayAdapter;

public class MyListActivity extends ListActivity {
  public void onCreate(Bundle icicle) {
    super.onCreate(icicle);
    String[] values = new String[] { "Android", "iPhone", "WindowsMobile",
        "Blackberry", "WebOS", "Ubuntu", "Windows7", "Max OS X",
        "Linux", "OS/2" };
    ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,
        android.R.layout.simple_list_item_1, values);
    setListAdapter(adapter);
  }
} 

3.2. ListActivity and Layout

ListActivity does not require that you assign a layout to it via the setContentView() method, if you only want to show a ListView ListActivity contains by default a ListView.

In case you need to include more Views than a ListView in your ListActivity you can still assign a layout to your activity. In this case your layout must contain a ListView with the android:id attribute set to @android:id/list.

<ListView
  android:id="@android:id/list"
  android:layout_width="match_parent"
  android:layout_height="wrap_content" >
</ListView> 

If you use a View with the @android:id/empty ID in your layout, ListActivity will automatically show this View if the ListView is empty and hide it otherwise. For example you could display here an error message.

4. ListFragment

A ListView can be used in Fragments.

Android provides the ListFragment class which provides simplifications for list handling. For example you can directly call the setListAdapter() method to assign the adapter. This is similar to the usage of ListActivity activities.

The following example code shows a simple ListFragment implementation.

package de.vogella.android.fragments;

import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.app.ListFragment;

public class MyListFragment extends ListFragment {

  @Override
  public void onActivityCreated(Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);
    String[] values = new String[] { "Android", "iPhone", "WindowsMobile",
        "Blackberry", "WebOS", "Ubuntu", "Windows7", "Max OS X",
        "Linux", "OS/2" };
    ArrayAdapter<String> adapter = new ArrayAdapter<String>(getActivity(),
        android.R.layout.simple_list_item_1, values);
    setListAdapter(adapter);
  }

  @Override
  public void onListItemClick(ListView l, View v, int position, long id) {
    // Do something with the data

  }
} 

5. ListViews and performance

5.1. Time consuming operations

Every View which get inflated from an XML layout file will result in a Java object. Creating Java objects is expensive with regards to time and memory consumption.

In addition using the findViewById() method is relatively time consuming, even though it is not as bad as XML inflating.

This part describes how to reduce these operations to make your ListView faster. The default Android adapters like ArrayAdapter are already performance optimized.

5.2. ConvertView

A ListView typically contain more data then the number of displayed rows. If the user scrolls the list then rows and their associated Views will be scrolled out of the visible area. The Java objects which represents the rows can be reused for newly visible rows.

If Android determines that a View which represents a row is not visible anymore it allows the getView() method to reuse it via the convertView parameter.

A performance optimized adapter assigns the new data to the convertView. This avoids inflating an XML file and creating new Java objects.

In case no View is available for reuse, Android will pass null to the convertView parameter. Therefore the adapter implementation need to check for this.

5.3. Holder Pattern

The View Holder pattern allows to avoid the findViewById() method call for a reused convertView.

A ViewHolder class is a static inner class in your adapter which hold references to the relevant Views in your layout. This reference is assigned to the View which represent the row layout as a tag via the setTag() method.

If we receive a convertView object, we can get the instance of the ViewHolder via the getTag() method and assign the new attributes to the Views via the ViewHolder reference.

While this sounds complex this is approx. 15 % faster then using the findViewById() method.

5.4. Example

The following code shows a performance optimized adapter implementation which reuses existing views and implements the holder pattern.

package de.vogella.android.listactivity;

import android.app.Activity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.TextView;

public class MyPerformanceArrayAdapter extends ArrayAdapter<String> {
  private final Activity context;
  private final String[] names;

  static class ViewHolder {
    public TextView text;
    public ImageView image;
  }

  public MyPerformanceArrayAdapter(Activity context, String[] names) {
    super(context, R.layout.rowlayout, names);
    this.context = context;
    this.names = names;
  }

  @Override
  public View getView(int position, View convertView, ViewGroup parent) {
    View rowView = convertView;
    if (rowView == null) {
      LayoutInflater inflater = context.getLayoutInflater();
      rowView = inflater.inflate(R.layout.rowlayout, null);
      ViewHolder viewHolder = new ViewHolder();
      viewHolder.text = (TextView) rowView.findViewById(R.id.TextView01);
      viewHolder.image = (ImageView) rowView
          .findViewById(R.id.ImageView01);
      rowView.setTag(viewHolder);
    }

    ViewHolder holder = (ViewHolder) rowView.getTag();
    String s = names[position];
    holder.text.setText(s);
    if (s.startsWith("Windows7") || s.startsWith("iPhone")
        || s.startsWith("Solaris")) {
      holder.image.setImageResource(R.drawable.no);
    } else {
      holder.image.setImageResource(R.drawable.ok);
    }

    return rowView;
  }
} 

6. Contextual action mode for ListViews

For following assumes that you already familiar with the concept of the ActionBar and contextual action mode in general. In part will explain how to use contextual action mode for a ListView selection.

To assign a contextual action mode to a long click on a individual item, use the method setOnItemLongClickListener() on ListView. This methods includes information about the selected item. In this method you can start the ActionMode.

The following examples demonstrates that, it assumes that you have a menu XML file defined called "rowselection.xml" and that this menu contains one entry with the "@+id/menuitem1_show" ID.

package de.vogella.android.listactivity;

import android.app.ListActivity;
import android.os.Bundle;
import android.view.ActionMode;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemLongClickListener;
import android.widget.Toast;

public class MyListActivityActionbar extends ListActivity {

  protected Object mActionMode;
  public int selectedItem = -1;

  public void onCreate(Bundle icicle) {
    super.onCreate(icicle);
    setContentView(R.layout.main);
    String[] values = new String[] { "Android", "iPhone", "WindowsMobile",
        "Blackberry", "WebOS", "Ubuntu", "Windows7", "Max OS X",
        "Linux", "OS/2", "Ubuntu", "Windows7", "Max OS X", "Linux",
        "OS/2", "Ubuntu", "Windows7", "Max OS X", "Linux", "OS/2" };

    MySimpleArrayAdapter adapter = new MySimpleArrayAdapter(this, values);
    setListAdapter(adapter);

    getListView().setOnItemLongClickListener(new OnItemLongClickListener() {

      @Override
      public boolean onItemLongClick(AdapterView<?> parent, View view,
          int position, long id) {

        if (mActionMode != null) {
          return false;
        }
        selectedItem = position;

        // Start the CAB using the ActionMode.Callback defined above
        mActionMode = MyListActivityActionbar.this
            .startActionMode(mActionModeCallback);
        view.setSelected(true);
        return true;
      }
    });
  }

  private ActionMode.Callback mActionModeCallback = new ActionMode.Callback() {

    // Called when the action mode is created; startActionMode() was called
    public boolean onCreateActionMode(ActionMode mode, Menu menu) {
      // Inflate a menu resource providing context menu items
      MenuInflater inflater = mode.getMenuInflater();
      // Assumes that you have "contexual.xml" menu resources
      inflater.inflate(R.menu.rowselection, menu);
      return true;
    }

    // Called each time the action mode is shown. Always called after
    // onCreateActionMode, but
    // may be called multiple times if the mode is invalidated.
    public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
      return false; // Return false if nothing is done
    }

    // Called when the user selects a contextual menu item
    public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
      switch (item.getItemId()) {
      case R.id.menuitem1_show:
        show();
        // Action picked, so close the CAB
        mode.finish();
        return true;
      default:
        return false;
      }
    }

    // Called when the user exits the action mode
    public void onDestroyActionMode(ActionMode mode) {
      mActionMode = null;
      selectedItem = -1;
    }
  };

  private void show() {
    Toast.makeText(MyListActivityActionbar.this,
        String.valueOf(selectedItem), Toast.LENGTH_LONG).show();
  }

} 

If you start your application and long press on an item in the list, you get your contextual ActionBar menu.

Using the contextual ActionBar on ListView

7. Implementing undo for an action

It is good practice to allow an user to undo critical actions, e.g. deleting items. A good pattern for this is to offer a selection at the end of the screen which vanishes after a while or once the user selects another element.

For example the Gmail application implements such a behavior.

Undo button

The following describes an example how you can implement such a behavior. Is also uses an animation to phase the buton bar automatically out after a small while.

Create for this example a new project called com.vogella.android.userinterface.undo based on the BlankTemplate template.

Create the following layout for your activity. It uses a FrameLayout to show two different parts of the user interface. The button bar is initial hidden. The button uses a drawable, either add such a drawable to your project or remove the reference.

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

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity" >

        <ListView
            android:id="@+id/listview"
            android:layout_width="match_parent"
            android:layout_height="match_parent" >
        </ListView>
    </RelativeLayout>

    <LinearLayout
        android:id="@+id/undobar"
        android:visibility="gone"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom|center_horizontal"
        android:layout_margin="20dp"
        android:alpha="100"
        android:background="#808080"
        android:dividerPadding="11dp"
        android:padding="4dp" >

        <TextView
            android:id="@+id/undobar_message"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Deleted"
            android:textAppearance="?android:attr/textAppearanceMedium"
            android:textColor="#fff" />

        <Button
            android:id="@+id/undobar_button"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginLeft="30dp"
            android:onClick="onClick"
            android:background="#808080"
            android:drawableLeft="@drawable/ic_undobar_undo"
            android:text="Undo"
            android:textAppearance="?android:attr/textAppearanceMedium"
            android:textColor="#fff" />
    </LinearLayout>

</FrameLayout> 

Change your activity so that it is similar to the following code. We reuse the ActionBar as created by the template.

package com.vogella.android.userinterface.undo;

import android.app.Activity;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.Toast;

import com.vogella.android.actionbar.undo.R;

public class MainActivity extends Activity {

  private View viewContainer;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    ListView l = (ListView) findViewById(R.id.listview);
    String[] values = new String[] { "Ubuntu", "Android", "iPhone",
        "Windows", "Ubuntu", "Android", "iPhone", "Windows" };
    ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,
        android.R.layout.simple_list_item_1, values);
    viewContainer = findViewById(R.id.undobar);
    l.setAdapter(adapter);
  }

  @Override
  public boolean onCreateOptionsMenu(Menu menu) {
    // Inflate the menu; this adds items to the action bar if it is present.
    getMenuInflater().inflate(R.menu.activity_main, menu);
    return true;
  }

  @Override
  public boolean onOptionsItemSelected(MenuItem item) {
    showUndo(viewContainer);
    return true;
  }

  public void onClick(View view) {
    Toast.makeText(this, "Deletion undone", Toast.LENGTH_LONG).show();
    viewContainer.setVisibility(View.GONE);
  }

  public static void showUndo(final View viewContainer) {
    viewContainer.setVisibility(View.VISIBLE);
    viewContainer.setAlpha(1);
    viewContainer.animate().alpha(0.4f).setDuration(5000)
        .withEndAction(new Runnable() {

          @Override
          public void run() {
            viewContainer.setVisibility(View.GONE);
          }
        });

  }
} 

If you select the entry in the ActionBar the button bar becomes visible for 5 seconds.

Undo button

8. Exercise: Using ListView and ListActivity

The following exercise demonstrates how to use a ListView in an ListActivity. You use the predefined ArrayAdapter class and an existing Android layout for the rows.

Create a new Android project called de.vogella.android.listactivity with the activity called MyListActivity.

Change MyListActivity to the following. Note that the setContentView() method is not used.

package de.vogella.android.listactivity;

import android.app.ListActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.Toast;

public class MyListActivity extends ListActivity {
  public void onCreate(Bundle icicle) {
    super.onCreate(icicle);
    String[] values = new String[] { "Android", "iPhone", "WindowsMobile",
        "Blackberry", "WebOS", "Ubuntu", "Windows7", "Max OS X",
        "Linux", "OS/2" };
    ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,
        android.R.layout.simple_list_item_1, values);
    setListAdapter(adapter);
  }

  @Override
  protected void onListItemClick(ListView l, View v, int position, long id) {
    String item = (String) getListAdapter().getItem(position);
    Toast.makeText(this, item + " selected", Toast.LENGTH_LONG).show();
  }
} 

ListActivity shows the items

9. Tutorial: ListActivity with own layout

In our example your will define your layout for the rows and use it in your adapter.

Create the "rowlayout.xml" layout file in the res/layout folder of the "de.vogella.android.listactivity" project.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content" >

    <ImageView
        android:id="@+id/icon"
        android:layout_width="22px"
        android:layout_height="22px"
        android:layout_marginLeft="4px"
        android:layout_marginRight="10px"
        android:layout_marginTop="4px"
        android:src="@drawable/ic_launcher" >
    </ImageView>

    <TextView
        android:id="@+id/label"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@+id/label"
        android:textSize="20px" >
    </TextView>

</LinearLayout> 

Change your activity so that is using the new layout. You use a different constructor to identify the View to which the ArrayAdapter assigns the text. If this ID is not provides Android searches for an element with the @android:id/text1 ID in the layout of the row.

package de.vogella.android.listactivity;

import android.app.ListActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.Toast;

public class MyListActivity extends ListActivity {
  public void onCreate(Bundle icicle) {
    super.onCreate(icicle);
    String[] values = new String[] { "Android", "iPhone", "WindowsMobile",
        "Blackberry", "WebOS", "Ubuntu", "Windows7", "Max OS X",
        "Linux", "OS/2" };
    // Use your own layout
    ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,
        R.layout.rowlayout, R.id.label, values);
    setListAdapter(adapter);
  }

  @Override
  protected void onListItemClick(ListView l, View v, int position, long id) {
    String item = (String) getListAdapter().getItem(position);
    Toast.makeText(this, item + " selected", Toast.LENGTH_LONG).show();
  }
} 

Displaying a ListView with a own layout

10. Tutorial: Implementing your own adapter

The following uses two images "no.png" and "ok.png". I placed it in the "res/drawable-mdpi" folder. You must create your own icons. In case you do not find any icons just copy "icon.png" and use a drawing program to change it a little bit.

Create the class MySimpleArrayAdapter which will serve as our adapter.

package de.vogella.android.listactivity;

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

public class MySimpleArrayAdapter extends ArrayAdapter<String> {
  private final Context context;
  private final String[] values;

  public MySimpleArrayAdapter(Context context, String[] values) {
    super(context, R.layout.rowlayout, values);
    this.context = context;
    this.values = values;
  }

  @Override
  public View getView(int position, View convertView, ViewGroup parent) {
    LayoutInflater inflater = (LayoutInflater) context
        .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    View rowView = inflater.inflate(R.layout.rowlayout, parent, false);
    TextView textView = (TextView) rowView.findViewById(R.id.label);
    ImageView imageView = (ImageView) rowView.findViewById(R.id.icon);
    textView.setText(values[position]);
    // Change the icon for Windows and iPhone
    String s = values[position];
    if (s.startsWith("Windows7") || s.startsWith("iPhone")
        || s.startsWith("Solaris")) {
      imageView.setImageResource(R.drawable.no);
    } else {
      imageView.setImageResource(R.drawable.ok);
    }

    return rowView;
  }
} 

To use this adapter, change the activity to the following.

package de.vogella.android.listactivity;

import android.app.ListActivity;
import android.os.Bundle;

public class MyListActivity extends ListActivity {
  public void onCreate(Bundle icicle) {
    super.onCreate(icicle);
    String[] values = new String[] { "Android", "iPhone", "WindowsMobile",
        "Blackberry", "WebOS", "Ubuntu", "Windows7", "Max OS X",
        "Linux", "OS/2" };
    MySimpleArrayAdapter adapter = new MySimpleArrayAdapter(this, values);
    setListAdapter(adapter);
  }

} 

If you run this example you should get a list with different icons for the certain elements.

ListView with different elements for each row

11. Performance Optimization

The following will implement a performance optimized version of the adapter from the previous example.

Create the following MyPerformanceArrayAdapter class.

package de.vogella.android.listactivity;

import android.app.Activity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.TextView;

public class MyPerformanceArrayAdapter extends ArrayAdapter<String> {
  private final Activity context;
  private final String[] names;

  static class ViewHolder {
    public TextView text;
    public ImageView image;
  }

  public MyPerformanceArrayAdapter(Activity context, String[] names) {
    super(context, R.layout.rowlayout, names);
    this.context = context;
    this.names = names;
  }

  @Override
  public View getView(int position, View convertView, ViewGroup parent) {
    View rowView = convertView;
    if (rowView == null) {
      LayoutInflater inflater = context.getLayoutInflater();
      rowView = inflater.inflate(R.layout.rowlayout, null);
      ViewHolder viewHolder = new ViewHolder();
      viewHolder.text = (TextView) rowView.findViewById(R.id.TextView01);
      viewHolder.image = (ImageView) rowView
          .findViewById(R.id.ImageView01);
      rowView.setTag(viewHolder);
    }

    ViewHolder holder = (ViewHolder) rowView.getTag();
    String s = names[position];
    holder.text.setText(s);
    if (s.startsWith("Windows7") || s.startsWith("iPhone")
        || s.startsWith("Solaris")) {
      holder.image.setImageResource(R.drawable.no);
    } else {
      holder.image.setImageResource(R.drawable.ok);
    }

    return rowView;
  }
} 

Use your new adapter in your activity. If you run the application it should look the same but it will be much faster, especially for large datasets.

package de.vogella.android.listactivity;

import android.app.ListActivity;
import android.os.Bundle;

public class MyListActivity extends ListActivity {
  public void onCreate(Bundle icicle) {
    super.onCreate(icicle);
    String[] values = new String[] { "Android", "iPhone", "WindowsMobile",
        "Blackberry", "WebOS", "Ubuntu", "Windows7", "Max OS X",
        "Linux", "OS/2" };
    setListAdapter(new MyPerformanceArrayAdapter(this, values));
  }

} 

12. Tutorial: How to display two items in a ListView

You can use the SimpleAdapter class to show the data of two elements. This class expects a Array of Strings (from data) in which the fields of the input data are defined. It also requires a Array of ints which defines the IDs of the widgets in the layout for the row to which these fields are mapped.

The actual data is then a list of Maps. The Map defines for each field in the from data a value.

The following shows an example which reuses an predefined layout from Android for the row.

package de.vogella.android.listactivity;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

import android.app.ListActivity;
import android.os.Bundle;
import android.widget.SimpleAdapter;

public class MyTwoListItemsActivity extends ListActivity {
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    ArrayList<Map<String, String>> list = buildData();
    String[] from = { "name", "purpose" };
    int[] to = { android.R.id.text1, android.R.id.text2 };

    SimpleAdapter adapter = new SimpleAdapter(this, list,
        android.R.layout.simple_list_item_2, from, to);
    setListAdapter(adapter);
  }

  private ArrayList<Map<String, String>> buildData() {
    ArrayList<Map<String, String>> list = new ArrayList<Map<String, String>>();
    list.add(putData("Android", "Mobile"));
    list.add(putData("Windows7", "Windows7"));
    list.add(putData("iPhone", "iPhone"));
    return list;
  }

  private HashMap<String, String> putData(String name, String purpose) {
    HashMap<String, String> item = new HashMap<String, String>();
    item.put("name", name);
    item.put("purpose", purpose);
    return item;
  }
  
} 

13. Selecting multiple items in the ListView

13.1. Interaction between the model and Listview

Frequently you need to select items in your ListView. As the row of the ListView are getting recycled you cannot store the selection on the View level.

ListView with Interaction and full model

Selection is just one possible example but you can imange other interaction between your row and model.

To persist the selection you have to update your data model with the selected state.

To update the data model in your ListView you define your own Adapter class. In this adapter class you attach a listener to the View which is responsible for selecting the model element. If selected you update the state in the model which you can add as a tag to the View to have access to it.

The following example demonstrates how to use standard Java object and how to interact from the Views with the model.

13.2. Tutorial: Domain Model and Rows interaction

Continue to use the de.vogella.android.listactivity project.

Create the following Model which hold the name and the information if this element is currently selected.

package de.vogella.android.listactivity;

public class Model {

  private String name;
  private boolean selected;

  public Model(String name) {
    this.name = name;
    selected = false;
  }

  public String getName() {
    return name;
  }

  public void setName(String name) {
    this.name = name;
  }

  public boolean isSelected() {
    return selected;
  }

  public void setSelected(boolean selected) {
    this.selected = selected;
  }

} 

Create the following new layout file called rowbuttonlayout.xml.

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content" >

    <TextView
        android:id="@+id/label"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@+id/label"
        android:textSize="30px" >
    </TextView>

    <CheckBox
        android:id="@+id/check"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentRight="true"
        android:layout_marginLeft="4px"
        android:layout_marginRight="10px" >
    </CheckBox>

</RelativeLayout> 

Create the following Adapter. This adapter adds a listener on the Checkbox view. If the checkbox is selected the underlying data of the model is changed. Checkbox gets the corresponding model element assigned via the getTag() method.

package de.vogella.android.listactivity;

import java.util.List;

import android.app.Activity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.TextView;

public class InteractiveArrayAdapter extends ArrayAdapter<Model> {

  private final List<Model> list;
  private final Activity context;

  public InteractiveArrayAdapter(Activity context, List<Model> list) {
    super(context, R.layout.rowbuttonlayout, list);
    this.context = context;
    this.list = list;
  }

  static class ViewHolder {
    protected TextView text;
    protected CheckBox checkbox;
  }

  @Override
  public View getView(int position, View convertView, ViewGroup parent) {
    View view = null;
    if (convertView == null) {
      LayoutInflater inflator = context.getLayoutInflater();
      view = inflator.inflate(R.layout.rowbuttonlayout, null);
      final ViewHolder viewHolder = new ViewHolder();
      viewHolder.text = (TextView) view.findViewById(R.id.label);
      viewHolder.checkbox = (CheckBox) view.findViewById(R.id.check);
      viewHolder.checkbox
          .setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {

            @Override
            public void onCheckedChanged(CompoundButton buttonView,
                boolean isChecked) {
              Model element = (Model) viewHolder.checkbox
                  .getTag();
              element.setSelected(buttonView.isChecked());

            }
          });
      view.setTag(viewHolder);
      viewHolder.checkbox.setTag(list.get(position));
    } else {
      view = convertView;
      ((ViewHolder) view.getTag()).checkbox.setTag(list.get(position));
    }
    ViewHolder holder = (ViewHolder) view.getTag();
    holder.text.setText(list.get(position).getName());
    holder.checkbox.setChecked(list.get(position).isSelected());
    return view;
  }
} 

Finally change your activity to the following.

package de.vogella.android.listactivity;

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

import android.app.ListActivity;
import android.os.Bundle;
import android.widget.ArrayAdapter;

public class MyList extends ListActivity {

  
/** Called when the activity is first created. */
public void onCreate(Bundle icicle) { super.onCreate(icicle); // Create an array of Strings, that will be put to our ListActivity ArrayAdapter<Model> adapter = new InteractiveArrayAdapter(this, getModel()); setListAdapter(adapter); } private List<Model> getModel() { List<Model> list = new ArrayList<Model>(); list.add(get("Linux")); list.add(get("Windows7")); list.add(get("Suse")); list.add(get("Eclipse")); list.add(get("Ubuntu")); list.add(get("Solaris")); list.add(get("Android")); list.add(get("iPhone")); // Initially select one of the items list.get(1).setSelected(true); return list; } private Model get(String s) { return new Model(s); } }

If you start your app you should be able to flag items. These changes will be reflected in your model.

14. Tutorial: Miscellaneous

14.1. Adding a longclick listener to the list items

You can also add a LongItemClickListener to the View. For this receive the ListView via the getListVIew() method and set the LongItemClickListener via the setOnItemLongClickListener() method.

package de.vogella.android.listactivity;

import android.app.ListActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemLongClickListener;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.Toast;

public class MyList extends ListActivity {

  
/** Called when the activity is first created. */
public void onCreate(Bundle icicle) { super.onCreate(icicle); // Create an array of Strings, that will be put to our ListActivity String[] names = new String[] { "Linux", "Windows7", "Eclipse", "Suse", "Ubuntu", "Solaris", "Android", "iPhone" }; ArrayAdapter<String> adapter = new MyPerformanceArrayAdapter(this, names); setListAdapter(adapter); ListView list = getListView(); list.setOnItemLongClickListener(new OnItemLongClickListener() { @Override public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) { Toast.makeText(MyList.this, "Item in position " + position + " clicked", Toast.LENGTH_LONG).show(); // Return true to consume the click event. In this case the // onListItemClick listener is not called anymore. return true; } }); } @Override protected void onListItemClick(ListView l, View v, int position, long id) { super.onListItemClick(l, v, position, id); // Get the item that was clicked Object o = this.getListAdapter().getItem(position); String keyword = o.toString(); Toast.makeText(this, "You selected: " + keyword, Toast.LENGTH_SHORT) .show(); } }

14.2. Multi vs. single Selection

To get the selected item(s) of a ListView use the getCheckedItemPosition() for a single selection method or listView.getCheckedItemPositions() for multiple selections. . If you have stable ID you could also use the getCheckedItemIds() method to get the selected IDs.

By default a ListView supports only the single selection of a row, but you can activate multi selection via a flag. See the following snippets for examples.

package de.vogella.android.listactivity;

import android.app.ListActivity;
import android.os.Bundle;
import android.widget.ArrayAdapter;
import android.widget.ListView;

public class MyList extends ListActivity {

  
/** Called when the activity is first created. */
public void onCreate(Bundle icicle) { super.onCreate(icicle); // Create an array of Strings, that will be put to our ListActivity String[] names = new String[] { "Linux", "Windows7", "Eclipse", "Suse", "Ubuntu", "Solaris", "Android", "iPhone", "Linux", "Windows7", "Eclipse", "Suse", "Ubuntu", "Solaris", "Android", "iPhone" }; setListAdapter(new ArrayAdapter<String>(this, android.R.layout.simple_list_item_multiple_choice, android.R.id.text1, names)); ListView listView = getListView(); listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE); } }

package de.vogella.android.listactivity;

import android.app.ListActivity;
import android.os.Bundle;
import android.widget.ArrayAdapter;
import android.widget.ListView;

public class MyList extends ListActivity {

  
/** Called when the activity is first created. */
public void onCreate(Bundle icicle) { super.onCreate(icicle); // Create an array of Strings, that will be put to our ListActivity String[] names = new String[] { "Linux", "Windows7", "Eclipse", "Suse", "Ubuntu", "Solaris", "Android", "iPhone", "Linux", "Windows7", "Eclipse", "Suse", "Ubuntu", "Solaris", "Android", "iPhone" }; setListAdapter(new ArrayAdapter<String>(this, android.R.layout.simple_list_item_single_choice, android.R.id.text1, names)); ListView listView = getListView(); listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE); } }

14.3. ExpandableListView

The ExpandableListView is similar to ListView but allow you to define groups and details for this group. ExpandableListView expects and adapter of type BaseExpandableListAdapter.

14.4. Header and Footer

You can of course put arbitrary Views elements around your ListView. For example you can define a layout with two TextViews and a ListView between them. In this case the two TextViews will always be visible above the List (header) and the other will be visible below the ListView. If you want to display a list header or list footer only at the see the beginning or end of the list you can use the setHeaderView() method or setFooterView() method on ListView.

package de.vogella.android.listactivity;

import android.app.ListActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.ListView;

public class MyList extends ListActivity {

  
/** Called when the activity is first created. */
public void onCreate(Bundle icicle) { super.onCreate(icicle); // Create an array of Strings, that will be put to our ListActivity String[] names = new String[] { "Linux", "Windows7", "Eclipse", "Suse", "Ubuntu", "Solaris", "Android", "iPhone", "Linux", "Windows7", "Eclipse", "Suse", "Ubuntu", "Solaris", "Android", "iPhone" }; View header = getLayoutInflater().inflate(R.layout.header, null); View footer = getLayoutInflater().inflate(R.layout.footer, null); ListView listView = getListView(); listView.addHeaderView(header); listView.addFooterView(footer); setListAdapter(new ArrayAdapter<String>(this, android.R.layout.simple_list_item_single_choice, android.R.id.text1, names)); } }

15. SimpleCursorAdapter

In case you work with a content provider or directly with the database you can use the SimpleCursorAdapter to define the data for your ListView. The following will demonstrates how to access the Contacts ContentProvider.

Create a new Android project called "de.vogella.android.listactivity.cursor" with the activity called MyListActivity. Change MyListActivity to the following.

package de.vogella.android.listactivity.cursor;

import android.app.ListActivity;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.provider.ContactsContract;
import android.widget.ListAdapter;
import android.widget.SimpleCursorAdapter;

public class MyListActivity extends ListActivity {
  
/** Called when the activity is first created. */
@Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Cursor mCursor = getContacts(); startManagingCursor(mCursor); // Now create a new list adapter bound to the cursor. // SimpleListAdapter is designed for binding to a Cursor. ListAdapter adapter = new SimpleCursorAdapter(this, // Context. android.R.layout.two_line_list_item, // Specify the row template // to use (here, two // columns bound to the // two retrieved cursor // rows). mCursor, // Pass in the cursor to bind to. // Array of cursor columns to bind to. new String[] { ContactsContract.Contacts._ID, ContactsContract.Contacts.DISPLAY_NAME }, // Parallel array of which template objects to bind to those // columns. new int[] { android.R.id.text1, android.R.id.text2 }); // Bind to our new adapter. setListAdapter(adapter); } private Cursor getContacts() { // Run query Uri uri = ContactsContract.Contacts.CONTENT_URI; String[] projection = new String[] { ContactsContract.Contacts._ID, ContactsContract.Contacts.DISPLAY_NAME }; String selection = ContactsContract.Contacts.IN_VISIBLE_GROUP + " = '" + ("1") + "'"; String[] selectionArgs = null; String sortOrder = ContactsContract.Contacts.DISPLAY_NAME + " COLLATE LOCALIZED ASC"; return managedQuery(uri, projection, selection, selectionArgs, sortOrder); } }

Make sure you give your application the permission to read the contacts. (Uses Permissions "android.permission.READ_CONTACTS" in AndroidManifest.xml)

16. Additional Open Source libraries

Sometimes having to press a refresh button on the ActionBar to refresh data can be annoying for the user. Chris Banes has implemented an Open Source library to implement the pull to refresh pattern for a Listview. https://github.com/chrisbanes/Android-PullToRefresh.

Also you may want to use the swipe to dismiss gesture to delete items from a ListView. Jack Wharton provides an Opens Source implementation at https://github.com/JakeWharton/SwipeToDismissNOA

17. Thank you

Please help me to support this article:

Flattr this

18. Questions and Discussion

Before posting questions, please see the vogella FAQ. If you have questions or find an error in this article please use the www.vogella.com Google Group. I have created a short list how to create good questions which might also help you.

19. Links and Literature

19.1. Source Code

Source Code of Examples

19.3. vogella Resources

vogella Training Android and Eclipse Training from the vogella team

Android Tutorial Introduction to Android Programming

GWT Tutorial Program in Java and compile to JavaScript and HTML

Eclipse RCP Tutorial Create native applications in Java

JUnit Tutorial Test your application

Git Tutorial Put everything you have under distributed version control system