Weblog of Alif

Exploring the Web: An Apple a day

Custom List Views with icons and Listeners in Android

August 15, 2011 -- alif

Customizing List Views in Android is very useful for most applications, but, there aren't many good tutorials on the web which explains how to properly customize list views (especially cases where there are Listeners with every element of ListView). So, I decided to write one.

Here's a screenshot of the what the output would look like:
Address Book Sample
Each List Item would have:

  • Name, a phone and email icon.
  • - Clicking on phone icon initiates a call.
  • - Clicking on email icon opens mail client

Please note that my emphasis is on the functionality and not on the aesthetics =).


This tutorial is divided into the following parts:

  1. Designing the Layout
  2. Developing the Custom Adapter
  3. Developing the Activity


Designing the Layout

I create 2 layout XML Files.

  1. main.xml - For the main activity
  2. address_book_list_item.xml - Represents each List Items on the List View


Here's the main.xml:



	
    
    
    	
    


As, can be seen, it has 2 elements only. A TextView for displaying the top label's text, and a ListView. I like to create separate styles instead of putting them inside the layout.xml file. Also, if you notice, I directly added ListView i.e. my activity won't extend ListActivity (as you will see later).


Here's the address_book_list_item.xml:



  
  		
  
  
  	
  	
  	
  	
  


A Straight-forward layout with the name and icons on the right. I use RelativeLayout, as its much faster than LinearLayout as per Google's Guideline. If you are new to RelativeLayout, please check out here


Part 2: Developing the Custom Adapter

To have a custom List View, we need to create our own Adapter and override the getView method. If you don't know much about this, please check out this excellent Google I/O video. For this task, I extended my Custom Adapter from SimpleAdapter.

The data in a SimpleAdapter is an ArrayList of HashMap. The constructor of the SimpleAdapter looks like:


 SimpleAdapter(Context context, List data, int resource, String[] from, int[] to)

I call my Adapter as SimpleAddressBookAdapter and it's constructor has the same parameters as that of SimpleAdapter. Here's the constructor:


	public SimpleAddressBookAdapter(Context context,
			List data, int resource, String[] from, int[] to) {

		super(context, data, resource, from, to);
		
		// save the ArrayList and context for later usage
		listMap = data;	
		context = context;
		resource = resource;
		
		resourceList = to;
		fromList = from;
      }

In addition to calling the super class, I store all the parameters passed into an instance variable for later usage (they will be needed on getView).

According to Google's Guideline, having a static inner class for the views greatly improves performance. So, I declare a static inner class ViewHolder inside SimpleAddressBookAdapter as follows:

	private static class ViewHolder
	{
		TextView[] textView;		
		ImageView phoneIcon;
		ImageView emailIcon;
		
		int position;
	}

The attributes are self-explanatory. The textView is used to map the TextView content's inside List View Item (I could have avoided using a textView array, but I used it if incase I decide to add a few textView's). Position stores the index of that List Item in the entire List View

Now here's the signature of getView:

public View getView(int position, View convertView, ViewGroup parent)

Basically, this method will generate a View for a List Item on the ListView. Inside the getView method, I check if the convertView is null or not. If it's null, then I generate a view for that List Item, attach Listeners, and store it for later usage. The following code is used to store it:

convertView.setTag(holder);
If it's not null, that means, it was previously generated, so I simply get it from holder from a previously generated convertView. So, I get it using the code below:
holder = (ViewHolder) convertView.getTag();

Here's the entire code of getView method:


	public View getView(int position, View convertView, ViewGroup parent)
	{
		// declare it final so that it could be accessed from the inner class methods
		final ViewHolder holder;
		
		if (convertView == null) {
		  LayoutInflater mInflater = (LayoutInflater)  context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
			convertView = mInflater.inflate(resource, parent, false);
						
			holder = new ViewHolder();	
			holder.textView = new TextView[fromList.length];
			
			for (int i = 0; i < fromList.length; i++) {
				holder.textView[i] = (TextView) convertView.findViewById(resourceList[i]);
			}
			
			holder.phoneIcon = (ImageView) convertView.findViewById(R.id.address_book_item_call);
			holder.emailIcon = (ImageView) convertView.findViewById(R.id.address_book_item_email);
			
			// add a listener for phone call
			holder.phoneIcon.setOnClickListener(new OnClickListener() {

				@Override
				public void onClick(View v) {

					String phone = SimpleAddressBookAdapter.listMap.get(holder.position).get("phone");
					ActivityHelper.startActivity(ActivityManager.PHONE_CALL, phone);
				}
				
			});
			
			// add listener for email 
			holder.emailIcon.setOnClickListener(new OnClickListener() {

				@Override
				public void onClick(View v) {

					String email = SimpleAddressBookAdapter.listMap.get(holder.position).get("email");
					ActivityHelper.startActivity(ActivityManager.EMAIL, email);
				}
				
			});
			
			convertView.setTag(holder);
			
		}
		else {
			holder = (ViewHolder) convertView.getTag();
		}
		
		for (int i = 0; i < fromList.length; i++) {
			holder.textView[i].setText(SimpleAddressBookAdapter.listMap.get(position).get(fromList[i]));
		}
		
		holder.position = position;
		
		
		return convertView;
	}	

So, that takes care of creating a custom Adapter!


Part 3: Developing the Activity

Now, the final part consisting of developing an activity for displaying the ListView. In most examples, people usually extend ListActivity, however, I will extend Activity in my code. The reason for this is because sometimes I may need to create by own BaseActivity and I may have to create List's Activity by extend BaseActivity. I call my Activity as AddressBookActivity

Here's the onCreate method:

    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        super.setContentView(R.layout.main);
        
        // update the current Activity

        ActivityHelper.setActivity(this);
        this.initAddressBookData();
        this.initListView();
    }

I set the layout and initialize the data's. ActivityHelper is a helper class which keeps track of current Activity running in the Application, and it has some helper method like startActivity etc. initAddressBookData method puts some dummy values into the List. initListView initializes the List View.

Here's a peek at the initListView method:

    public void initListView() 
    {   
    	/* get the list view from the layout */
    	ListView listView = (ListView) super.findViewById(R.id.address_book_listview);
    	
    	/* create an adapter */
    	this.adapter = new SimpleAddressBookAdapter(
    			listView.getContext(), 
    			this.addressList, 
    			R.layout.address_book_list_item,
    			new String[] { "name" },
    			new int[] { R.id.address_book_item_name }
    		);
    	
    	// set the list's adapter
    	listView.setAdapter(this.adapter);
    }

I get the ListVIew from the XML Layout file, and i set it's adapter using listView.setAdapter.

That's about it!. Please download the zip files and go through the code.

Download from Github

Comments

Submitted by adam (not verified) on

awesome job! very straightforward and unique. most tutorials show the bare minimum whereas these are useful for nontrivial apps

Submitted by Josue C.S Martins (not verified) on

Thanks man

Submitted by amp (not verified) on

thanks! it was helpful. Especially the part of storing the position of the item on the ViewHolder class.

Add new comment