Android Series: Custom ListView items and adapters

Posted by admin | Posted in Mobile Development | Posted on 20-05-2009

249

This is a short tutorial on how to populate your android list view, with data downloaded from the internet or other sources, using ArrayAdapter. ListView items view is declared in a separate XML file and displayed using custom adapter class.
First things first, so go ahead and create a new project using Eclipse equipped with ADT plugin.
The project described below assumes you have a list of objects created, this can be either downloaded from the internet as XML and parsed to create ArrayList of your custom objects or anything you imagine. I will not go into details on this tutorial how to create such an ArrayList but your imagination is the limit. Parsing XML downloaded from the net will be covered in the next tutorial coming up soon.


Click File -> New -> Project and select the ‘Android Project’ wizard:

android11

Click next and fill out the next screen with the following values:

android21

Once you have filled out all the necessary data you can click finish.
Your new project has just been created. Now lets modify it a bit to display our custom made list.
Open up SoftwarePassionView.java in the eclipse editor and change the class file to the following:

1. Define necessary member variables we will use later in our class

    private ProgressDialog m_ProgressDialog = null;
    private ArrayList<Order> m_orders = null;
    private OrderAdapter m_adapter;
    private Runnable viewOrders;

m_ProgressDialog is a small pop up displaying information that your data is being downloaded or retrieved other way.
m_orders is an ArrayList which will hold our data downloaded from the internet or acquired other way
m_adapter is our custom class extending ArrayAdapter
viewOrders is a runnable for downloading data from the internet in a separate thread

To import whatever you can at this point click Ctrl+Shift+O, some classes like Order or OrderAdapter are not created yet but don’t worry we will come to that point soon.
Another important note at this point is that your SoftwarePassoinView should extend ListActivity instead of simple Activity.
Your class should look more or less something like this now:

package com.softberries.lve;

import java.util.ArrayList;
import android.app.ListActivity;
import android.app.ProgressDialog;
import android.os.Bundle;

public class SoftwarePassionView extends ListActivity{
   
    private ProgressDialog m_ProgressDialog = null;
    private ArrayList<Order> m_orders = null;
    private OrderAdapter m_adapter;
    private Runnable viewOrders;
   
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
    }
}

Now lets create our simple Order class holding single order.
Right click on the project and and select ‘New’ -> ‘Class’, name it order and open it up in the editor.
The source code for our orders looks like this:

package com.softberries.lve;

public class Order {
   
    private String orderName;
    private String orderStatus;
   
    public String getOrderName() {
        return orderName;
    }
    public void setOrderName(String orderName) {
        this.orderName = orderName;
    }
    public String getOrderStatus() {
        return orderStatus;
    }
    public void setOrderStatus(String orderStatus) {
        this.orderStatus = orderStatus;
    }
}

The Order class is very simple and contains only 2 strings with getters and setter generated for them
Now lets change our main.xml file to hold our custom list items:

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

This layout will display our list items if any and if the list is empty it will display ‘No orders to display’ string defined in string.xml resource file.

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="hello">Hello World, SoftwarePassionView!</string>
    <string name="app_name">Software Passion</string>
    <string name="main_no_items">No orders to display</string>
</resources>



Our list item (single row on the list) have a custom layout as well, defined in row.xml file:

<?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="?android:attr/listPreferredItemHeight"
    android:padding="6dip">
    <ImageView
        android:id="@+id/icon"
        android:layout_width="wrap_content"
        android:layout_height="fill_parent"
        android:layout_marginRight="6dip"
        android:src="@drawable/icon" />
    <LinearLayout
        android:orientation="vertical"
        android:layout_width="0dip"
        android:layout_weight="1"
        android:layout_height="fill_parent">
        <TextView
            android:id="@+id/toptext"
            android:layout_width="fill_parent"
            android:layout_height="0dip"
            android:layout_weight="1"
            android:gravity="center_vertical"
        />
        <TextView
            android:layout_width="fill_parent"
            android:layout_height="0dip"
            android:layout_weight="1"
            android:id="@+id/bottomtext"
            android:singleLine="true"
            android:ellipsize="marquee"
        />
    </LinearLayout>
</LinearLayout>

Single row example has been borrowed from the romain Guy website here

Ok, so we have all our layouts defined in the res folder under layout. Now its time to go back to our code and create our custom OrderAdapter class which will manage our list of orders:

private class OrderAdapter extends ArrayAdapter<Order> {

        private ArrayList<Order> items;

        public OrderAdapter(Context context, int textViewResourceId, ArrayList<Order> items) {
                super(context, textViewResourceId, items);
                this.items = items;
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
                View v = convertView;
                if (v == null) {
                    LayoutInflater vi = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
                    v = vi.inflate(R.layout.row, null);
                }
                Order o = items.get(position);
                if (o != null) {
                        TextView tt = (TextView) v.findViewById(R.id.toptext);
                        TextView bt = (TextView) v.findViewById(R.id.bottomtext);
                        if (tt != null) {
                              tt.setText("Name: "+o.getOrderName());                            }
                        if(bt != null){
                              bt.setText("Status: "+ o.getOrderStatus());
                        }
                }
                return v;
        }
}

This is a private class and should be added to our SoftwarePassionView. This is extended ListAdapter which inside overriden getView method returns our row with assigned string values to the textfields defined in row.xml.

A huge part of our application is already done. Now we have to add some modifications to the onCreate method to initialize everything properly and add a method retrieving our orders from somewhere, lets start with the latter:

private void getOrders(){
          try{
              m_orders = new ArrayList<Order>();
              Order o1 = new Order();
              o1.setOrderName("SF services");
              o1.setOrderStatus("Pending");
              Order o2 = new Order();
              o2.setOrderName("SF Advertisement");
              o2.setOrderStatus("Completed");
              m_orders.add(o1);
              m_orders.add(o2);
                 Thread.sleep(2000);
              Log.i("ARRAY", ""+ m_orders.size());
            } catch (Exception e) {
              Log.e("BACKGROUND_PROC", e.getMessage());
            }
            runOnUiThread(returnRes);
        }



Instead of creating our simple orders in the method above you could of course download them from somewhere and assign the result to the m_orders array list. The method runOnUIThread is a utility method for running tasks back on the main UI thread after the job is done on the separate thread created for long running tasks. We will call our getOrders method from a separate thread.

The returnRes runnable adds newly retrieved Order object to our custom Adapter and notifies it of the data change:

private Runnable returnRes = new Runnable() {

            @Override
            public void run() {
                if(m_orders != null && m_orders.size() > 0){
                    m_adapter.notifyDataSetChanged();
                    for(int i=0;i<m_orders.size();i++)
                    m_adapter.add(m_orders.get(i));
                }
                m_ProgressDialog.dismiss();
                m_adapter.notifyDataSetChanged();
            }
          };

Now lets move to our overriden onCreate method. We will initialize here all the member variables as well as start a new thread retrieving our orders:

       @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        m_orders = new ArrayList<Order>();
        this.m_adapter = new OrderAdapter(this, R.layout.row, m_orders);
                setListAdapter(this.m_adapter);
       
        viewOrders = new Runnable(){
            @Override
            public void run() {
                getOrders();
            }
        };
    Thread thread =  new Thread(null, viewOrders, "MagentoBackground");
        thread.start();
        m_ProgressDialog = ProgressDialog.show(SoftwarePassionView.this,    
              "Please wait...", "Retrieving data ...", true);
    }

After initialization, we start new thread using the viewOrders runnable and show the progress dialog which we close once the orders are retrieved.
Now you should be able to run your application. After the application starts it spawns new thread and displays the loader:

screen_load screen1

And thats it. You can add an Item Click Listener to your list to start new activities etc.
Full source code for the SoftwarePassionView below:

package com.softberries.lve;

import java.util.ArrayList;
import android.app.ListActivity;
import android.app.ProgressDialog;
import android.content.Context;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.TextView;

public class SoftwarePassionView extends ListActivity{
   
    private ProgressDialog m_ProgressDialog = null;
    private ArrayList<Order> m_orders = null;
    private OrderAdapter m_adapter;
    private Runnable viewOrders;
   
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        m_orders = new ArrayList<Order>();
        this.m_adapter = new OrderAdapter(this, R.layout.row, m_orders);
        setListAdapter(this.m_adapter);
       
        viewOrders = new Runnable(){
            @Override
            public void run() {
                getOrders();
            }
        };
        Thread thread =  new Thread(null, viewOrders, "MagentoBackground");
        thread.start();
        m_ProgressDialog = ProgressDialog.show(SoftwarePassionView.this,    
              "Please wait...", "Retrieving data ...", true);
    }
    private Runnable returnRes = new Runnable() {

        @Override
        public void run() {
            if(m_orders != null && m_orders.size() > 0){
                m_adapter.notifyDataSetChanged();
                for(int i=0;i<m_orders.size();i++)
                m_adapter.add(m_orders.get(i));
            }
            m_ProgressDialog.dismiss();
            m_adapter.notifyDataSetChanged();
        }
    };
    private void getOrders(){
          try{
              m_orders = new ArrayList<Order>();
              Order o1 = new Order();
              o1.setOrderName("SF services");
              o1.setOrderStatus("Pending");
              Order o2 = new Order();
              o2.setOrderName("SF Advertisement");
              o2.setOrderStatus("Completed");
              m_orders.add(o1);
              m_orders.add(o2);
              Thread.sleep(5000);
              Log.i("ARRAY", ""+ m_orders.size());
            } catch (Exception e) {
              Log.e("BACKGROUND_PROC", e.getMessage());
            }
            runOnUiThread(returnRes);
        }
    private class OrderAdapter extends ArrayAdapter<Order> {

        private ArrayList<Order> items;

        public OrderAdapter(Context context, int textViewResourceId, ArrayList<Order> items) {
                super(context, textViewResourceId, items);
                this.items = items;
        }
        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
                View v = convertView;
                if (v == null) {
                    LayoutInflater vi = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
                    v = vi.inflate(R.layout.row, null);
                }
                Order o = items.get(position);
                if (o != null) {
                        TextView tt = (TextView) v.findViewById(R.id.toptext);
                        TextView bt = (TextView) v.findViewById(R.id.bottomtext);
                        if (tt != null) {
                              tt.setText("Name: "+o.getOrderName());                            }
                        if(bt != null){
                              bt.setText("Status: "+ o.getOrderStatus());
                        }
                }
                return v;
        }
}
}



Enjoy!

Comments (249)

Thanks, It’s great. when an item is focused it change background to orange (very beauty). But when I clicked in an item how can I make the same focus. any body help me. Thanks.

excellent tutorial.

How would you extend the same concept to data from a database using a cursor and string of multiple values such as :

Cursor studCursor = mDbHelper.fetchAllExercisesStud(mRowId);
startManagingCursor(studCursor);

// Create an array to specify the fields we want to display in the list
String[] from = new String[]{
gradeBookDbAdapter.KEY_DIDEXERCISE,
gradeBookDbAdapter.KEY_EXSCORE,
gradeBookDbAdapter.KEY_ATTITUDE,
gradeBookDbAdapter.KEY_DTIME,
gradeBookDbAdapter.KEY_COMMENT};

I’m having an issue with the code. I’m trying to run SoftwarePassionView inside of a tabbed layout. For some reason it is blowing up to full screen, covering up the tabs as well as the title bar. Does this have to do with the vi.inflate function?

Otherwise, great code! Thanks!

Thanks..this is really helpful. But one question,
why do you need to execute the below code in the run method of the returnRes???
if(m_orders != null && m_orders.size() > 0){
m_adapter.notifyDataSetChanged();
for(int i=0;i<m_orders.size();i++)
m_adapter.add(m_orders.get(i));
}

The OrderAdapter was already passed the m_orders arraylist in the onCreate() method (OrderAdapter constructor). Shouldn't you just be able to call notifyDataSetChanged()???

[...] 7, 2011 1. Custom listviews – listviews which work with array of different UI elements.  (Example , Example 2) 2. AsyncTask + Services – The UI thread should never be blocked for example [...]

Like Deepak, I wanted to know if there is any way to still use setOnItemClickListener or setTextFilterEnabled? The formatting worked perfectly but I really need to be able to click on the TextView items.

Thank you for such a nice tutorial with list view.
I followed your tutor and i was able to slove my problem to an extend.
Since you have an icon in the list view, i decided to have a checkbox and thats fine for me.

I wish to know how to accomplish questions below:
1. Firstly how to hide check box initially when the list is fetched.
2. Secondly, i have a menu, which has Edit, when i press the edit, i wish to show checkbox in the ListView.

How can i perform as such in question 1 and 2.
Please put some insight in it.

Thanks a lot for such self sufficient tutorial ….
It helped me a lot to know how to create proper Listview
after listening the Google conference video on Listview.

Great tutorial. Learning android and used this to help with my first app.

@AF – that doesn’t work as you need to update convertView based on the position index

[...] wirklich eine Antwort durchs googeln finde. Ich habe eine Liste aus folgendem Tutorial erstellt: Android Series: Custom ListView items and adapters | Software Passion Funktioniert ganz gut soweit, allerdings habe ich 'versucht', in der Methode [...]

thanks a lot.

Hi,
To access the selected item in the list, you can straightaway override ‘onListItemClick’ method since its base is a ‘ListActivity’. Use the following code and change ‘Booking’ to ‘Order’.

Thanks

@Override
protected void onListItemClick(ListView l, View v, int position, long id)
{
try
{
super.onListItemClick(l, v, position, id);
Booking bkg = (Booking)l.getItemAtPosition(position);
String strTextToDisplay = “Selected item is : ” +bkg.getBookingName();
Toast.makeText(this, strTextToDisplay, Toast.LENGTH_LONG).show();
}
catch(Exception ex)
{
Toast.makeText(this, ex.getMessage(), Toast.LENGTH_LONG).show();
}

}

Many thanks, its was really helpful.

Finally! – a tutorial based on populating a ListView with entity objects instead of just string arrays. Very nice, can’t wait to copy it in my own project. :-)

can you please send me the project file because the application stops runing (its not runing at all)

Hi, i followed this tutorial and when i run the app, i get

E/AndroidRuntime(11676): Caused by: java.lang.RuntimeException: Your content must have a ListView whose id attribute is ‘android.R.id.list’

and the list in the main.xml is
android:id=”@+id/android:list”

Do you have any tips for this?

Thanks allot, very nice explaining

very nice tut!
thanks!

[...] custom row views from an XML file. This is a very common task that is presented in many tutorials (Here is one). Basically, the idea is to override the Adapter#getView(int, View, ViewGroup) method, [...]

hi tried to run ur application but for me its giving exception (force close).
i tried to create a new project and just changed “extends Activity” to “extends ListActivity” even this project is getting crash, is their any thing i am missing..

Very Good!! THANKS

Thanks for the tutorial! Finally i understand the system.

Has anyone tried this using 2.2 as a build target? It just seems to FC when it tries to run. Switching it back to 1.5 works fine.

awsome tutorial . loved it and learned what i wanted.

Congratulations! It`s the best Adapter tutorial I have found, simple and straightfoward.

Thanks. Great tutorial. I’m just learning java/android (I’ve been developing in php/perl/python/etc for years). development. Tutorials such as this are awesome for guys like me trying to wrap their heads around a new language.

Awesome tutorial. It’s help me a lot. Moreover I need to implement filter in order to implement search on list view. But m_adapter wont filter the listview. I want to filter the entire list view based on text in a textview. Please help me!!

same with Zidar said on 23-08-2011

I also get message :

“Your content must have a ListView whose id attribute is ‘android.R.id.list’

Do you have any solution?
Thx

Hehe… Ctrl+SHIT+O can really help me out! :D . Change Shit to Shift please.

He he, thanks Aaron! Fixed!

Great article!

Great work! Thanks a lot!

@735

“Your content must have a ListView whose id attribute is ‘android.R.id.list’”

Since you are extending the ListVew class, the ListView in your layout must use a standard id from the android package.

Oops it stripped it out, here’s what you add to the ListView definition:

android:id=”@android:id/list”

Thank you Boss :)

it was really helpful tutorial…

thank you so much…

I agree with Roli. As is, the run method is creating memory problems.
This version works for me:

@Override
public void run() {
m_ProgressDialog.dismiss();
m_adapter.notifyDataSetChanged();
}

świetna stronka ;)

hi i am new to this android i have doubt first you are calling the listview defined in main.xml and calling the row .xml after loading the data then why you need listview u are not using it anywhere and android:layout_height=”?android:attr/listPreferredItemHeight” means what ? please let me know anybody and this doubt must seems to be little bit silly .but as a starty to this environment i need to know. thanks in advance

I am getting the error in OrderAdapter class in below line..

LayoutInflater vi = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);

Error:
====================
The method getSystemService(String) is undefined for the type OrderAdapter
====================

Whats is the solution for this error

I had the same error but when i declared OrderAdapter as a private class inside my main public class (SoftwarePassionView here) the error solved itself.

Good luck,

and thanks to the author!

great tutorial one cuestion
how i can update the list view??????

best tutorial … very useful.. thank u so much for ur effort.

Very good tutorial. It helped me to create custom ListView for my phone log application. My next move will be to create tabs and to put this custom ListView in one tab.

Thank you!

I got the same error at the SystemService (getSystemService… method not defined).

I haven’t created a private class inside.
The solution was to add a new private field at the adapter class with type “Context”.
At the constructor you have to put the parameter context to the private field property (this.property = context).

After this you are able to trigger the “getSystemService” method.
Just put the property name . getSystemService etc..

With these changes it works perfect! Thanks for the great tutorial and I hope I could help someone else too :)

Great tut!! Sharing source code will be awesome :)

[...] You can then use the Android Dev tutorial for implementing your listview: http://developer.android.com/resources/tutorials/views/hello-listview.html Or this custom listview tut I found really helpful (more screenshots): http://www.softwarepassion.com/android-series-custom-listview-items-and-adapters/ [...]

Thank u !! It is really helpful

Write a comment