Version 4/2
Copyright © 2010, 2011, 2012, 2013 Lars Vogel
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 |
Table of Contents
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.

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

Android provides the
ListView
class
which is capable of displaying a scrollable list of items. These
items can be of any type.
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.
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; } } }
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.
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.
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.
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(); } });
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.

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.
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; } }
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.
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); } }
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.
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 } }
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.
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.
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.
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; } }
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.

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.

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.

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(); } }

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(); } }

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.

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)); } }
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; } }
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.

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.
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.
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(); } }
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); } }
The
ExpandableListView
is similar to
ListView
but allow you to define groups and details for this group.
ExpandableListView
expects
and adapter of type
BaseExpandableListAdapter.
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)); } }
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)
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
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.
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