Android: Custom ArrayAdapter for a ListView

[Fuente: http://devtut.wordpress.com/2011/06/09/custom-arrayadapter-for-a-listview-android/]

For work, I needed to come up with a nice way to display information from multiple objects, and have the ability to format each individual part of that information.

I decided to use a ListView. Since I had an ArrayList of the objects I created, this ListView needed to extend ArrayAdapter.

For this simple example to work, you are going to need 3 Java classes (MainActivity, Item, ItemAdapter), 2 XML files (main, list_item), and of course, the Android Manifest.

To get a get visual on what we’re creating here, take a look at the screenshot below:

Custom Adapter Example

So first off, you’ll need to create your Item class. The one in the example is simple, three fields and their getters/setters. Here is the code if you’d like to take a look:

Item

package com.doctororeo.examples;

public class Item {
	private String details;
	private String name;
	private int price;

	public Item(){

	}

	public Item(String i, String d, int p){
		this.details = d;
		this.name = i;
		this.price = p;
	}

	public String getDetails() {
		return details;
	}

	public void setDetails(String details) {
		this.details = details;
	}

	public String getName() {
		return name;
	}

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

	public int getPrice() {
		return price;
	}

	public void setPrice(int price) {
		this.price = price;
	}

}

If you’re unsure of what this code does, please read further on creating classes/objects in the Sun API – the documentation is adequate for beginners.

The next step is where things get a bit hairy. What we’re going to need to do is make our ItemAdapter class – this class will extend the ArrayAdapter class. When we extend a class, the child class inherits all of the parent class’ attributes.

Ok – skip to the code. I’ve commented it fairly well, but you’re welcome to ask if anything confuses you.

ItemAdapter

package com.doctororeo.examples;

import java.util.ArrayList;

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

public class ItemAdapter extends ArrayAdapter<Item> {

	// declaring our ArrayList of items
	private ArrayList<Item> objects;

	/* here we must override the constructor for ArrayAdapter
	* the only variable we care about now is ArrayList<Item> objects,
	* because it is the list of objects we want to display.
	*/
	public ItemAdapter(Context context, int textViewResourceId, ArrayList<Item> objects) {
		super(context, textViewResourceId, objects);
		this.objects = objects;
	}

	/*
	 * we are overriding the getView method here - this is what defines how each
	 * list item will look.
	 */
	public View getView(int position, View convertView, ViewGroup parent){

		// assign the view we are converting to a local variable
		View v = convertView;

		// first check to see if the view is null. if so, we have to inflate it.
		// to inflate it basically means to render, or show, the view.
		if (v == null) {
			LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
			v = inflater.inflate(R.layout.list_item, null);
		}

		/*
		 * Recall that the variable position is sent in as an argument to this method.
		 * The variable simply refers to the position of the current object in the list. (The ArrayAdapter
		 * iterates through the list we sent it)
		 * 
		 * Therefore, i refers to the current Item object.
		 */
		Item i = objects.get(position);

		if (i != null) {

			// This is how you obtain a reference to the TextViews.
			// These TextViews are created in the XML files we defined.

			TextView tt = (TextView) v.findViewById(R.id.toptext);
			TextView ttd = (TextView) v.findViewById(R.id.toptextdata);
			TextView mt = (TextView) v.findViewById(R.id.middletext);
			TextView mtd = (TextView) v.findViewById(R.id.middletextdata);
			TextView bt = (TextView) v.findViewById(R.id.bottomtext);
			TextView btd = (TextView) v.findViewById(R.id.desctext);

			// check to see if each individual textview is null.
			// if not, assign some text!
			if (tt != null){
				tt.setText("Name: ");
			}
			if (ttd != null){
				ttd.setText(i.getName());
			}
			if (mt != null){
				mt.setText("Price: ");
			}
			if (mtd != null){
				mtd.setText("$" + i.getPrice());
			}
			if (bt != null){
				bt.setText("Details: ");
			}
			if (btd != null){
				btd.setText(i.getDetails());
			}
		}

		// the view must be returned to our activity
		return v;

	}

}

As you can see, the ItemAdapter sets the text for each TextView. We haven’t implemented the TextViews yet. Time for a bit of XML.

main.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"
   >
<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="No items to display."/>
</LinearLayout>

list_item.xml

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

    android:padding="6dip">
    <!-- Item Name -->
   <TextView
		android:id="@+id/toptext"

		android:layout_width="wrap_content"
		android:layout_height="26dip"

		android:layout_alignParentTop="true"
		android:layout_alignParentLeft="true"

		android:textStyle="bold"
		android:singleLine="true"
		android:ellipsize="marquee"
   />

       <!-- Actual Item Name Data -->
   <TextView
		android:id="@+id/toptextdata"

		android:layout_width="fill_parent"
		android:layout_height="wrap_content"

		android:layout_alignParentRight="true"
		android:layout_alignParentTop="true"
		android:layout_toRightOf="@id/toptext"

		android:singleLine="true"
		android:ellipsize="marquee"
   />

   <!-- Price Tag -->
   <TextView
	 	android:id="@+id/middletext"

	 	android:layout_width="wrap_content"
	 	android:layout_height="26dip"

		android:layout_alignParentLeft="true"
		android:layout_below="@id/toptext"

	 	android:textStyle="bold"
	 	android:gravity="center_vertical"
   	/>

   	       <!-- Actual Price Data -->
   <TextView
		android:id="@+id/middletextdata"

		android:layout_width="fill_parent"
		android:layout_height="26dip"

		android:layout_alignParentRight="true"
		android:layout_below="@id/toptext"
		android:layout_toRightOf="@id/middletext"

		android:gravity="center_vertical"
   />

   	<!-- Description Tag -->
   <TextView
		android:layout_width="fill_parent"
		android:layout_height="wrap_content"

		android:layout_alignParentRight="true"
		android:layout_below="@id/middletext"

		android:textStyle="bold"
		android:id="@+id/bottomtext"
		android:singleLine="false"
   />
   <!-- This is the actual description -->
      <TextView
		android:layout_width="fill_parent"
		android:layout_height="wrap_content"

		android:layout_alignParentRight="true"
		android:layout_alignParentBottom="true"
		android:layout_below="@id/bottomtext"

		android:id="@+id/desctext"
		android:singleLine="false"
   />
</RelativeLayout>

I’m not going to explain how these two files work, because the point of this article isn’t to teach XML. However, as always, if you have any questions feel free to ask in the comments.

There’s one last part to this equation. The MainActivity file – it must extend ListActivity instead of Activity. If you don’t do this, it WILL NOT work. I only emphasize this, because I forgot to when I re-did the code for this article. Only took me a few minutes to figure it out, but I figured I might as well mention it. In addition to displaying the data, I’m going to show you how to use a thread to get the data. This is useful when processing large amounts of data. In an actual application, you may want to include a progress bar to show the user that something is “being done”, otherwise it will appear as though the application has frozen. I won’t be showing you how to do this in this article, but it’s fairly easy to find on Google.

MainActivity

package com.doctororeo.examples;

import java.util.ArrayList;

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

public class MainActivity extends ListActivity {

	// declare class variables
	private ArrayList<Item> m_parts = new ArrayList<Item>();
	private Runnable viewParts;
	private ItemAdapter m_adapter;

    /** Called when the activity is first created. */
    public void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        // instantiate our ItemAdapter class
        m_adapter = new ItemAdapter(this, R.layout.list_item, m_parts);
        setListAdapter(m_adapter);

        // here we are defining our runnable thread.
        viewParts = new Runnable(){
        	public void run(){
        		handler.sendEmptyMessage(0);
        	}
        };

        // here we call the thread we just defined - it is sent to the handler below.
        Thread thread =  new Thread(null, viewParts, "MagentoBackground");
        thread.start();
    }

    private Handler handler = new Handler()
	 {
		public void handleMessage(Message msg)
		{
			// create some objects
			// here is where you could also request data from a server
			// and then create objects from that data.
			m_parts.add(new Item("MyItemName", "This is item #1", 0));
			m_parts.add(new Item("MyItemName #2", "This is item #2", 0));

			m_adapter = new ItemAdapter(MainActivity.this, R.layout.list_item, m_parts);

			// display the list.
	        setListAdapter(m_adapter);
		}
	};
}

 

That’s it. That’s all the code you need to create a ListView with multiple data fields, and the ability to customize each part of it. Feel free to play with the code for learning purposes.

Stay tuned for more examples! Please vote below to let me know how I’m doing!