Logo Icon Logo
A Crowd-sourced Cookbook on Writing Great Android® Apps
GitHub logo Twitter logo OReilly Book Cover Art

Creating an Advanced ListView with Images and Text

Author: Marco Dinacci
Published? true
FormatLanguage: WikiFormat

Problem:

You want to write a ListView that shows an image next to a string.

Solution:

We're going to create an Activity that extends from ListActivity, prepare the XML resource files and at last create a custom view adapter to load the resources onto the view.

Discussion:

The Android documentation says that the ListView widget is easy to use. It is true if you just want to display a simple list of strings but as soon as you want to customize your list things become more complicated.

This recipe shows you how to write a ListView that displays a static list of images and strings, similar to the settings list on your phone.

Here's a picture of the final result:

Let's start with the Activity code. First of all, we extends from ListActivity instead of Activity so we can easily supply our custom adapter:

public class AdvancedListViewActivity extends ListActivity {

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        
        Context ctx = getApplicationContext();
	Resources res = ctx.getResources();

	String[] options = res.getStringArray(R.array.country_names);
	TypedArray icons = res.obtainTypedArray(R.array.country_icons);
		
	setListAdapter(new ImageAndTextAdapter(ctx, R.layout.main_list_item, options, icons));
    }
}

In the onCreate we also create an array of strings, which contains the country names, and a TypedArray, which will contain our Drawable flags.

The arrays are created from an XML file, here's the content of the countries.xml file.

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string-array name="country_names">
        <item>Bhutan</item>
        <item>Colombia</item>
        <item>Italy</item>
        <item>Jamaica</item>
        <item>Kazakhstan</item>
        <item>Kenya</item>
    </string-array>
    <array name="country_icons">
        <item>@drawable/bhutan</item>
        <item>@drawable/colombia</item>
        <item>@drawable/italy</item>
        <item>@drawable/jamaica</item>
        <item>@drawable/kazakhstan</item>
        <item>@drawable/kenya</item>
    </array>
</resources>

Now we're ready to create the adapter. The official documentation for Adapter says:

<para>An Adapter object acts as a bridge between an AdapterView and the underlying data for that view. The Adapter provides access to the data items. The Adapter is also responsible for making a View for each item in the data set.</para>

There are several subclasses of Adapter; we're going to extend on ArrayAdapter which is a concrete BaseAdapter that is backed by an array of arbitrary objects.

public class ImageAndTextAdapter extends ArrayAdapter<String> {

	private LayoutInflater mInflater;
	
	private String[] mStrings;
	private TypedArray mIcons;
	
	private int mViewResourceId;
	
	public ImageAndTextAdapter(Context ctx, int viewResourceId,
			String[] strings, TypedArray icons) {
		super(ctx, viewResourceId, strings);
		
		mInflater = (LayoutInflater)ctx.getSystemService(
				Context.LAYOUT_INFLATER_SERVICE);
		
		mStrings = strings;
		mIcons = icons;
		
		mViewResourceId = viewResourceId;
	}

	@Override
	public int getCount() {
		return mStrings.length;
	}

	@Override
	public String getItem(int position) {
		return mStrings[position];
	}

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

	@Override
	public View getView(int position, View convertView, ViewGroup parent) {
		convertView = mInflater.inflate(mViewResourceId, null);
		
		ImageView iv = (ImageView)convertView.findViewById(R.id.option_icon);
		iv.setImageDrawable(mIcons.getDrawable(position));

		TextView tv = (TextView)convertView.findViewById(R.id.option_text);
		tv.setText(mStrings[position]);
		
		return convertView;
	}
}

The constructor accepts a Context, the id of the layout that will be used for every row (more on this soon), an array of strings (the country names) and a TypedArray (our flags).

The getView method is where we build a row for the list. We first use a LayoutInflater to create a View from XML, then we retrieve the country flag as a Drawable and the country name as a String and we use them to populate the ImageView and TextView that we've declared in the layout.

The layout for the list rows is the following:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android">
	<ImageView
	android:id="@+id/option_icon"
	android:layout_width="48dp"
	android:layout_height="fill_parent"/>
	<TextView
	   android:id="@+id/option_text"
	    android:layout_width="fill_parent"
	    android:layout_height="fill_parent"
	    android:padding="10dp"
	    android:textSize="16dp" >
	</TextView>
</LinearLayout>

And this is the content of the main layout:

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

Note that the ListView ID must be exactly @android:id/listor you'll get a RuntimeException.

Download:

The source code for this project is in the Android Cookbook repository, http://github.com/IanDarwin/Android-Cookbook-Examples/,in the subdirectory ListViewAdvanced.
No records found.