Everything you need to know about Android ListViews and CheckBoxes!
I have recently been developing a simple Shopping list application in Android and for the list itself, I wanted to have a list of all the items i wanted to buy with a checkbox on the far right so that I could mark any item as complete - on ticking the checkbox I wanted two things to happed:1. The item displayed becomes struck through
2. The tick is persisted (in case I navigate anway from the app and want to come back later, i dont want to re-tick things)
I searched everywhere to find a solution, and posted on StackOverflow but managed to find no solution as to how I could implement a Checkbox listener that could work out which row in the list had been ticked. Fortunately, with some help from here I have managed to solve the problem - So here is a detailed walkthrough how to do it! This covers how to do it using a custom Adapter, in this case I extended the ArrayAdapter
The first problem people encounter, that is already pretty well documented on the web is that if you put a checkbox on a listview row, then you can no longer click on the row (for the context menu for example) as all focus is on the check box - this is easily fixed! in the getView() method, simply initialise the CheckBox and call checkBox.setFocusable(false);
Now to the problem of discovering the row that has been selected:
1. First, in the getView() method whilst we are populating the row data (for example here we set the text in the textview) we will add a "Tag" to our checkbox - this tag will indicate the row position that the CheckBox belongs to:
checkBox.setTag(new Integer(position)); //we tag every checkbox we create with the row number for later
2. Next we set the OnCheckChangedListener - this could be a seperate class, but for ease of this overview I just changed my Adapter to implement the listener and added the required onCheckChange method
checkBox.setOnCheckedChangeListener(this); //define the listener (we will use this class but could be another)
3. Now we need to implement the listener method to use this information:
public void onCheckedChanged(CompoundButton cb, boolean isChecked) { //first we need to work out the row selected - we can do this as we have already tagged the CheckBox Integer posSelected = (Integer)cb.getTag(); //We can now get the associated JSONObject (we might want to persist some data here..) JSONObject checked = getItem(posSelected); //persistChangeToDb(checked); //now we will retrieve the current row selected so we can change the appearance View row = (View) cb.getParent(); //once we have the row, we are going to change the TextView field as strikethrough (or not) if it is checked TextView descrip = (TextView)row.findViewById(R.id.item_description); if (isChecked){ descrip.setPaintFlags(descrip.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG); } else{ descrip.setPaintFlags(Constants.CLEAR_FORMATTING_TEXTVIEW); } }
As you can see, as we have the CheckBox in this listener, we can get its tag to discover the row position clicked - this allows us to access the JSONObject associated with the row and use that data as we please (here I persist the change to the checkbox so it is remembered).
We can also get the entire row, this allows us to change the text/formatting/background etc - you will see that I get the TextView (this is the textView that has my item description) and then strike it through (or remove formatting) if it is checked.
Below is the entire adapter class:
public class ShoppingListItemsAdapter extends ArrayAdapterimplements OnCheckedChangeListener { public ShoppingListItemsAdapter(Activity activity, List shoppingLists) { super(activity, 0, shoppingLists); } @Override public View getView(int position, View convertView, ViewGroup parent) { Activity activity = (Activity) getContext(); LayoutInflater inflater = activity.getLayoutInflater(); // Inflate the views from XML View rowView = inflater.inflate(R.layout.shopping_list_item_view, null); JSONObject jsonCurrentRow = getItem(position); ////////////////////////////////////////////////////////////////////////////////////////////////////// //The next section we update at runtime the text - as provided by the JSON being passed in //////////////////////////////////////////////////////////////////////////////////////////////////// // Set the text on the TextView TextView textView = (TextView) rowView.findViewById(R.id.item_description); //initialise the checkbox CheckBox checkBox = (CheckBox)rowView.findViewById(R.id.boughtCheck); checkBox.setFocusable(false); //this is so the onLongClick on the row still works! checkBox.setTag(new Integer(position)); //we tag every checkbox we create with the row number for later checkBox.setOnCheckedChangeListener(this); //define the listener (we will use this class but could be another) try { //populating the row with data from the JSONObject String title = (String)jsonCurrentRow.get("title"); textView.setText(title); } catch (JSONException e) { textView.setText("JSON Exception"); } return rowView; } /** * This method is our checkbox change listener - this is where * we handle the changes in the checkbox state */ @Override public void onCheckedChanged(CompoundButton cb, boolean isChecked) { //first we need to work out the row selected - we can do this as we have already tagged the CheckBox Integer posSelected = (Integer)cb.getTag(); //We can now get the associated JSONObject (we might want to persist some data here..) JSONObject checked = getItem(posSelected); //persistChangeToDb(checked); //now we will retrieve the current row selected so we can change the appearance View row = (View) cb.getParent(); //once we have the row, we are going to change the TextView field as strikethrough (or not) if it is checked TextView descrip = (TextView)row.findViewById(R.id.item_description); if (isChecked){ descrip.setPaintFlags(descrip.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG); } else{ descrip.setPaintFlags(Constants.CLEAR_FORMATTING_TEXTVIEW); //this is a bit of a hack! to remove strike through the normal setting is 257 so I created a constant for this, you could just pass the int literal 257 in here to clear formatting } } }
2 comments: