Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Mobile / Android

Custom list item layout

4.85/5 (11 votes)
23 Jan 2012CPOL7 min read 52.7K   1.9K  
Describe how to create list of items with custom defined layout resource

device-2012-01-18-235347.png

Introduction

In this short article i want to present the fast and easy way customize the view of list of items using the separate resource file.

Background

When we work with the specific framework, some in time, we find that the build in features are not enough. And one of such situation is presenting the list of items. What if do not need to display just list of simple text?

The example in this article is the base line code for feature customization and extension. I try to make it as simple as possible, so i could present only the way to use the resource file for the item layout, as well how to fill it with item data.

How to

As a working environment I use the Eclipse environment with ADT plugin.

To keep the example as simple as possible and focus only on the customizing the list item view, all code base on the clean standard Android Project.

Item layout

First we create new layout file by right click on res/layout folder and choosing New > Android XML File. In the dialog select Resource Type: Layout, Root Element: Linear Layout and enter the File item.xml.

As an example we will display three values of each item on the list:

  • position - the index of the element.
  • id - customized identifier of the element.
  • item - the value of the item.
The source of the item layout looks like this:
XML
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical" >
    <TextView
        android:id="@+id/position"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text=":position" />
    <TextView
        android:id="@+id/id"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text=":id" />
    <TextView
        android:id="@+id/item"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text=":item" />
</LinearLayout> 

To preview the layout in the graphical designer I give the initial values to the android:text tag. This way our item will look more or less like this:

Design view of item.xml

We could create the different resource files for this layout depending on the screen size, device orientation and resolution. We inherit all the beauty of the resources system in the android. Righ know we will keep it simple as possible.

Custom list adapter

When we know how the item will looks like now we need to implement the adapter. The purpose of this class will creating the item view based on the collection.

On the main application code package (example: net.origami.android.examples) right click and choose New > Class. Give it the Name CustomAdapter and as the Superclass enter android.widget.BaseAdapter.

First we will create constructor.

Java
public CustomAdapter(Context context, String... items) {
    _inflater = (LayoutInflater)
            context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    _items = items;
}

As the parameters we get the context with give us access to the application and system resources and our collection of items. For this example I chose strings but it could be collection of any types that work for you, maybe some custom domain object.

What i will remember in my adapter are:

  • _inflater - the system service that read the resource and instantiate the View from it.
  • _items - my collection to display.

Next we override the simple methods:

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

@Override
public Object getItem(int position) {
    return _items[position];
}

@Override
public long getItemId(int position) {
    return position + 1;
}
  • <code>getCount - number of item in the collection.
  • getItem - allow to get the item at the specific position.
  • getItemId - allow to get the custom identifier of the item. This could be, but not need, a position. In this example we change the 0 based index to start form 1 to n.
And now the latest overridden method where all the magic is made:
Java
@Override
public View getView(int position, View convertView, ViewGroup parent) {
    // 1.
    View view = _inflater.inflate(R.layout.item, null);

    // 2.
    TextView positionText = (TextView) convertView
            .findViewById(R.id.position);
    TextView idText = (TextView) convertView.findViewById(R.id.id);
    TextView itemText = (TextView) convertView.findViewById(R.id.item);

    // 3.
    positionText.setText("position: " + position);
    idText.setText("id: " + getItemId(position));
    itemText.setText("item: " + getItem(position));

    return view;
}

We implement the method with performing three general steps:

  1. Create the item view based on resource layout.
    We use the remembered in constructor _inflater.
  2. Find all customized elements.
    All identifiable texts, images.
  3. Customize the item view based on position.
    Assigning the texts, loading images as well as showing, hiding some elements if needed.

Optimization

The example is not fully workable we do not use of the adapter yet. Anyway the execution optimization in mobile word is the key factor i will make the digression now.

Feature investigation of the getView calls shows that it is executed 3 times for each visible item on the list before it will be displayed for the first time. User maybe will not see much different on the screen but could notice that our app "eat the battery" very fast switching between views.

This happen because of the layout logic in the android. The measure, layout, and measure are this three passes. This was the reason why the adapter (in real this optimization is implemented in ListView) has build in optimization the contenerView.

The containerView is the parameter that give us possibility to reuse previously inflated item view object to customize it for other item. We do not need care how its happen just know that if the containerView is not null we can reuse it. So after optimization our getView implementation will look like this:

Java
 @Override
public View getView(int position, View convertView, ViewGroup parent) {
    // 1.
    if (convertView == null) {
        convertView = _inflater.inflate(R.layout.item, null);
    }

    // 2.
    TextView positionText = (TextView) convertView
            .findViewById(R.id.position);
    TextView idText = (TextView) convertView.findViewById(R.id.id);
    TextView itemText = (TextView) convertView.findViewById(R.id.item);

    // 3.
    positionText.setText("position: " + position);
    idText.setText("id: " + getItemId(position));
    itemText.setText("item: " + getItem(position));

    return convertView;
}

As you could see only the first step changed dramatically. The view variable was also removed while we use the parameter.

HOW DOES IT WORK?

As I mention the ListView has buld in optimization. When the list is filled for the first time the fallowing sequence is performed:

  • measure - the container needs to determine how many elements will be visible in the list. It calls getView on the adapter for the first item view measure its size. For next items its calls getView but pass already created item view as convertView parameter. It continue till it will fill all available space of the ListView. It can do so just because it needs only the size of the items on the list.
  • layout - the container will set visible items in the specific locations. Now its need separate view for each individual item. One it already has so it calls getView for first item with passing cached view. For all other it will require to get the real one so the converterView will be null.
  • measure - in real i do not know why this pass is made beside the fact that the getView for first element gets the null in converterView. All other calls for visible items will get the cached values. The only one reason i figure out is that the additional created view will be the same like the back buffer in the animation.
On the end we got 1 + "number of visible items" of views and this is enough to display the list. Now if we will scroll the items the getView will be called and the view that will disappear will be reused for the item that will just show up. No more inflating of the resources.

See the reference material form I/O 2010 session for details.

There is one more optimization we could make. Searching of the customized elements could be also power consuming. Maybe it is not so spectacular as the converterView parameter but we should do our best to give good quality and optimized software. Look at the code:

Java
    static class ViewHolder {
        TextView positionText;
        TextView idText;
        TextView itemText;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        ViewHolder holder;

        if (convertView == null) {
            // 1.
            convertView = _inflater.inflate(R.layout.item, null);

            // 2.
            holder = new ViewHolder();
            holder.positionText = (TextView) convertView
                    .findViewById(R.id.position);
            holder.idText = (TextView) convertView.findViewById(R.id.id);
            holder.itemText = (TextView) convertView.findViewById(R.id.item);

            convertView.setTag(holder);
        } else {
            // 2.
            holder = (ViewHolder) convertView.getTag();
        }

        // 3.
        holder.positionText.setText("position: " + position);
        holder.idText.setText("id: " + getItemId(position));
        holder.itemText.setText("item: " + getItem(position));

        return convertView;
    }  

The new internal class ViewHolder was created. It is the cache container for result of search elements for customization. We will search them one time for each created item view and store it in the item view itself as the tag. When the convertView is reused we simple retrieve the ViewHolder from tags, no need to search the item view for customized elements again.

Use of adapter

Now its the time to display the list of our items.

First we need to add ListView to the main activity view resource (main.xml):

XML
<?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"
    >
    <TextView  
            android:layout_width="fill_parent" 
            android:layout_height="wrap_content" 
            android:text="@string/description"
        />
    <ListView 
        android:id="@+id/list"
            android:layout_width="fill_parent" 
            android:layout_height="wrap_content"         
        />
</LinearLayout> 

And assign our adapter with collection to it when the activity is created (MainActivity.java):

Java
@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);

    ListView list = (ListView) findViewById(R.id.list);
    list.setAdapter(new CustomAdapter(this,
        "Item 1", "Item 2", "Item 3", "Item 4", "Item 5", "Item 6",
        "Item 7", "Item 8", "Item 9", "Item 10", "Item 11", "Item 12",
        "Item 13", "Item 14", "Item 15", "Item 16", "Item 17",
        "Item 18", "Item 19", "Item 20"));
}

This is it!

Selection action

One more thing that could be useful is to accessing the selected item on the list. Typical it will show us the details of the item. For this example I will display the simple Toast popup with the same data that are displayed in the item view.

To do so just add the lines bellow just after setting the adapter.

list.setOnItemClickListener(new OnItemClickListener() {
    @Override
    public void onItemClick(AdapterView<?> adapter, View view,
            int position, long id) {
        Toast toast = Toast.makeText(
            getApplicationContext(),
            "position: " + position +
                ", id: " + id +
                ", value: " + adapter.getItemAtPosition(position),
            Toast.LENGTH_LONG);

        toast.show();
    }
});

Feature

This is of course one way to work with ListView other possibilities are:

  • ListActivity - use this as the base class of your activity and mark the ListView elemnt with id attribute set to @android:id/list. This is useful when we want to display the list on the whole screen. Is such case we do not need to search the ListView in the code, just bind our adapter to the activity using setListAdapter method.
  • ListFragment (since API 11) - the same as ListActivity but in the Fragment way.
  • SimpleCursorAdapter - build in adapter to work with the Cursor object that could be retrived form the Database as well as Content Provider. In the constructor we pass the Cursor as well as the item layout, fields to map and corresponding ids of the item layout elements.
The ListView and the adapter have lots more features like displaying diffident item types with different customized views. Of course with reusing views. Headers, footers, etc. But this is another story... if you curies see the Google I/O session listed in the references section.

References

History

  • 2012-01-21 - initial version of the article.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)