Android | Custom ListView with Titles, Icons & Counter

android-pro-listviewYou can customize the ListView by providing a new layout for the list items “rows”. The new layout can be more than just a single TextView. Here will see how to develop a custom ListView with items having leading icons “ImageView“, trailing counters “TextView” and some text in between.  Also, we will see how to add items that will act as a group header “title” for a set of other items.

 

 

Objectives:

  • How to create ListView with custom View?
  • How to create ListView items with leading icons “ImageView” and trailing counters?
  • How to create ListView items that act as header of group of items?

Environment & Tools:

  • Android Developer Tools (ADT) (or Eclipse + ADT plugin)
  • AVD Nexus S Android 4.3 “emulator” or,
  • Min SDK 8

( 1 ) Create Layout “UI” for ListView Items

We will define the items layout. We need two different layouts one for the target item and one for the group header item.

1. target_item.xml

2. group_header_item.xml

1. Target Item

  • res/layout/target_item.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="48dp"
    android:background="#F3F3F3">

     <!-- icon -->
     <ImageView
         android:id="@+id/item_icon"
         android:layout_width="32dp"
         android:layout_height="32dp"
         android:layout_alignParentLeft="true"
         android:layout_marginLeft="8dp"
         android:layout_marginRight="8dp"
         android:layout_marginTop="8dp"
         android:src="@drawable/action_help"
         />

    <!-- title -->
    <TextView
         android:id="@+id/item_title"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:layout_toRightOf="@+id/item_icon"
         android:layout_alignBaseline="@+id/item_counter"
         android:textSize="18dp" />

        <!-- counter -->
        <TextView
            android:id="@+id/item_counter"
            android:layout_width="32dp"
            android:layout_height="32dp"
            android:layout_alignParentRight="true"
            android:layout_marginRight="8dp"
            android:layout_marginTop="8dp"
            android:background="@drawable/rectangle"
            android:gravity="center"
	        android:textColor="#FFFFFF"
            android:textSize="12sp"
            android:textStyle="bold" />

</RelativeLayout>

Note: @drawable/rectangle is defined in res/drawable/rectangle.xml

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
   <solid android:color="#5490CC"></solid>
   <corners android:radius="8px"></corners> 
   <stroke  android:width="2dp" android:color="#A4C2E0"></stroke>  
</shape>

2. Group Header Item

  • res/layout/group_header_item.xml

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

   	android:background="#F3F3F3">

    <!-- title -->
    <TextView
         android:id="@+id/header"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:gravity="center_vertical"
         android:textSize="16dp"
         android:layout_marginLeft="12dp"
         android:layout_marginBottom="4dp"
         android:layout_alignParentBottom="true" />

    <!--  divider -->
    <View
        android:layout_width="match_parent"
        android:layout_height="1dp"
        android:layout_marginBottom="1dp"
        android:layout_alignParentBottom="true"
        android:background="#DADADC" ></View>

</RelativeLayout>

( 2 ) Model.java Class “Model”

  • Create a model class and call it “Model.java” to hold data to be displayed on list items.
  • We need to define an object of type Model to hold the resources “data” for each list item.
  • The way we define the Model object “which constructor we use” will determine which list item layout “target item or group header item” will be displayed. Basically we set the a boolean property “isGroupHeader” to be always false “if we are creating the Model object for target item layout” unless we define the Model object “for group header item layout” using the single argument constructor then isGroupHeader will be true.
  • For target_item we need a Model object to be declared as following:
new Model(R.drawable.action_help_32,"Menu Item 1","1")
//R.drawable.action_help_32 is a png image in res/drawable folder

  • For group_header_item we need a Model object to be declared as following:
new Model("Group Title")

  • src/com/hmkcode/android/Model.java
package com.hmkcode.android;

public class Model{

	private int icon;
	private String title;
	private String counter;

	private boolean isGroupHeader = false;

	public Model(String title) {
		this(-1,title,null);
		isGroupHeader = true;
	}
	public Model(int icon, String title, String counter) {
		super();
		this.icon = icon;
		this.title = title;
		this.counter = counter;
	}

//gettters & setters...
}

( 3 ) MyAdapter.java

  • MyAdpater is the class that will fill the gap between step 1 & 2 i.e. the item layout and Model data.
  • MyAdapter extends ArrayAdapter<Model>
  • MyAdapter(Context context, ArrayList<Model> modelsArrayList) constructor takes context i.e. “Activity” & array of objects of type Model.
  • Override getView() method
      • getView() method will basically go over the passed array of objects “Model”
      • If the Model object isGroupHeader = false then inflate target_item, get its views, set their values and return View with target_layout
      • else return View with group_header_layout.
  • src/com/hmkcode/android/MyAdapter.java
package com.hmkcode.android;

import java.util.ArrayList;

import com.hmkcode.android.R;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.TextView;

public class MyAdapter extends ArrayAdapter<Model> {

		private final Context context;
		private final ArrayList<Model> modelsArrayList;

		public MyAdapter(Context context, ArrayList<Model> modelsArrayList) {

			super(context, R.layout.target_item, modelsArrayList);

			this.context = context;
			this.modelsArrayList = modelsArrayList;
		}

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

			// 1. Create inflater 
			LayoutInflater inflater = (LayoutInflater) context
		        .getSystemService(Context.LAYOUT_INFLATER_SERVICE);

			// 2. Get rowView from inflater

			View rowView = null;
			if(!modelsArrayList.get(position).isGroupHeader()){
				rowView = inflater.inflate(R.layout.target_item, parent, false);

				// 3. Get icon,title & counter views from the rowView
				ImageView imgView = (ImageView) rowView.findViewById(R.id.item_icon); 
				TextView titleView = (TextView) rowView.findViewById(R.id.item_title);
				TextView counterView = (TextView) rowView.findViewById(R.id.item_counter);

			    // 4. Set the text for textView 
			    imgView.setImageResource(modelsArrayList.get(position).getIcon());
			    titleView.setText(modelsArrayList.get(position).getTitle());
			    counterView.setText(modelsArrayList.get(position).getCounter());
			}
			else{
					rowView = inflater.inflate(R.layout.group_header_item, parent, false);
					TextView titleView = (TextView) rowView.findViewById(R.id.header);
				    titleView.setText(modelsArrayList.get(position).getTitle());

			}

		    // 5. retrn rowView
		    return rowView;
		}
}

( 4 ) MainActivity.java

  • For MainActivity class we have two options either to extend Activity or ListActivity, comments on the code explain how to use each one.
  • src/com/hmkcode/android/MainActivity.java
package com.hmkcode.android;

import java.util.ArrayList;

import android.app.Activity;
import android.app.ListActivity;
import android.os.Bundle;
import android.widget.ListView;

public class MainActivity extends ListActivity {

	public void onCreate(Bundle icicle) {
		super.onCreate(icicle);

		// if extending Activity
		//setContentView(R.layout.activity_main);

		// 1. pass context and data to the custom adapter
		MyAdapter adapter = new MyAdapter(this, generateData());

		// if extending Activity 2. Get ListView from activity_main.xml
		//ListView listView = (ListView) findViewById(R.id.listview);

		// 3. setListAdapter
		//listView.setAdapter(adapter); if extending Activity
		setListAdapter(adapter);
	}

	private ArrayList<Model> generateData(){
		ArrayList<Model> models = new ArrayList<Model>();
	    models.add(new Model("Group Title"));
	    models.add(new Model(R.drawable.action_help_32,"Menu Item 1","1"));
	    models.add(new Model(R.drawable.action_search_32,"Menu Item 2","2"));
	    models.add(new Model(R.drawable.collections_cloud_32,"Menu Item 3","12"));

	    return models;
	}

}

Run the app you will get the following UI

Icons used in this sample are downloaded from Download the Action Bar Icon Pack

Source Code @ GitHub