1

Background

when you create your own custom views for listView, you can have a checkBox (or any other view that has "state_pressed" for its drawable) inside that will only be shown as touched only when it's really the one that is clicked , instead of clicking anywhere on the row of the listView.

The way to do it for listView is just use the next code on its adapter (as shown here) :

public boolean isEnabled(int position) 
  { 
  return false; 
  } 

The problem

this solution doesn't work for ExpandableListView, since it doesn't have this function.

Not only that, but I think that any clicking on items on the ExpandableListView (even those that do not expand) will trigger a notifyDataSetChanged (or a layout that triggers it).

Why does it occur?

What I've tried

I've tried using "isChildSelectable" ,"areAllItemsEnabled" , "setClickable" and even "duplicateParentState" . None of those solutions has helped.

The question

What is the best way to have a checkbox within rows, and allow only them to handle touches effects on themselves?

Also, why does clicking on items (even those without children) trigger refreshing of the whole ExpandableListView ?

Community
  • 1
  • 1
android developer
  • 114,585
  • 152
  • 739
  • 1,270

1 Answers1

0

If I understood correctly, you want to have a checkbox in each parent row of an ExpandableListView. I've done something similar before (not using checkboxes, but buttons) and I had to implement a custom adapter. It would be something like this:

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import android.content.Context;
import android.content.Intent;
import android.graphics.Typeface;
import android.net.Uri;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.BaseExpandableListAdapter;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;

public class ExpandableListTest extends BaseExpandableListAdapter {
private Context context;
private List<String> _listDataHeader; // header titles
// child data in format of header title, child title
private HashMap<String, List<String>> _listDataChild;

public ExpandableListTest(Context context) {
    this.context = context;
}

@Override
public Object getChild(int groupPosition, int childPosititon) {
    if (_listDataChild != null) {
        return this._listDataChild.get(
                this._listDataHeader.get(groupPosition))
                .get(childPosititon);
    } else {
        return null;
    }
}

@Override
public long getChildId(int groupPosition, int childPosition) {
    return childPosition;
}

@Override
public View getChildView(int groupPosition, final int childPosition,
        boolean isLastChild, View convertView, ViewGroup parent) {

    final int position = groupPosition;
    final String childText = (String) getChild(groupPosition, childPosition);

    if (convertView == null) {
        LayoutInflater infalInflater = (LayoutInflater) this.context
                .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        convertView = infalInflater.inflate(R.layout.child_layout, null);
    }

    TextView txtListChild = (TextView) convertView.findViewById(R.id.text);

    txtListChild.setText(childText);

    convertView.setOnClickListener(new OnClickListener() {
        @Override
        public void onClick(View arg0) {
            // do something
        }
    });

    return convertView;
}

@Override
public int getChildrenCount(int groupPosition) {
    if (_listDataChild == null) {
        return 0;
    } else {
        return this._listDataChild.get(
                this._listDataHeader.get(groupPosition)).size();
    }
}

@Override
public Object getGroup(int groupPosition) {
    if (_listDataHeader == null) {
        return null;
    } else {
        return this._listDataHeader.get(groupPosition);
    }

}

@Override
public int getGroupCount() {
    if (_listDataHeader == null) {
        return 1;
    } else {
        return this._listDataHeader.size();
    }

}

@Override
public long getGroupId(int groupPosition) {
    return groupPosition;
}

@Override
public View getGroupView(int pos, boolean isExpanded, View view,
        ViewGroup parent) {

    if (view == null) {
        LayoutInflater infalInflater = (LayoutInflater) this.context
                .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        view = infalInflater.inflate(R.layout.layout_with_checkbox, null);
    }
    getGroup(pos);

    CheckBox checkBox=(CheckBox)view.findViewById(R.id.checkBox);
    checkBox.setOnCheckedChangeListener(new OnCheckedChangeListener() {
        @Override
        public void onCheckedChanged(CompoundButton arg0, boolean arg1) {
            if(arg1){
                //the checkBox is checked
            }
        }
    });

    return view;
}

@Override
public boolean hasStableIds() {
    return false;
}

@Override
public boolean isChildSelectable(int groupPosition, int childPosition) {
    return false;
}

}

In case you wanted to put the checkbox in the child row, just switch the code and the layouts used in getGroupView() and getChildView().

Parent layout xml:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical" >

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:paddingLeft="10dp"
        android:paddingRight="10dp" >

        <CheckBox
            android:id="@+id/checkBox"
            android:layout_width="30dp"
            android:layout_height="wrap_content" />

        <TextView
            android:id="@+id/text"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerVertical="true"
            android:layout_marginLeft="40dp"
            android:layout_toRightOf="@+id/imageView1"
            android:text="Text goes here"
            android:textSize="18sp" />
    </RelativeLayout>

</LinearLayout>
RominaV
  • 3,335
  • 1
  • 29
  • 59
  • The checkboxes handle only touch events on themselves? – RominaV Dec 11 '13 at 12:56
  • in my case, they are actually checkable imageViews (but it will occur for any checkable view with drawables with pressing state) , and I use setOnClickListener on them. the problem is that when you touch the row itself , it seems as if you touch them too, so they get their effect of their drawable for "state_pressed" . – android developer Dec 11 '13 at 12:58
  • Have you tried this code? I've just tested it again and it works fine. I didn't use a "Checkable ImageView", but I tested it with a checkbox, a togglebutton, and both with styles with drawables for pressed state. They don't respond to touch events on the row. – RominaV Dec 11 '13 at 13:18
  • ok, i've tried your sample (and btw, you've writen "layout_with_checkbox" instead of "parent_layout.xml") . it has an issue: it doesn't allow showing any effect on the selected item. this means that if you click anywhere that is not on the checkbox, you don't get any indication of it being touched. not only that, but even though i've added multiple items to your hashmap, it doesn't seem to allow to expand the rows in order to show them. it's as if touching is totally blocked besides the checkboxes... – android developer Dec 11 '13 at 22:28