View

Friday 6 May 2011

How to use ExpandableListView using Check box




how to add checkboxes to an expandable list view like this one? Nothing can be simpler, you just add CheckBox control to the child view that forms the row and that's it. Except that it does not work without some tweaks.

package com.pack;

import android.app.ExpandableListActivity;
import android.os.Bundle;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ListView;
import android.widget.CheckBox;
import android.widget.ExpandableListView;
import android.widget.SimpleExpandableListAdapter;
import android.widget.TextView;
import java.util.List;
import java.util.ArrayList;
import android.util.Log;

public class ELSample extends ExpandableListActivity
{
    private static final String LOG_TAG = "ElistCBox2";
    private ColorAdapter expListAdapter;

    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle icicle)
    {
        super.onCreate(icicle);
        setContentView(R.layout.main);
        ArrayList<String> groupNames = new ArrayList<String>();
        groupNames.add( "Food" );
   groupNames.add( "Fruit" );
   groupNames.add( "Juice" );
   groupNames.add( "Ice Cream" );
        ArrayList<ArrayList<Color>> colors = new ArrayList<ArrayList<Color>>(); 
        ArrayList<Color> color = new ArrayList<Color>();
        color.add( new Color( "Meals","Indian Meals", false ) ); 
        color.add( new Color( "Andra Meals","Staple food", false ) );
color.add( new Color( "Sea-Food","Butter fish", true ) ); 
color.add( new Color( "Crispy-Food","Burger", false ) );
        colors.add( color );
        
        color = new ArrayList<Color>();
color.add( new Color( "Apple","American apple",false ) );
color.add( new Color(  "Orange","Navel orange",false ) ); 
color.add( new Color( "Grape","Black Grape", true ) );
        colors.add( color );
        color = new ArrayList<Color>();
color.add( new Color( "Milk Shake","Butter Fruit Mix",true ) );
color.add( new Color( "Carrot","Juice with cherry",false ) ); 
color.add( new Color( "Mango","Juice with curd", true ) );
        colors.add( color );
        color = new ArrayList<Color>();
color.add( new Color( "Strawberry","Cone",true ) );
color.add( new Color( "Brain Freeze Ice Cream","Bar",false ) ); 
color.add( new Color( "Cookie Puss Ice Cream","Chocolate Flavour", false ) );
        colors.add( color );

expListAdapter = new ColorAdapter( this,groupNames, colors );
setListAdapter( expListAdapter );
    }

    public void onContentChanged  () {
        super.onContentChanged();
        Log.d( LOG_TAG, "onContentChanged" );
    }

    public boolean onChildClick(
            ExpandableListView parent, 
            View v, 
            int groupPosition,
            int childPosition,
            long id) {
        Log.d( LOG_TAG, "onChildClick: "+childPosition );
        CheckBox cb = (CheckBox)v.findViewById( R.id.check1 );
        if( cb != null )
            cb.toggle();
        return false;
    }
}

_________________________________________________________________________________

The first bizarre thing you can notice in res/layout/child_row.xml that the CheckBox is made non-focusable. Why to do that when we want the checkbox to capture "click" events beside "touch" events? There is a complicated answer already presented in this post. The ViewGroup encapsulating the list row (the LinearLayout in res/layout/child_row.xml) must retain the focus otherwise we don't get onChildClick events (see ElistCBox.java).



public class ColorAdapter extends BaseExpandableListAdapter {

    private Context context;
    private ArrayList<String> groups;
    private ArrayList<ArrayList<Color>> colors;
    private LayoutInflater inflater;

    public ColorAdapter(Context context, 
                        ArrayList<String> groups,
ArrayList<ArrayList<Color>> colors ) { 
        this.context = context;
this.groups = groups;
        this.colors = colors;
        inflater = LayoutInflater.from( context );
    }

    public Object getChild(int groupPosition, int childPosition) {
        return colors.get( groupPosition ).get( childPosition );
    }

    public long getChildId(int groupPosition, int childPosition) {
        return (long)( groupPosition*1024+childPosition );  // Max 1024 children per group
    }

    public View getChildView(int groupPosition, int childPosition, boolean isLastChild, View convertView, ViewGroup parent) {
        View v = null;
        if( convertView != null )
            v = convertView;
        else
            v = inflater.inflate(R.layout.child_row, parent, false); 
        Color c = (Color)getChild( groupPosition, childPosition );
TextView color = (TextView)v.findViewById( R.id.childname );
if( color != null )
color.setText( c.getColor() );
TextView rgb = (TextView)v.findViewById( R.id.rgb );
if( rgb != null )
rgb.setText( c.getRgb() );
CheckBox cb = (CheckBox)v.findViewById( R.id.check1 );
        cb.setChecked( c.getState() );
        return v;
    }

    public int getChildrenCount(int groupPosition) {
        return colors.get( groupPosition ).size();
    }

    public Object getGroup(int groupPosition) {
        return groups.get( groupPosition );        
    }

    public int getGroupCount() {
        return groups.size();
    }

    public long getGroupId(int groupPosition) {
        return (long)( groupPosition*1024 );  // To be consistent with getChildId
    } 

    public View getGroupView(int groupPosition, boolean isExpanded, View convertView, ViewGroup parent) {
        View v = null;
        if( convertView != null )
            v = convertView;
        else
            v = inflater.inflate(R.layout.group_row, parent, false); 
        String gt = (String)getGroup( groupPosition );
TextView colorGroup = (TextView)v.findViewById( R.id.childname );
if( gt != null )
colorGroup.setText( gt );
        return v;
    }

    public boolean hasStableIds() {
        return true;
    }

    public boolean isChildSelectable(int groupPosition, int childPosition) {
        return true;
    } 

    public void onGroupCollapsed (int groupPosition) {} 
    public void onGroupExpanded(int groupPosition) {}


}


Then Create another class for set a color,name as Color.java

public class Color {
    public String color = null;
    public String rgb = null;
    public boolean state = false;

    public Color( String color, String rgb, boolean state ) {
        this.color = color;
        this.rgb = rgb;
        this.state = state;
    }

    public String getColor() {
   return color;
    }

    public String getRgb() {
   return rgb;
    }

    public boolean getState() {
   return state;
    }

}
_________________________________________________________________________________

This would solve the problem for a Button but not for a CheckBox. Actually, I don't know why the Button behaves correctly and the CheckBox does not. My experience is that even if CheckBox has the focus, clicking on the row of the CheckBox does not toggle its state. I am curious if anyone can provide an explanation, here I just record the fact. Remove the android:focusable="false" line from the CheckBox element in child_row.xml and observe, that you can click on the highlighted row as much as you like but the CheckBox does not toggle. That's why I implemented it by "hand" - I took away the focus from the CheckBox, this makes the child row deliver onChildClick events then I toggled the state of the CheckBox programmatically. If anyone has a better solution, I would be deeply interested.

[Name as Group_row.xml]

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content">

    <TextView android:id="@+id/childname"
         android:paddingLeft="50px"
         android:textSize="14px"
          android:textColor="#990000"
         android:textStyle="italic"
         android:layout_width="150px"
         android:layout_height="wrap_content"/>

</LinearLayout>

[close group_row.xml]

Then again create a new child xml file,name as Child_row.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal"
    android:layout_width="fill_parent"
    android:background="#3300FF"
    android:layout_height="wrap_content">

    <TextView android:id="@+id/childname"
         android:paddingLeft="50px"
         android:focusable="false"
         android:textSize="14px"
         android:textStyle="italic"
         android:layout_width="150px"
         android:layout_height="wrap_content"/>

    <TextView android:id="@+id/rgb"
         android:focusable="false"
         android:textSize="14px"
         android:textStyle="italic"
         android:layout_width="100px"
         android:layout_height="wrap_content"/>

    <CheckBox android:id="@+id/check1"
           android:focusable="false"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"/>

</LinearLayout>
   





8 comments:

  1. Hello SaGa.

    Indeed a very interesting post.

    Did you ever get the row click to toggle the checkbox??? I am facing the same issue.
    If I ever get it resolved and you haven't I will share.

    Please let me know if you did...

    Constructive suggestion - comments are hard to read cause of the colors...

    Cheers.

    Jadeye.
    blog.ndroidians.com

    ReplyDelete
  2. i want to download this example

    ReplyDelete
    Replies
    1. This comment has been removed by the author.

      Delete
    2. This comment has been removed by the author.

      Delete
  3. I am having a problem, onChildClick is not working with checkboxes, if I remove checkboxes it works fine... Please help me out..
    Thank you in advance

    ReplyDelete
  4. How is it possible to load checked boxes with sharedpreferences in the oncreate method?

    Like i managed to save every checkbox state in sharedpreferences, but on loading the activity, i want to enable the checkboxes according to the sharedpreferences state.

    ReplyDelete
  5. Thanks
    Great Blog..
    It's very Useful to know about Android Certification

    ReplyDelete
  6. This is truly a great read for me. I have bookmarked it and I am looking forward to reading new articles. Keep up the good work.
    kajal agarwal hot

    ReplyDelete