Adding checkbox to a Custom ListView..sounds simple right?? Add a CheckBox to the layout,inflate,add to the ListView and everything's fine UNTIL you start scrolling!! Try checking a CheckBox and scroll down.You will find many more checked ones down below. Try scrollig up again and you will find even more!!! You will soon realize that the more you scroll,the CheckBoxes get jumbled up even more!! Wondering why?? The troublemaker here is convertView,which the ListView re-uses as you scroll. When you check a CheckBox,every list item that uses that specific convertView will have a checked CheckBox.
So how do you get around this??
Simple.Have a cache which saves the state of every CheckBox in the ListView.
Since a CheckBox can have only two states,checked or unchecked,a simple boolean array
will serve the purpose.
In effect,whenever a CheckBox in the ListView is checked or unchecked,its resulting state is stored in the boolean array.When the user scrolls,this boolean array is used to set the state of the CheckBox.
This is my ListView:
The layout for the list item is:
So how do you get around this??
Simple.Have a cache which saves the state of every CheckBox in the ListView.
Since a CheckBox can have only two states,checked or unchecked,a simple boolean array
will serve the purpose.
In effect,whenever a CheckBox in the ListView is checked or unchecked,its resulting state is stored in the boolean array.When the user scrolls,this boolean array is used to set the state of the CheckBox.
This is my ListView:
The layout for the list item is:
<?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"> <CheckBox android:id="@+id/checkBox" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentLeft="true" /> <ImageView android:id="@+id/photo" android:layout_width="55dp" android:layout_height="70dp" android:layout_toRightOf="@id/checkBox" android:scaleType="fitXY"/> <TextView android:id="@+id/name" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_toRightOf="@id/photo" android:layout_alignTop="@id/photo" android:textSize="18sp"/> <TextView android:id="@+id/team" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_toRightOf="@id/photo" android:layout_below="@id/name"/> </RelativeLayout>
My main.xml for the ListActivity:
<?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="@android:id/list" android:layout_width="fill_parent" android:layout_height="0dp" android:layout_weight="1"/> <ImageView android:id="@android:id/empty" android:layout_width="fill_parent" android:layout_height="fill_parent" android:src="@drawable/no_player"/> </LinearLayout>
The CustomAdapter is where we declare the boolean array for holding the CheckBox states.
We also add an onClickListener to the CheckBox. When the user clicks a CheckBox,
if it is checked,then the listener sets the corresponding boolean array to true.Else,it is
set to false.
This boolean array sets the state of the CheckBox using the setChecked(boolean) method.
This is the CustomAdapter I used:
//define your custom adapter private class CustomAdapter extends ArrayAdapter<HashMap<String, Object>> { // boolean array for storing //the state of each CheckBox boolean[] checkBoxState; ViewHolder viewHolder; public CustomAdapter(Context context, int textViewResourceId, ArrayList<HashMap<String, Object>> players) { //let android do the initializing :) super(context, textViewResourceId, players); //create the boolean array with //initial state as false checkBoxState=new boolean[players.size()]; } //class for caching the views in a row private class ViewHolder { ImageView photo; TextView name,team; CheckBox checkBox; } @Override public View getView(final int position, View convertView, ViewGroup parent) { if(convertView==null) { convertView=inflater.inflate(R.layout.players_layout, null); viewHolder=new ViewHolder(); //cache the views viewHolder.photo=(ImageView) convertView.findViewById(R.id.photo); viewHolder.name=(TextView) convertView.findViewById(R.id.name); viewHolder.team=(TextView) convertView.findViewById(R.id.team); viewHolder.checkBox=(CheckBox) convertView.findViewById(R.id.checkBox); //link the cached views to the convertview convertView.setTag( viewHolder); } else viewHolder=(ViewHolder) convertView.getTag(); int photoId=(Integer) players.get(position).get("photo"); //set the data to be displayed viewHolder.photo.setImageDrawable(getResources().getDrawable(photoId)); viewHolder.name.setText(players.get(position).get("name").toString()); viewHolder.team.setText(players.get(position).get("team").toString()); //VITAL PART!!! Set the state of the //CheckBox using the boolean array viewHolder.checkBox.setChecked(checkBoxState[position]); //for managing the state of the boolean //array according to the state of the //CheckBox viewHolder.checkBox.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { if(((CheckBox)v).isChecked()) checkBoxState[position]=true; else checkBoxState[position]=false; } }); //return the view to be displayed return convertView; } }
Note lines 65 and 72.
In line 65,the state of the CheckBox is set to its correct state using the boolean array.
Line 72 alters the boolean array.When the user clicks on a CheckBox,the resulting state of that CheckBox is cached in the boolean array.This cached state is the one that is used to set the CheckBox to its correct state in line 65.
And this is my ListActivity class by the way:
public class CustomListViewWithCheckBox extends ListActivity { //ArrayList that will hold the original Data ArrayList<HashMap<String, Object>> players; LayoutInflater inflater; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); //get the LayoutInflater for inflating the customomView //this will be used in the custom adapter inflater=(LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE); //these arrays are just the data that //I'll be using to populate the ArrayList //You can use our own methods to get the data String names[]={"Ronaldo","Messi","Torres","Iniesta", "Drogba","Gerrard","Rooney","Xavi"}; String teams[]={"Real Madrid","Barcelona","Chelsea", "Barcelona","Chelsea","Liverpool", "ManU","Barcelona"}; Integer[] photos={R.drawable.cr7,R.drawable.messi, R.drawable.torres,R.drawable.iniesta, R.drawable.drogba,R.drawable.gerrard, R.drawable.rooney,R.drawable.xavi}; players=new ArrayList<HashMap<String,Object>>(); //temporary HashMap for populating the //Items in the ListView HashMap<String , Object> temp; //total number of rows in the ListView int noOfPlayers=names.length; //now populate the ArrayList players for(int i=0;i<noOfPlayers;i++) { temp=new HashMap<String, Object>(); temp.put("name", names[i]); temp.put("team", teams[i]); temp.put("photo", photos[i]); //add the row to the ArrayList players.add(temp); } /*create the adapter *first param-the context *second param-the id of the layout file you will be using to fill a row *third param-the set of values that will populate the ListView */ final CustomAdapter adapter=new CustomAdapter(this, R.layout.players_layout,players); //finally,set the adapter to the default ListView setListAdapter(adapter); }This doesn't need any explanation I suppose.If you do,you are always welcome to check out
this post of mine..:).
Summing up,this is how it looks like:
public class CustomListViewWithCheckBox extends ListActivity { //ArrayList that will hold the original Data ArrayList<HashMap<String, Object>> players; LayoutInflater inflater; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); //get the LayoutInflater for inflating the customomView //this will be used in the custom adapter inflater=(LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE); //these arrays are just the data that //I'll be using to populate the ArrayList //You can use our own methods to get the data String names[]={"Ronaldo","Messi","Torres","Iniesta", "Drogba","Gerrard","Rooney","Xavi"}; String teams[]={"Real Madrid","Barcelona","Chelsea", "Barcelona","Chelsea","Liverpool", "ManU","Barcelona"}; Integer[] photos={R.drawable.cr7,R.drawable.messi, R.drawable.torres,R.drawable.iniesta, R.drawable.drogba,R.drawable.gerrard, R.drawable.rooney,R.drawable.xavi}; players=new ArrayList<HashMap<String,Object>>(); //temporary HashMap for populating the //Items in the ListView HashMap<String , Object> temp; //total number of rows in the ListView int noOfPlayers=names.length; //now populate the ArrayList players for(int i=0;i<noOfPlayers;i++) { temp=new HashMap<String, Object>(); temp.put("name", names[i]); temp.put("team", teams[i]); temp.put("photo", photos[i]); //add the row to the ArrayList players.add(temp); } /*create the adapter *first param-the context *second param-the id of the layout file you will be using to fill a row *third param-the set of values that will populate the ListView */ final CustomAdapter adapter=new CustomAdapter(this, R.layout.players_layout,players); //finally,set the adapter to the default ListView setListAdapter(adapter); } //define your custom adapter private class CustomAdapter extends ArrayAdapter<HashMap<String, Object>> { // boolean array for storing //the state of each CheckBox boolean[] checkBoxState; ViewHolder viewHolder; public CustomAdapter(Context context, int textViewResourceId, ArrayList<HashMap<String, Object>> players) { //let android do the initializing :) super(context, textViewResourceId, players); //create the boolean array with //initial state as false checkBoxState=new boolean[players.size()]; } //class for caching the views in a row private class ViewHolder { ImageView photo; TextView name,team; CheckBox checkBox; } @Override public View getView(final int position, View convertView, ViewGroup parent) { if(convertView==null) { convertView=inflater.inflate(R.layout.players_layout, null); viewHolder=new ViewHolder(); //cache the views viewHolder.photo=(ImageView) convertView.findViewById(R.id.photo); viewHolder.name=(TextView) convertView.findViewById(R.id.name); viewHolder.team=(TextView) convertView.findViewById(R.id.team); viewHolder.checkBox=(CheckBox) convertView.findViewById(R.id.checkBox); //link the cached views to the convertview convertView.setTag( viewHolder); } else viewHolder=(ViewHolder) convertView.getTag(); int photoId=(Integer) players.get(position).get("photo"); //set the data to be displayed viewHolder.photo.setImageDrawable(getResources().getDrawable(photoId)); viewHolder.name.setText(players.get(position).get("name").toString()); viewHolder.team.setText(players.get(position).get("team").toString()); //VITAL PART!!! Set the state of the //CheckBox using the boolean array viewHolder.checkBox.setChecked(checkBoxState[position]); //for managing the state of the boolean //array according to the state of the //CheckBox viewHolder.checkBox.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { if(((CheckBox)v).isChecked()) checkBoxState[position]=true; else checkBoxState[position]=false; } }); //return the view to be displayed return convertView; } } }That's it.Happy coding.:)
Note: The setOnItemClickListener() may not fire when a 'clickable' widget like a Button or CheckBox is part of a list item.
To resolve this,simply set
android:focusable="false"
android:focusableInTouchMode="false"
to the clickable widget.
how implemenet event for a row
ReplyDeletesetOnItemClickListener(new OnItemClickListener() {
public void onItemClick ...
in this adapter
Hey,:)
DeleteI suppose you are asking this because the setOnItemClickListener doesn't fire as it should..right??
The solution is simple..just set these properties to your checkbox in the layout file:
android:focusable="false"
android:focusableInTouchMode="false"
Perfect, thanks!!
DeleteIts not working ,,,where you assign adapter to Listview ....??
ReplyDeleteu should check the code again.. setListAdapter()??
DeleteGreat stuff! Helped me out a lot. I don't understand how one figures all that out by himself.:D
ReplyDeleteI don't understand that a workaround like this one is needed to achieve something simple as a checkbox before every item in a list. I have developed an application for Windows Phone 8.0 and you don't have to write any programming-code to achieve it. It can all be done in the layout x(a)ml-file.
Anyhow: Thank you very much! Your post has been a great help.
Hey thanks dude..It's working. Simple and neat blog..
ReplyDeleteThanks,this worked me correctly as i expected...
ReplyDeletethanks for your share... it is worked for me :)
ReplyDeleteGreat work man... Perfectly the thing i needed as of now.
ReplyDeleteThnk u so much.............its very very use full......
ReplyDeleteThank you so much! I finally got my code work using your example.
ReplyDeletecan you please send me the full source code? i want to know how to implement them.. :( I'm a newbie here.. please?
ReplyDeleteHi all,
ReplyDeletethere's been several requests for the complete source of this sample.
To be frank..I no longer have it :) and I'm too lazy to rebuild this.. :P although this is just elementary level.
Besides, the idea was to give a simple solution to the nagging problem of checkboxes within ListViews. I'm pretty sure it has been answered here.
Is this code under a licence?
ReplyDeleteHello nice blog..
ReplyDeletethank you for shearing.custom check designs
Boss great work you really helped me alot
ReplyDeleteThanks man. It was quite helpful. Couldn't find solution anywhere else
ReplyDeleteI have read your blog and I gathered some needful information from your blog. Keep update your blog. Awaiting for your next update.
ReplyDeleteI have read your blog and I gathered some needful information from your blog. Keep update your blog. Awaiting for your next update.