Question

I have a list (ExpandableListView), and every item of the list has a ToggleButton that shows/hides the child items when toggled (the child is always 1 and it's a sort of toolbar with two buttons). Thanks to this tutorial I could set a custom button instead of the expandablelistview's indicator and I made it so that I can do something else than showing the toolbar when clicking on a list item. Also, I used the answer to this question to automatically close an open toolbar when opening another one. So, I need to collapse the currently expanded toolbar when touching anything on the screen that is not its ToggleButton (I would need to collapse it even when clicking on one of the toolbar's buttons, as long as its own "onClick" is sent anyway).

here's an image of the app:

enter image description here

Here's the ExpandableListView:

<ExpandableListView
    android:id="@+id/normalList"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:groupIndicator="@drawable/toggle_button_selector" >
</ExpandableListView>

Here's the xml of the group item:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/filesListDrawerLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="2dp" >

<ImageView
    android:id="@+id/img"
    android:layout_width="48dp"
    android:layout_height="48dp"
    android:layout_alignParentLeft="true"
    android:layout_alignParentTop="true"
    android:layout_margin="2dp"
    android:contentDescription="@string/icondescription" />

<TextView
    android:id="@+id/text"
    android:layout_width="wrap_content"
    android:layout_height="29dp"
    android:layout_alignTop="@+id/img"
    android:layout_marginLeft="15dp"
    android:layout_toRightOf="@+id/img"
    android:gravity="center_vertical"
    android:textAppearance="?android:attr/textAppearanceMedium" />

<TextView
    android:id="@+id/counter"
    android:layout_width="wrap_content"
    android:layout_height="19dp"
    android:layout_alignBottom="@+id/img"
    android:layout_below="@+id/text"
    android:layout_marginLeft="15dp"
    android:layout_toRightOf="@+id/img"
    android:gravity="center_vertical"
    android:textAppearance="?android:attr/textAppearanceSmall" />

<ToggleButton
    android:id="@+id/toggle"
    android:layout_width="25dp"
    android:layout_height="25dp"
    android:layout_alignParentRight="true"
    android:layout_below="@+id/text"
    android:layout_marginRight="15dp"
    android:background="@drawable/toggle_button_selector"
    android:focusable="false"
    android:focusableInTouchMode="false"
    android:textColorLink="@android:color/transparent"
    android:textOff=""
    android:textOn="" />

The child item:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content" >

    <TextView
        android:id="@+id/details"
        android:layout_width="0dp"
        android:layout_height="57dip"
        android:layout_weight="1"
        android:clickable="true"
        android:drawableTop="@drawable/ic_action_about"
        android:drawablePadding="-12dp"
        android:background="@drawable/toolbar_selector"
        android:gravity="center"
        android:text="@string/details" />

    <TextView
        android:id="@+id/send"
        android:layout_width="0dp"
        android:layout_height="57dip"
        android:layout_weight="1"
        android:clickable="true"
        android:drawableTop="@drawable/ic_action_new_email"
        android:drawablePadding="-12dp"
        android:background="@drawable/toolbar_selector"
        android:gravity="center"
        android:text="@string/send" />

</LinearLayout>

The relevant parts of the adapter:

public class CustomList extends BaseExpandableListAdapter {

    private final Activity context;
    public final List<Item> names; //Item is a custom Object
    private final Integer[] imageId;
    private int lastExpandedGroupPosition;

    public CustomList(Activity context, List<Item> names, Integer[] imageId) {

        this.context = context;
        this.names = names;
        this.imageId = imageId;
    }

    public View getGroupView(final int position, boolean isExpanded, View convertView, ViewGroup parent) {

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

        TextView txtTitle = (TextView) convertView.findViewById(R.id.text);
        TextView txtData = (TextView) convertView.findViewById(R.id.counter);
        ImageView imageView = (ImageView) convertView.findViewById(R.id.img);
        ToggleButton toggle = (ToggleButton) convertView.findViewById(R.id.toggle);
        final ExpandableListView list = (ExpandableListView) parent.findViewById(R.id.normalList);

        txtTitle.setText(names.get(position).getName());

        txtData.setText(names.get(position).getData());

        if(/*some conditions*/) {

            imageView.setImageResource(imageId[0]);
            toggle.setOnCheckedChangeListener(new OnCheckedChangeListener() {

                public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {

                    if(isChecked) {

                        list.expandGroup(position, true);

                    } else {

                        list.collapseGroup(position);
                    }
                }
            });

        } else if(/*other conditions*/) {

            imageView.setImageResource(imageId[2]);
            toggle.setVisibility(View.GONE);

        } else {

            imageView.setImageResource(imageId[1]);
            toggle.setVisibility(View.GONE);
        }

        return convertView;
    }

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

        if (convertView == null) {

            LayoutInflater inflater = (LayoutInflater) this.context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            convertView = inflater.inflate(R.layout.list_child, null);
        }


        TextView details = (TextView) convertView.findViewById(R.id.details);
        TextView send = (TextView) convertView.findViewById(R.id.send);

        details.setOnClickListener(new OnClickListener() {

            public void onClick(View v) {

                //do something
            }
        });

        send.setOnClickListener(new OnClickListener() {

            public void onClick(View v) {

                //do something else
            }
        });


        return convertView;
    }

    @Override
    public void onGroupExpanded(int groupPosition) {

        ToggleButton button = (ToggleButton) ((ExpandableListView) context
            .findViewById(R.id.normalList)).getChildAt(lastExpandedGroupPosition)
            .findViewById(R.id.toggle);

        if(groupPosition != lastExpandedGroupPosition && button.isChecked()) {
            button.performClick();
        }

        super.onGroupExpanded(groupPosition);
        lastExpandedGroupPosition = groupPosition;
    }
}

And finally the important parts in the activity:

lv = (ExpandableListView)findViewById(R.id.normalList);
lv.setGroupIndicator(null);

lv.setOnGroupClickListener(new OnGroupClickListener() {

        public boolean onGroupClick(ExpandableListView parent, View v, int position, long id) {

            //do some things

            return true; //this tells the list that it mustn't show the child items
        }
});

adapter = new CustomList(myActivity.this, list, icons);

lv.setAdapter(adapter);

P.S.: The code to hide the open toolbars doesn't work properly, but I really can't understand why. But this is another story...

--- EDIT ---

About the P.S. above: now I know why that happens.

Was it helpful?

Solution 3

Ok I solved the problem.
Instead of toggling the buttons with "performClick()" or "setChecked" in some methods and managing the expand/collapse action in an OnCheckedChangedListener, I decided to store the status of the togglebuttons in an array, and to expand/collapse the child views in the "getGroupView" method depending on the state of the groupview's button, whose position in the array corresponds to the groupview's position in the list. To update the list, I call notifyDataSetChanged() on the adapter. I added a FrameLayout around the ToggleButton to increase its clickable area.

Here's the code for clarity:

The relevant parts of the MainActivity:

public class MainActivity extends ActionBarActivity {

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    .
    .
    .

    ExpandableListView lv = (ExpandableListView) findViewById(R.id.normalList);
    CustomList adapter = new CustomList(MainActivity.this, list /*the list of Items, declared elsewhere*/, lv);
    lv.setAdapter(adapter);
    DrawerLayout mDrawerLayout = (DrawerLayout) findViewById(R.id.filesListDrawerLayout);

    lv.setOnGroupClickListener(new OnGroupClickListener() {

        public boolean onGroupClick(ExpandableListView parent, View v, int position, long id) {

            uncheckButtons(mDrawerLayout);

            //do some unrelated stuff

            return true;
        }
    });
}

public void uncheckButtons(ViewGroup vg) {

    for(int i = 0; i < adapter.buttons.length; ++i)
        adapter.buttons[i] = false;

        adapter.lastChecked = -1;

        adapter.notifyList();
}

The relevant parts of the adapter:

public class CustomList extends BaseExpandableListAdapter {

private final Activity context;
public final List<Item> names; //Item is a custom Object
public boolean[] buttons;
public int lastChecked;
private final Integer[] imageId = new Integer[] { R.drawable.image1, R.drawable.image1, R.drawable.image3 };
private ExpandableListView list;

public CustomList(Activity context, List<Item> names, boolean forUpload, ExpandableListView list) {

    this.context = context;
    this.names = names;
    this.list = list;
    this.buttons = new boolean[names.size()];
    this.lastChecked = -1;
}

private static class GroupHolder {

    TextView txtTitle;
    TextView txtData;
    ImageView imageView;
    ToggleButton toggle;
    FrameLayout frame;
}

public View getGroupView(final int position, boolean isExpanded, View convertView, ViewGroup parent) {

    GroupHolder holder;

    if (convertView == null) {
        LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        convertView = inflater.inflate(R.layout.list_single, null);

        holder.txtTitle = (TextView) convertView.findViewById(R.id.text);
        holder.txtData = (TextView) convertView.findViewById(R.id.counter);
        holder.imageView = (ImageView) convertView.findViewById(R.id.img);
        holder.toggle = (ToggleButton) convertView.findViewById(R.id.toggle);
        holder.frame = (FrameLayout) convertView.findViewById(R.id.frame);

        convertView.setTag(holder);

    } else {

        holder = (GroupHolder) convertView.getTag();
    }

    txtTitle.setText(names.get(position).getName());

    txtData.setText(names.get(position).getData());

    if(/*some conditions*/) {

        imageView.setImageResource(imageId[0]);

        holder.frame.setOnTouchListener(new OnTouchListener() {

            public boolean onTouch(View v, MotionEvent event) {

                if(event.getAction() == MotionEvent.ACTION_UP) {

                    if(lastChecked != -1) {//if there's a checked button
                    buttons[lastChecked] = !buttons[lastChecked];//uncheck it

                    if(position == lastChecked)//if I clicked on the last checked button 
                        lastChecked = -1;//there's no checked button
                    else {
                        buttons[position] = !buttons[position];//check the button
                        lastChecked = position;//and set it as the last checked one
                    }

                    notifyList();
                }

                return false;
            }
        });

        holder.toggle.setChecked(buttons[position]);

        if(holder.toggle.isChecked()) {

            if(Build.VERSION.SDK_INT <= Build.VERSION_CODES.HONEYCOMB_MR2)
                list.expandGroup(position);
            else
                list.expandGroup(position, true);
        } else {

            list.collapseGroup(position);
        }

    } else if(/*other conditions*/) {

        //do other stuff with the views that don't interest us
    }

    return convertView;
}

private static class ChildHolder {

    TextView details;
    TextView send;
    TextView other;
}

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

    ChildHolder holder;

    if (convertView == null) {
        LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        convertView = inflater.inflate(R.layout.list_child, null);

        holder = new ChildHolder();

        holder.details = (TextView) convertView.findViewById(R.id.details);
        holder.send = (TextView) convertView.findViewById(R.id.send);
        holder.other = (TextView) convertView.findViewById(R.id.other);

    } else {

        holder = (ChildHolder) convertView.getTag();
    }

    //maybe I could manage the listeners more nicely and without redundance...

    holder.details.setOnClickListener(new OnClickListener() {

        public void onClick(View v) {

            buttons[lastChecked] = !buttons[lastChecked];//uncheck the only checked button (it always exists at this point)
            lastChecked = -1;//there's no checked button
            notifyList();

            //call a certain method in the main activity...
        }
    });

    holder.send.setOnClickListener(new OnClickListener() {

        public void onClick(View v) {

            buttons[lastChecked] = !buttons[lastChecked];//uncheck the only checked button (it always exists at this point)
            lastChecked = -1;//there's no checked button
            notifyList();

            //call a certain method in the main activity...
        }
    });

    holder.other.setOnClickListener(new OnClickListener() {

        public void onClick(View v) {

            buttons[lastChecked] = !buttons[lastChecked];//uncheck the only checked button (it always exists at this point)
            lastChecked = -1;//there's no checked button
            notifyList();

            //call a certain method in the main activity...
        }
    });

    return convertView;
}

public void notifyList() {

    this.notifyDataSetChanged();
}

//I don't need OnGroupExpanded anymore

The ExpandableListView:

<ExpandableListView
    android:id="@+id/normalList"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:choiceMode="singleChoice"
    android:groupIndicator="@drawable/toggle_button_selector" >
</ExpandableListView>

The xml of the group item:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/filesListDrawerLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="2dp" >

<ImageView
    android:id="@+id/img"
    android:layout_width="48dp"
    android:layout_height="48dp"
    android:layout_alignParentLeft="true"
    android:layout_alignParentTop="true"
    android:layout_marginLeft="10dp"
    android:layout_marginRight="2dp"
    android:layout_marginTop="2dp"
    android:layout_marginBottom="2dp"
    android:contentDescription="@string/icondescription" />

<TextView
    android:id="@+id/text"
    android:layout_width="wrap_content"
    android:layout_height="29dp"
    android:layout_alignTop="@+id/img"
    android:layout_marginLeft="15dp"
    android:layout_toRightOf="@+id/img"
    android:gravity="center_vertical"
    android:textAppearance="?android:attr/textAppearanceMedium"
    android:textColor="@color/black" />

<TextView
    android:id="@+id/counter"
    android:layout_width="wrap_content"
    android:layout_height="19dp"
    android:layout_alignBottom="@+id/img"
    android:layout_below="@+id/text"
    android:layout_marginLeft="15dp"
    android:layout_toRightOf="@+id/img"
    android:gravity="center_vertical"
    android:textAppearance="?android:attr/textAppearanceSmall"
    android:textColor="@color/black" />

<FrameLayout
    android:id="@+id/frame"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_alignParentRight="true"
    android:layout_below="@+id/text"
    android:layout_marginTop="-15dp"
    android:clickable="true"
    android:paddingTop="10dp"
    android:paddingRight="15dp"
    android:paddingLeft="10dp" >

    <ToggleButton
        android:id="@+id/toggle"
        android:layout_width="25dp"
        android:layout_height="25dp"
        android:background="@drawable/toggle_button_selector"
        android:clickable="false"
        android:duplicateParentState="true"
        android:focusable="false"
        android:focusableInTouchMode="false"
        android:textColorLink="@android:color/transparent"
        android:textOff=""
        android:textOn="" />

</FrameLayout>

</RelativeLayout>

The xml of the child item:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content" >

    <TextView
        android:id="@+id/details"
        android:layout_width="0dp"
        android:layout_height="57dip"
        android:layout_weight="1"
        android:clickable="true"
        android:drawableTop="@drawable/ic_action_about"
        android:drawablePadding="-12dp"
        android:background="@drawable/toolbar_selector"
        android:gravity="center"
        android:text="@string/details" />

    <TextView
        android:id="@+id/send"
        android:layout_width="0dp"
        android:layout_height="57dip"
        android:layout_weight="1"
        android:clickable="true"
        android:drawableTop="@drawable/ic_action_new_email"
        android:drawablePadding="-12dp"
        android:background="@drawable/toolbar_selector"
        android:gravity="center"
        android:text="@string/send" />

    <!-- this one is new -->
    <TextView
        android:id="@+id/other"
        android:layout_width="0dp"
        android:layout_height="57dip"
        android:layout_weight="1"
        android:background="@drawable/toolbar_selector"
        android:clickable="true"
        android:drawablePadding="-12dp"
        android:drawableTop="@drawable/ic_action_other"
        android:gravity="center"
        android:text="@string/other" />

</LinearLayout>

There. I'm happy now.

OTHER TIPS

Your list looks like ExpandableListView. Did you try to use it? It has build it second level rows, expanding, hiding, etc. Simply add your pdf rows as group headers and then add tool rows as child rows. Then you can handle row clicks to show and hide rows you would like to show/hide. See: http://developer.android.com/reference/android/widget/ExpandableListView.html

And, btw., using additional rows with hiding is a bit wrong UX design. Maybe it would be better to use drop down lists or custom dialog windows? Both of them have built in functionality do disappear when touched outside.

You can simply put all your ToggleButton whenfilling the list in an ArrayList. This way you just have to force the click on each button of list toggled when one of them is clicked.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top