Tuesday, 5 August 2014

Custom List View with Check Boxes

Many a time you find that default list view item layout is not good enough for your app. You may want to include many fields such as an image or a check box along with list item text in each list item. You can have desired fields for each item by creating your own  list item layout with the fields and passing it to data adapter along with data for the fields and associating the adapter with the list view of your activity.

First, create your activity layout that contains ListView field as following:

File: activity_main.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="in.queper.blog.custlistitemcheckbox.MainActivity" >

    <ListView
        android:id="@+id/lvBooks"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        >
    </ListView>

</RelativeLayout>

Have a custom list item layout with desired fields in it. An example that contains one text field and one check box field is as follows:

File: list_item.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="horizontal"
>
<TextView
android:text=""
    android:id="@+id/tvBookTitle"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:gravity="left"
    android:layout_gravity="left"
    android:layout_weight="1"
    />

<CheckBox
android:id="@+id/cbBook"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:checked="false"
android:focusable="false"
android:gravity="end"
        />
</LinearLayout>

Important: Make sure attribute "android:focusable" is set to to "false". Otherwise, control may not go to  list item listener or context menu methods (not described in this post) when you click on a list item.

Define an appropriate adapter  class for populating the list view with data. As only book title is needed to populate each list item, we need a simple class extended from ArrayAdapater class that accepts String objects. An example class is:

    public class BookArrayAdapter extends ArrayAdapter<String>{

    private Context c;
    private int layoutResourceId;
    private String items[];
   
    public BookArrayAdapter(Context context, int loResourceId, String bks[]) {
    super(context, loResourceId, books);
    c = context;
    layoutResourceId = loResourceId;
    items = bks;
    }
   
    public String getItem(int i) {
    return items[i];
    }
   
    @Override
        public View getView(int position, View convertView, ViewGroup parent) {
                   View vwListItem = convertView;
                   if (vwListItem == null) {
                       LayoutInflater li = ((Activity)c).getLayoutInflater();
                       vwListItem = li.inflate(layoutResourceId, null);
                   }
                   final String bookTitle = items[position];
                   if (bookTitle != null) {                        
                       TextView tvBookTitle = (TextView) vwListItem.findViewById(R.id.tvBookTitle);
                       tvBookTitle.setText(bookTitle);
                       /*
                        * Get user selection specified in the check box in the list item
                        */                        
                       CheckBox cb = (CheckBox) vwListItem.findViewById(R.id.cbBook);

                       // Check box listener.
                       cb.setOnCheckedChangeListener(new OnCheckedChangeListener() {
                          @Override
                          public void onCheckedChanged(CompoundButton buttonView,
                          boolean isChecked) {
                          Toast.makeText(getBaseContext(),"Checked for book '" +
                          bookTitle + "'?:" + isChecked, Toast.LENGTH_SHORT).show();    
                          }
                       });
                   }
                   return vwListItem;
           }
    }

The method getView() gives access to each list item. You can extract fields from the list item and do required operations on them.  The method sets a listener for check box  so that you can perform required action when user clicks on the check box.

In the main activity you need to create an object of the adapater class, pass custom list view item resource ID and the actual data used to populate the list items.

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
setTitle("Custom List With Check Box");
     
        // Create book adapter with custom list item layout and data items
bookAdapter = new BookArrayAdapter(this, R.layout.list_item, books);
lv = (ListView)findViewById(R.id.lvBooks);
lv.setAdapter(bookAdapter);

// List item click listener.
lv.setOnItemClickListener(new OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View vwItem, int position,
                    long id) {
                TextView tvBookTitle = (TextView) vwItem.findViewById(R.id.tvBookTitle);
      Toast.makeText(getBaseContext(),"Clicked on book '" +
              tvBookTitle.getText().toString(), Toast.LENGTH_SHORT).show();
            }
        });
    }

R.layout.list_item refers to your customized list item layout.

Variable books is an array of strings that contain book titles where each book title forms a list item along with check box. Note that if you want to supply an initial value for check box, then you need to create a class that contains a book title and an initial value for the check box. Accordingly you need to modify the adapter class. You can also set a listener for the list view that is invoked when you tap on a list item and get all data associated with the list item.

Complete source code is as follows:

package in.queper.blog.custlistitemcheckbox;

import android.support.v7.app.ActionBarActivity;
import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ArrayAdapter;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;


public class MainActivity extends ActionBarActivity {

    private BookArrayAdapter bookAdapter;
 
 
    ListView lv = null;

    String books[] = {"Introduction to C++ ", "Guide to C#", "Core Java",
    "All about JavaScript", "Catch Python!"};

    public class BookArrayAdapter extends ArrayAdapter<String>{

    private Context c;
    private int layoutResourceId;
    private String items[];
   
    public BookArrayAdapter(Context context, int loResourceId, String bks[]) {
    super(context, loResourceId, books);
    c = context;
    layoutResourceId = loResourceId;
    items = bks;
    }
   
    public String getItem(int i) {
    return items[i];
    }
   
    @Override
        public View getView(int position, View convertView, ViewGroup parent) {
                   View vwListItem = convertView;
                   if (vwListItem == null) {
                       LayoutInflater li = ((Activity)c).getLayoutInflater();
                       vwListItem = li.inflate(layoutResourceId, null);
                   }
                   final String bookTitle = items[position];
                   if (bookTitle != null) {                        
                       TextView tvBookTitle = (TextView) vwListItem.findViewById(R.id.tvBookTitle);
                       tvBookTitle.setText(bookTitle);
                       /*
                        * Get user selection specified in the check box in the list item
                        */                        
                       CheckBox cb = (CheckBox) vwListItem.findViewById(R.id.cbBook);

                       // Check box listener.
                       cb.setOnCheckedChangeListener(new OnCheckedChangeListener() {
                          @Override
                          public void onCheckedChanged(CompoundButton buttonView,
                          boolean isChecked) {
                          Toast.makeText(getBaseContext(),"Checked for book '" +
                          bookTitle + "'?:" + isChecked, Toast.LENGTH_SHORT).show();    
                          }
                       });
                   }
                   return vwListItem;
           }
    }
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
setTitle("Custom List With Check Box");
     
        // Create book adapter with custom list item layout and data items
bookAdapter = new BookArrayAdapter(this, R.layout.list_item, books);
lv = (ListView)findViewById(R.id.lvBooks);
lv.setAdapter(bookAdapter);

// List item click listener.
lv.setOnItemClickListener(new OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View vwItem, int position,
                    long id) {
                TextView tvBookTitle = (TextView) vwItem.findViewById(R.id.tvBookTitle);
             Toast.makeText(getBaseContext(),"Clicked on book '" +
                       tvBookTitle.getText().toString(), Toast.LENGTH_SHORT).show();
            }
        });
    }
 
}

Note that main class extends from ActionBarActivity. It could be just Activity but not ListActivity.