ListView with CheckBox - Single row selection in Android

    In my previous post about ListView with CheckBox, users can select many ListView items (rows) by checking CheckBox. It's will use to save or put data to other Activity. But what about single row can be checked (selected) in a list? Sometimes, this requiment still happens when the aim is you can select only one case! For this, we can also use CheckBox-ListView design. Through this tip, I provide a solution can resolved it through handling CheckBox on click event and update adapter immediately!
    DEMO VIDEO:

    First, define a simple activity layout file (xml), only includes a ListView:
    The most important work will be done in ListView adapter class (extends from ArrayAdapter class). In this, define a int variable called selectedPosition by defaul value is -1. When we checked at each CheckBox, it's value will be changed to row's postion and restore to the default values when unchecked. So, we have this method:
private View.OnClickListener onStateChangedListener(final CheckBox checkBox, final int position) {
        return new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (checkBox.isChecked()) {
                    selectedPosition = position;
                } else {
                    selectedPosition = -1;
                }
                notifyDataSetChanged(); // update adapter after changed
            }
        };
    }
    In getView() method (override from parent class), set event for CheckBox through call:
holder.checkBox.setOnClickListener(onStateChangedListener(holder.checkBox, position));
    Important Note: Always use setOnClickListener() for CheckBox instead of setOnCheckedChangeListener() because of recycling feature of ListView, the first rows cannot be checked!
    Provide a ViewHolder class to get scroll smoother, we have full code for this adapter class:
package info.devexchanges.singlchecklistview;

import android.app.Activity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.TextView;

import java.util.List;

public class ListViewAdapter extends ArrayAdapter<Friend> {

    private List<Friend> myFriends;
    private Activity activity;
    private int selectedPosition = -1;

    public ListViewAdapter(Activity context, int resource, List<Friend> objects) {
        super(context, resource, objects);

        this.activity = context;
        this.myFriends = objects;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        ViewHolder holder;
        LayoutInflater inflater = (LayoutInflater) activity.getSystemService(Activity.LAYOUT_INFLATER_SERVICE);

        // If holder not exist then locate all view from UI file.
        if (convertView == null) {
            // inflate UI from XML file
            convertView = inflater.inflate(R.layout.item_listview, parent, false);
            // get all UI view
            holder = new ViewHolder(convertView);
            // set tag for holder
            convertView.setTag(holder);
        } else {
            // if holder created, get tag from view
            holder = (ViewHolder) convertView.getTag();
        }

        holder.checkBox.setTag(position); // This line is important.

        holder.friendName.setText(getItem(position).getName());
        if (position == selectedPosition) {
            holder.checkBox.setChecked(true);
        } else holder.checkBox.setChecked(false);

        holder.checkBox.setOnClickListener(onStateChangedListener(holder.checkBox, position));

        return convertView;
    }

    private View.OnClickListener onStateChangedListener(final CheckBox checkBox, final int position) {
        return new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (checkBox.isChecked()) {
                    selectedPosition = position;
                } else {
                    selectedPosition = -1;
                }
                notifyDataSetChanged();
            }
        };
    }

    private static class ViewHolder {
        private TextView friendName;
        private CheckBox checkBox;

        public ViewHolder(View v) {
            checkBox = (CheckBox) v.findViewById(R.id.check);
            friendName = (TextView) v.findViewById(R.id.name);
        }
    }
}
    In activity, I get a dummy data set from text file in Assests to create a List object use for ListView then. Set header for our ListView, handling each item click event,...we have full code:
package info.devexchanges.singlchecklistview;

import android.app.Dialog;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.text.method.CharacterPickerDialog;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.AdapterView;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;

public class MainActivity extends AppCompatActivity {

    private ArrayList<Friend> friendArrayList;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        friendArrayList = new ArrayList<>();

        ListView listView = (ListView) findViewById(R.id.list_item);

        //set ListView header
        LayoutInflater inflater = getLayoutInflater();
        View header = inflater.inflate(R.layout.header_listview, listView, false);
        listView.addHeaderView(header, null, false);

        readDataFromAssets();

        ListViewAdapter adapter = new ListViewAdapter(this, R.layout.item_listview, friendArrayList);
        listView.setAdapter(adapter);

        listView.setOnItemClickListener(onItemClickListener());
    }

    private AdapterView.OnItemClickListener onItemClickListener() {
        return new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView parent, View view, int position, long id) {
                final Dialog dialog = new Dialog(MainActivity.this);
                dialog.setContentView(R.layout.layout_dialog);
                dialog.setTitle("Friend Details");
                dialog.setCancelable(true);

                TextView friendID = (TextView) dialog.findViewById(R.id.position);
                TextView friendName = (TextView) dialog.findViewById(R.id.name);

                friendID.setText("Position: " + (position + 1));
                friendName.setText("Name: " + ((Friend) parent.getItemAtPosition(position)).getName());

                dialog.show();
            }
        };
    }

    private void readDataFromAssets() {
        BufferedReader reader = null;
        try {
            reader = new BufferedReader(new InputStreamReader(getAssets().open("friend.txt")));

            // do reading, usually loop until end of file reading
            String line;
            while ((line = reader.readLine()) != null) {
                friendArrayList.add(new Friend(line));
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (reader != null) {
                try {
                    reader.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
     Output when app running:
    Finally, there are some necessary files to complete this project:
- A layout for each ListView item, contains a CheckBox and a TextView:
Note: Always set android:focusable="false" property to CheckBox if you want ListView row clickable!
- A Dialog layout (show on each item clicked):
Output:
- In activity code, we added a header layout for ListView, so this is it xml file:
Conclusions: There are a lot of ways to make this trick in ListView with CheckBox, my solution is just only one of them. By searching on Internet, you'll find out another quickly. Hoping my simple project in this tip is helpful!


Android ListView with Checkbox example

Android ListView with Checkbox example

In this example, I present:

  • How to combine check box to "filter" ListView elements.
  • Sending a Parcelable object or a list of Parcelable objects through Intent.
  • Adding many view elements to a ViewGroup

Now, Let's start an Android project!

Defining a Friend object for use. Implement Parcelable to put through Intent later. Friend.java source code may be like this:
Friend.java
public class Friend implements Parcelable {

    private boolean gender;
    private String name;
    private boolean isSelected;

    /**
     * Create parcelable of friend
     */
    public static final Parcelable.Creator<Friend> CREATOR = new Parcelable.Creator<Friend>() {
 public Friend createFromParcel(Parcel in) {
     return new Friend(in);
 }

 public Friend[] newArray(int size) {
     return new Friend[size];
 }
    };

    /**
     * Create Friend from Parcel object.
     * 
     * @param in
     */
    public Friend(Parcel in) {
 this.name = in.readString();
 this.gender = in.readByte() != 0;
 this.isSelected = in.readByte() != 0;
    }

    @Override
    public int describeContents() {
 return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
 dest.writeString(this.name);
 dest.writeByte((byte) (this.gender ? 1 : 0));
 dest.writeByte((byte) (this.isSelected ? 1 : 0));
    }

    public Friend(String name, boolean gender) {
 this.name = name;
 this.gender = gender; // true is male, false is woman
 this.isSelected = false; // not selected when create
    }

    public boolean isGender() {
 return gender;
    }

    public void setGender(boolean gender) {
 this.gender = gender;
    }

    public String getName() {
 return name;
    }

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

    public boolean isSelected() {
 return isSelected;
    }

    public void setSelected(boolean isSelected) {
 this.isSelected = isSelected;
    }

}
Next, we will create MainActivity. In this, we have some important methods:

  •  setListViewHeader(): add header for ListView, click at this header button, we will go to SelectedListActivity contain all friends which checked.
  •  setAdapterData): provide data for ListView.
  • Handling event for "Go to selected list" button.

Source code:
MainActivity.java
public class MainActivity extends Activity {

    private ListView listView;
    private View btnList;

    private ArrayList<Friend> friends;
    private ListViewAdapter adapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 setContentView(R.layout.activity_main);

 listView = (ListView) findViewById(R.id.list_view);
 setListViewHeader();
 setListViewAdapter();
 setAdapterData();
 btnList.setOnClickListener(gotoSelectedListActivity()); // go to checked list
        
    }

    /**
     * set listview footer method
     */
    private void setListViewHeader() {
 LayoutInflater inflater = getLayoutInflater();
 ViewGroup header = (ViewGroup) inflater.inflate(
  R.layout.header_listview, listView, false);
 listView.addHeaderView(header, null, false);

 btnList = (Button) findViewById(R.id.button);
    }

    private void setListViewAdapter() {
 friends = new ArrayList<Friend>();
 adapter = new ListViewAdapter(this, R.layout.item_listview, friends);
 listView.setAdapter(adapter);
    }

    private void setAdapterData() {
 friends.add(new Friend("Harry Brown", true));
 friends.add(new Friend("Nguyen Tuan Anh", true));
 friends.add(new Friend("Rin Zuzuki", false));
 friends.add(new Friend("Zheng Lee", true));
 friends.add(new Friend("Ana Olenhina", false));
 friends.add(new Friend("Nhat Trang", false));
 friends.add(new Friend("Mai Phuong", false));
 friends.add(new Friend("Yasmin Abdulaziz", false));
 friends.add(new Friend("Park Ji Won", true));

 adapter.notifyDataSetChanged(); // update adapter
    }

    /**
     * handle footer listview button event
     * @return
     */
    private OnClickListener gotoSelectedListActivity() {
 return new OnClickListener() {
     
     @Override
     public void onClick(View v) {
  Intent intent = new Intent(MainActivity.this, SelectedListActivity.class);
  intent.putParcelableArrayListExtra("Checked List", friends);
  startActivity(intent);
     }
 };
    }
}
And this is activity_main.xml file for layout:
<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="com.blogspot.hongthaiit.listviewwithcheckbox.MainActivity" >

    <ListView
        android:id="@+id/list_view"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:scrollbars="none" >
    </ListView>

</RelativeLayout>
As already mentioned above, we must create SelectedListActivity to show selected elements. In this activity, I will present the way to add many elements to a LinearLayout in generateDataToContainerLayout() method. Full file source code:
SelectedListActivity.java
public class SelectedListActivity extends Activity {

    private LinearLayout container;
    private ArrayList<Friend> checkedList;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 setContentView(R.layout.activity_list);

 container = (LinearLayout) findViewById(R.id.layout);
 checkedList = new ArrayList<Friend>(); // initializing list
 getDataFromIntent(); // receive data from intent (put by MainActivity)
 generateDataToContainerLayout();
    }

    private void getDataFromIntent() {
 checkedList = getIntent().getParcelableArrayListExtra("Checked List");
 Log.i("ListActivity", "size" + checkedList.size());
    }

    @SuppressLint("InflateParams") 
    private void generateDataToContainerLayout() {

 int i = 0;
 if (checkedList.size() == i) { //do nothing
 }
 while (checkedList.size() > i) {
     final Friend friend = checkedList.get(i);
     LayoutInflater inflater = (LayoutInflater) getSystemService(Activity.LAYOUT_INFLATER_SERVICE);
     View view = inflater.inflate(R.layout.item_listview, null,
      false);

     ImageView genderImage = (ImageView) view.findViewById(R.id.gender);
     TextView friendName = (TextView) view.findViewById(R.id.name);
     CheckBox checked = (CheckBox)view.findViewById(R.id.check);
     checked.setVisibility(View.GONE);
     if (friend.isSelected()) {
  Log.i("ListActivity", "here" + friend.getName());
  friendName.setText(friend.getName());
  if (friend.isGender()) {
      genderImage.setImageResource(R.drawable.male);
  } else {
      genderImage.setImageResource(R.drawable.female);
  }

  // add view after all
  container.addView(view);
     }

     i++; // rise i
 }
    }
}
Other work is creating a ListView adapter. We should extend ArrayAdapter, not BaseAdapter, it's more convenient:
ListViewAdapter.java
public class ListViewAdapter extends ArrayAdapter<Friend> {

    private Activity activity;
    private ArrayList<Friend> Friends;
    private final String TAG = ListViewAdapter.class.getSimpleName();

    public ListViewAdapter(Activity activity, int resource, ArrayList<Friend> Friends) {
 super(activity, resource, Friends);
 this.activity = activity;
 this.Friends = Friends;
 Log.i(TAG, "init adapter");
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
 ViewHolder holder = null;

 // inflate layout from xml
 LayoutInflater inflater = (LayoutInflater) activity
  .getSystemService(Activity.LAYOUT_INFLATER_SERVICE);
 
 // If holder not exist then locate all view from UI file.
 if (convertView == null) {
     // inflate UI from XML file
     convertView = inflater.inflate(R.layout.item_listview, parent, false);
     // get all UI view
     holder = new ViewHolder(convertView);
     // set tag for holder
     convertView.setTag(holder);
 } else {
     // if holder created, get tag from view
     holder = (ViewHolder) convertView.getTag();
 }
 
 Friend friend = Friends.get(position);
 
 //set Friend data to views
 holder.name.setText(friend.getName());
 if (friend.isGender()) {
     holder.image.setImageDrawable(this.activity.getResources()
      .getDrawable(R.drawable.male));
 } else {
     holder.image.setImageDrawable(this.activity.getResources()
      .getDrawable(R.drawable.female));
 }
 
 //set event for checkbox
 holder.check.setOnCheckedChangeListener(onCheckedChangeListener(friend));

 return convertView;
    }

    /**
     * handle check box event
     * @param f
     * @return
     */
    private OnCheckedChangeListener onCheckedChangeListener(final Friend f) {
 return new OnCheckedChangeListener() {
     
     @Override
     public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
  if (isChecked) {
      f.setSelected(true);
  } else {
      f.setSelected(false);
  }
     }
 };
    }

    private class ViewHolder {
 private ImageView image;
 private TextView name;
 private CheckBox check;

 public ViewHolder(View v) {
     image = (ImageView) v.findViewById(R.id.gender);
     name = (TextView) v.findViewById(R.id.name);
     check = (CheckBox) v.findViewById(R.id.check);
 }
    }

}
And these are some xml files we need:
- activity_list.xml (Layout for SelectedActivity.java):
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="#666666"
        android:layout_marginTop="10dp"
        android:textColor="@android:color/white"
        android:text="@string/ckecked" />

    <LinearLayout
        android:id="@+id/layout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical" >
    </LinearLayout>

</LinearLayout>
- item_listview.xml (Layout for each ListView row):
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal" >

    <CheckBox
        android:id="@+id/check"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="0.1" />

    <ImageView
        android:id="@+id/gender"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="0.35"
        android:contentDescription="@string/action_settings" />

    <TextView
        android:id="@+id/name"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="0.45"
        android:textColor="@android:color/holo_green_dark" />

</LinearLayout>
- header_listview.xml (to make the ListView header):
<?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="match_parent"
    android:background="#555555" >

    <TextView
        android:id="@+id/text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerVertical="true"
        android:text="@string/friend"
        android:textColor="@android:color/white" />

    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentRight="true"
        android:layout_centerInParent="true"
        android:layout_marginLeft="5dp"
        android:layout_toRightOf="@+id/text"
        android:background="@drawable/blue_button"
        android:contentDescription="@string/action_settings"
        android:text="@string/goto_selected_list"
        android:textColor="@android:color/white"
        android:textSize="14sp" />

</RelativeLayout>

Running program, we have result like these srceens (click each image for full screen):
pic name pic name pic name