Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Highlight for selected item in expandable list

I have a layout where I have an expandable list in a fragment on the left and a details fragment on the right. This all works fine.

Now I would like to indicate what item on the left is having it's details shown on the right and here I'm having a problem.

In a normal list view I have achieved this by setting the choice mode for the list view to single and then using a state drawable based on the "activated" state. When I click the item, the background is set to my selected color and stays that way until I select another item on the list.

I tried to apply this to my expandable listview and it was/is a dismal failure. There were no errors, but the selected item did not maintain it's color state. I'm not sure if I'm not setting the choice mode properly for it (I've tried it in the layout file as well as programatically, it doesn't seem to make a difference), or pointing it to the wrong thing (not sure how that could be, but...)

Any help/pointers appreciated (even if it's in a completely different direction).

Most current failure:

expandable listview code

private void fillData(String group, String child) {
    ExpandableListView lv;
    mGroupsCursor = mDbHelper.fetchGroup(group);
    getActivity().startManagingCursor(mGroupsCursor);
    mGroupsCursor.moveToFirst();
    lv = (ExpandableListView) getActivity().findViewById(R.id.explist);
    lv.setChoiceMode(ExpandableListView.CHOICE_MODE_SINGLE);        

    mAdapter = new MyExpandableListAdapter(mGroupsCursor, getActivity(),
            R.layout.explistlayout,
            R.layout.explistlayout,
            new String[] { "_id" },
            new int[] { android.R.id.text1 },
            new String[] { child },
            new int[] { android.R.id.text1 });
    lv.setAdapter(mAdapter);
    registerForContextMenu(lv);

    lv.setOnChildClickListener(new ExpandableListView.OnChildClickListener() { 
        @Override 
        public boolean onChildClick(ExpandableListView parent, View v, int groupPosition, int childPosition, long id)  
        { 
            mRowId  = id;
            EventDisplayFragment eventdisplay = new EventDisplayFragment();
            getFragmentManager().beginTransaction().replace(R.id.rightpane, eventdisplay).commit();
            return true; 
        } 
        }); 
}

public class MyExpandableListAdapter extends SimpleCursorTreeAdapter {

    public MyExpandableListAdapter(Cursor cursor, Context context,
            int groupLayout, int childLayout, String[] groupFrom,
            int[] groupTo, String[] childrenFrom, int[] childrenTo) {
        super(context, cursor, groupLayout, groupFrom, groupTo,
                childLayout, childrenFrom, childrenTo);
    }
    @Override
    protected Cursor getChildrenCursor(Cursor groupCursor) {
        Cursor childCursor = mDbHelper.fetchChildren(mGroup, groupCursor
                .getString(groupCursor
                        .getColumnIndex(AttendanceDB.EVENT_ROWID)));
        getActivity().startManagingCursor(childCursor);
        childCursor.moveToFirst();
        return childCursor;
    }
}

item_selector.xml

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item 
     android:state_pressed="true" 
     android:drawable="@color/green" />
    <item 
     android:state_selected="true" 
     android:drawable="@color/blue" />
    <item 
     android:state_focused="true" 
     android:drawable="@color/violet" />
    <item 
     android:state_activated="true" 
     android:drawable="@color/blue" />
</selector>
like image 911
Barak Avatar asked Apr 25 '12 15:04

Barak


8 Answers

Do the following on ExpandableListView:

Step 1. Set choice mode to single (Can be done in xml as android:choiceMode = "singleChoice")

Step 2. Use a selector xml as background (android:listSelector = "@drawable/selector_list_item")

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android"
   android:exitFadeDuration="@android:integer/config_mediumAnimTime">

   <item android:drawable="@android:color/holo_blue_bright" android:state_pressed="true"/>
   <item android:drawable="@android:color/holo_blue_light" android:state_selected="true"/>
   <item android:drawable="@android:color/holo_blue_dark" android:state_activated="true"/>

</selector>

Step 3. Call expandableListView.setItemChecked(index,true) in onChildClick() callback.

index is a 0 based index of the child item calculated as follows

Group 1 [index = 0]

  • Child item 1
  • Child item 2
  • Child item 3 [index = 3]

Group 2 [index = 4]

  • Child item 1
  • Child item 2
  • Child item 3 [index = 7]

Group 3 [index = 8]

  • Child item 1
  • Child item 2
  • Child item 3 [index = 11]

If we have list headers as well, they would also account for index values.

Here is a working example:

@Override
public boolean onChildClick(ExpandableListView parent, View v,
        int groupPosition, int childPosition, long id) {
    ...

    int index = parent.getFlatListPosition(ExpandableListView.getPackedPositionForChild(groupPosition, childPosition));
    parent.setItemChecked(index, true);


    return true;
}
like image 55
silvermouse Avatar answered Oct 20 '22 11:10

silvermouse


After spending 3-4 hours it is made it its so simple no need to create extra XML files just check if the group item is expanded if yes then select color and in else statement make them as they are ....

@Override
//in this method you must set the text to see the parent/group on the list
public View getGroupView(final int i, boolean b, View view, ViewGroup viewGroup) {

    if (view == null) {
        view = inflater.inflate(R.layout.list_item_parent, viewGroup,false);
    }

    if(b){
        view.setBackgroundResource(R.color.waterblue);
    }else{
        view.setBackgroundColor(color.transparent);
    }


    //return the entire view
    return view;
}
like image 40
Prakash Gavade Avatar answered Oct 20 '22 11:10

Prakash Gavade


I finally figured out how to make this work (for me). It seems a bit of the hack, but it's the only thing I've managed to get working for this.

Basically I create a two dimensional array to hold the selected state of the children, initialize it in the adapter constructor, then I reference that in my getChildView method (in case the currently selected child scrolls out of view) and my onChildClick method (to change the current selection and turn off the old one).

private selectedStatus[][];  // array to hold selected state
private View oldView;        // view to hold so we can set background back to normal after selection of another view

Constructor initialize the array

    public MyExpandableListAdapter(Cursor cursor, Context context,
            int groupLayout, int childLayout, String[] groupFrom,
            int[] groupTo, String[] childrenFrom, int[] childrenTo) {
        super(context, cursor, groupLayout, groupFrom, groupTo,
                childLayout, childrenFrom, childrenTo);
        selectedStatus = new boolean[cursor.getCount()][];
        for (int i = 0; i < cursor.getCount(); i++) {
            cursor.moveToPosition(i);
            Cursor childCheckCursor = mDbHelper.fetchChildren(mGroup,
                    cursor.getString(cursor
                            .getColumnIndex(AttendanceDB.EVENT_ROWID)));
            getActivity().startManagingCursor(childCheckCursor);
            selectedStatus[i] = new boolean[childCheckCursor.getCount()];
            for (int j = 0; j < childCheckCursor.getCount(); j++) {
                selectedStatus[i][j] = false;
            }
        }

    }

getChildView needed for when the child scrolls out of view

    @Override 
    public View getChildView(int groupPosition, int childPosition, boolean isLastChild, View convertView, ViewGroup parent) { 
        final View v = super.getChildView(groupPosition, childPosition, isLastChild, convertView, parent); 
        if(selectedStatus[groupPosition][childPosition] == true){
            v.setBackgroundResource(R.color.blue);                          
        } else {
            v.setBackgroundResource(R.color.black);
        }
        return v; 
    } 

onChildClick change selected item

    lv.setOnChildClickListener(new ExpandableListView.OnChildClickListener() {
        @Override
        public boolean onChildClick(ExpandableListView parent, View v,
                int groupPosition, int childPosition, long id) {
            mRowId = id;
            EventDisplayFragment eventdisplay = new EventDisplayFragment();
            getFragmentManager().beginTransaction()
                    .replace(R.id.rightpane, eventdisplay).commit();
            if(oldView != null){
                oldView.setBackgroundResource(R.color.black);   // change the background of the old selected item back to default black                 }
            oldView = v;                                        // set oldView to current view so we have a reference to change back on  next selection
            for (int i = 0; i < selectedStatus.length; i++) {
                for (int j = 0; j < checkedStatus[i].length; j++) {
                    if(i == groupPosition && j == childPosition){  // set the current view to true and all others to false
                        selectedStatus[i][j] = true;
                    } else {
                        selectedStatus[i][j] = false;
                    }
                }
            }
            v.setBackgroundResource(R.color.blue);
            return true;
        }
    });
like image 28
Barak Avatar answered Oct 20 '22 11:10

Barak


I was having the same problem I fixed this by adding

v.setSelected(true);

here's a sample of my code :

expandableListDetailsLevel.setChoiceMode(ExpandableListView.CHOICE_MODE_SINGLE);
        expandableListDetailsLevel
                .setOnGroupExpandListener(new OnGroupExpandListener() {

                    public void onGroupExpand(int groupPosition) {

                        if(groupPosition!=1){
                            expandableIsThere = false;
                        }

                        for (int i = 0; i < len; i++) {
                            if (i != groupPosition) {
                                expandableListDetailsLevel.collapseGroup(i);
                            }
                        }
                    }
                });

        expandableListDetailsLevel
                .setOnChildClickListener(new OnChildClickListener() {

                    @Override
                    public boolean onChildClick(ExpandableListView parent,
                            View v, int groupPosition, int childPosition,
                            long id) {


                        v.setSelected(true);

                        }

my item xml

<?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="wrap_content"
android:orientation="horizontal"
android:background="@drawable/selector" >


<TextView
    android:id="@+id/TVChild"
    android:layout_width="fill_parent"
    android:layout_height="match_parent"
    android:textColor="#000"
    android:textSize="12dp"/>

</LinearLayout>

attribute android:drawSelectorOnTop="true" in the ExpandableListView element in the xml file (not the item child or group ) like this :

<ExpandableListView
                android:id="@+id/expandableViewDetailsBriefing"
                android:layout_width="fill_parent"
                android:layout_height="match_parent"
                android:background="#fff"
                android:drawSelectorOnTop="true"
                android:dividerHeight="1px"
                android:childDivider="@color/Gris"
                android:indicatorRight="690dp"
                android:textFilterEnabled="true" >
            </ExpandableListView>
like image 35
Driss Bounouar Avatar answered Oct 20 '22 10:10

Driss Bounouar


The next solution help you make different list selector for group and child.

First of all, you need disable list selector for ExpendableListView:


...
   <ExpandableListView
            ...
            android:listSelector="@android:color/transparent"
            ...
            />
...

After, you need copy an empty listSelector. You can do you like this


...
private Drawable empty;
...

// binding view here. Name of ExpandableListView will be elv
empty = elv.getSelector();


Now, we need know when we will enable list selector or disable. You should add follow changes to your adapter for that:


my adapter here {
...
    @Override
    public View getGroupView(int groupPosition, boolean isExpanded, View view, ViewGroup parent)
    {
       ...
       // after, we've got view of goup
       view.setOnTouchListener(new View.OnTouchListener()
       {
           @Override
           public boolean onTouch(View v, MotionEvent event)
           {
               elv.setSelector(empty);
               return false;
           }
       });
       ...
    }
...
    @Override
    public View getChildView(final int groupPosition, final int childPosition, boolean isLastChild, View convertView, final ViewGroup parent)
    {
       ...
       // after, we've got view of child
       view.setOnTouchListener(new View.OnTouchListener()
       {
           @Override
           public boolean onTouch(View v, MotionEvent event)
           {
               elv.setSelector(R.drawable.my_custom_list_selector);
               return false;
           }
       });
       ...
    }
...
}

That's all. I hope this solution keeps your time.

like image 43
Rusfearuth Avatar answered Oct 20 '22 09:10

Rusfearuth


You can save the current view and change the background color of view within your click listeners:

View currentView;
ExpandableListView elv;

elv.setOnGroupClickListener(new ExpandableListView.OnGroupClickListener() {
    @Override
    public boolean onGroupClick(ExpandableListView parent, View v, int groupPosition, long id) {
        if(currentView != null) {
            currentView.setBackgroundColor(Color.parseColor("#FFFFFF"));
        }
        v.setBackgroundColor(Color.parseColor("#EEEEEE"));
        currentDrawerView = view;

        //do something

        return false;
    }
});
elv.setOnChildClickListener(new ExpandableListView.OnChildClickListener() {
    @Override
    public boolean onChildClick(ExpandableListView parent, View v, int groupPosition, int childPosition, long id) {
        if(currentView != null) {
            currentView.setBackgroundColor(Color.parseColor("#FFFFFF"));
        }
        v.setBackgroundColor(Color.parseColor("#EEEEEE"));
        currentDrawerView = view;

        //do something

        return false;
    }
});
like image 30
Jorge Leandro Garcia Avatar answered Oct 20 '22 09:10

Jorge Leandro Garcia


In case you need only the child item color to be changed than in the onchildclick method set the background color to the View v.

@Override
            public boolean onChildClick(ExpandableListView parent, View v,
                    int groupPosition, int childPosition, long id) {
                // TODO Auto-generated method stub
                v.setBackgroundColor(Color.RED);
}

to do so set isChildSelectable to true in the adapter.

like image 43
user2779311 Avatar answered Oct 20 '22 09:10

user2779311


This simple solution has helped me when I was faced with the same question as the author

I have a layout where I have an expandable list in a fragment on the left and a details fragment on the right. This all works fine. Now I would like to indicate what item on the left is having it's details shown on the right and here I'm having a problem.

To indicate what item in expandable list is selected go to ExpandableListView properties (in layout) and chose color for listSelector.

like image 37
Ilya Shirokov Avatar answered Oct 20 '22 10:10

Ilya Shirokov