4

I made an usual 3-level ExpandableListView based on the many examples on the internet and it works well.

With one caveat. Clicking on the 2nd and 3rd level does not work always. Although sometimes it works fine. Usually at start it works everywhere. Then it does not work anymore, on like 50% of the items. Without any logic. Sometimes I expand a 2nd group and then I cannot collapse that group anymore. Or sometimes I expand a 2nd group and then I cannot collapse any other group of the 2nd level. The only consistent thing is that it often gets fixed by scrolling the locked up view out of the visible area and back in.

What could be going on?

    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.chooselib);

        //...

        ExpandableListView lv = (ExpandableListView) findViewById(R.id.libListView);
        lv.setAdapter(new LibraryListAdapter());
    }

    final List<String> states = new ArrayList<String>();
    final List<List<String>> cities = new ArrayList<List<String>>();
    final List<List<List<Map<String, String>>>> localLibs = new ArrayList<List<List<Map<String, String>>>>();



    public abstract class LibraryListBaseAdapter extends BaseExpandableListAdapter{ //changing these functions has no effect whatsoever on the problem
        @Override
        public Object getChild(int groupPosition, int childPosition)
        {
            return getChildId(groupPosition, childPosition);
        }

        @Override
        public long getChildId(int groupPosition, int childPosition)
        {
            return (groupPosition << 15) + childPosition + 1;
        }

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

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

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

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


    public class LibraryListAdapter extends LibraryListBaseAdapter
    {
        Map<Integer, ExpandableListView> cache = new TreeMap<Integer, ExpandableListView>();
        @Override
        public View getChildView(final int groupPosition, final int childPosition,
                                 boolean isLastChild, View convertView, ViewGroup parent)
        {
            if (cache.containsKey(groupPosition*10000 + childPosition))
                return cache.get(groupPosition*10000 + childPosition);
            LibraryListCityView l = new LibraryListCityView();
            l.setPadding((int)(60 * getResources().getDisplayMetrics().density), l.getPaddingTop(), l.getPaddingRight(), l.getPaddingBottom());
            //l.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); has no effect whatsoever
            //l.setFocusable(true); has no effect whatsoever

            l.setAdapter(new LibraryListCityAdapter(groupPosition,childPosition));
            if (cities.get(groupPosition).size() == 1) l.expandGroup(0);

            l.setOnChildClickListener(new ExpandableListView.OnChildClickListener() {
                @Override
                public boolean onChildClick(ExpandableListView expandableListView, View view, int i, int lib, long l) {
                    onLeafClick(groupPosition,childPosition,lib);
                    return false;
                }
            });

            cache.put(groupPosition*10000 + childPosition, l);
            return l;
        }

        @Override
        public int getChildrenCount(int groupPosition)
        {
            return cities.get(groupPosition).size();
        }

        @Override
        public int getGroupCount()
        {
            return states.size();
        }

        @Override
        public View getGroupView(int groupPosition, boolean isExpanded,
                                 View convertView, ViewGroup parent)
        {
            View row = //convertView != null && convertView instanceof TextView ? convertView :
                    getLayoutInflater().inflate(android.R.layout.simple_expandable_list_item_1, parent, false);
            ((TextView) row).setText(states.get(groupPosition));
            return row;
        }
    }

    public class LibraryListCityView extends ExpandableListView
    {
        int intGroupPosition, intChildPosition, intGroupid;
        public LibraryListCityView()
        {
            super(LibraryList.this);
        }
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
        {
            widthMeasureSpec = MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.EXACTLY);
            heightMeasureSpec = MeasureSpec.makeMeasureSpec(2000, MeasureSpec.AT_MOST); //what does this do?
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        }

        //from http://stackoverflow.com/questions/19298155/issue-with-expanding-multi-level-expandablelistview
        @Override
        protected void onDetachedFromWindow() {
            try {
                super.onDetachedFromWindow();
            } catch (IllegalArgumentException e) {
                // TODO: Workaround for http://code.google.com/p/android/issues/detail?id=22751
            }
        }
    }

    public class LibraryListCityAdapter extends LibraryListBaseAdapter
    {
        int state,city;

        LibraryListCityAdapter (int a, int b) {
            state = a;
            city = b;
        }

        @Override
        public View getChildView(int groupPosition, int childPosition,
                                 boolean isLastChild, View convertView, ViewGroup parent)
        {
            //todo: use convertView (but do not want to mix groupView/childView up)
            View row = getLayoutInflater().inflate(R.layout.libraryinlistview , parent, false);
            ((TextView) row).setText(localLibs.get(state).get(city).get(childPosition).get("NAME"));
            return row;
        }

        @Override
        public int getChildrenCount(int groupPosition)
        {
            return localLibs.get(state).get(city).size();
        }


        @Override
        public int getGroupCount()
        {
            return 1;
        }

        @Override
        public View getGroupView(int groupPosition, boolean isExpanded,
                                 View convertView, ViewGroup parent)
        {
            View row = getLayoutInflater().inflate(R.layout.librarycityinlistview, parent, false);
            ((TextView) row).setText(cities.get(state).get(city));
            return row;
        }

    }

chooselib.xml:

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

    <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/lay_chooselib_choose"
            android:id="@+id/textView" android:layout_gravity="center_vertical|left" android:padding="5sp"/>
    <ExpandableListView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:id="@+id/libListView" android:layout_gravity="right|center_vertical"
            />
</LinearLayout>

librarycityinlistview.xml:

<TextView xmlns:android="http://schemas.android.com/apk/res/android"
          android:id="@android:id/text1"
          android:layout_width="fill_parent"
          android:layout_height="?android:attr/listPreferredItemHeight"
          android:gravity="center_vertical"
          android:paddingLeft="?android:attr/expandableListPreferredChildPaddingLeft"
          android:singleLine="true"
          android:textAppearance="?android:attr/textAppearanceListItem"
        />

libraryinlistview.xml:

<TextView xmlns:android="http://schemas.android.com/apk/res/android"
          android:id="@android:id/text1"
          android:layout_width="fill_parent"
          android:layout_height="?android:attr/listPreferredItemHeight"
          android:paddingLeft="60dip"
          android:gravity="center_vertical"
        />

how it looks

Sounds a little like https://code.google.com/p/android/issues/detail?id=3414 but kind of is the opposite (since the problematic list is an item to the outer list view), and changing descand focusability did not change anything. (since only textviews are in the list they are not focusable anyways). And nothing useful in the logcat, too

What I do not really understand of the source I based on is the stuff in onMeasure. And the split between the groups and children of the listviews. Perhaps it is better to have an expandable list view for every group on the 1st level, instead one for every group on the 2nd level?

BeniBela
  • 16,412
  • 4
  • 45
  • 52
  • I think it has something to do with [this](http://stackoverflow.com/questions/8815066/android-delayed-clicks-in-listview) – BeniBela Sep 06 '14 at 13:37
  • 1
    Are you sure that you have not f*cked up the caching? The ListView reuses child views and if you do not reinitialise *all* the listeners. to the correct (new) data your listeners will update the incorrect subview. – mach Sep 09 '14 at 06:05
  • @mach: I had a logging statement in every listener. When the problem occurs the listener is never called (or only later on parent collapse like in the linked question) – BeniBela Sep 09 '14 at 08:30
  • 1
    Try removing if (cache.containsKey(groupPosition*10000 + childPosition)) return cache.get(groupPosition*10000 + childPosition); and see if it works – mach Sep 09 '14 at 09:17
  • @mach: oh, it seems that fixes it, so far. But why, it is the opposite of what they had to do in the linked thread. – BeniBela Sep 13 '14 at 11:58
  • Without caching it might work, but I decided to play save and reimplemented it with nested LinearLayouts for now... – BeniBela Sep 16 '14 at 15:17

3 Answers3

1

Looking at your code I noticed that your hasStableIds method returns true, but getGroupId and getChildId return values based on position variables. I think this confuses ExpandableListView logic. hasStableIds should return true only if your objects have non mutable unique ids.

Okas
  • 2,664
  • 1
  • 19
  • 26
  • Makes no difference. (the information is constant, so ther eis always the same object at the same position) – BeniBela Sep 13 '14 at 12:10
1

Try to attach your click listener on eachgetChildView and getGroupViewmethods

public class LibraryListAdapter extends LibraryListBaseAdapter
{
    Map<Integer, ExpandableListView> cache = new TreeMap<Integer, ExpandableListView>();
    @Override
    public View getChildView(final int groupPosition, final int childPosition,
                             boolean isLastChild, View convertView, ViewGroup parent)
    {
        if (cache.containsKey(groupPosition*10000 + childPosition))
            return cache.get(groupPosition*10000 + childPosition);
        LibraryListCityView l = new LibraryListCityView();
        l.setPadding((int)(60 * getResources().getDisplayMetrics().density), l.getPaddingTop(), l.getPaddingRight(), l.getPaddingBottom());
        //l.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); has no effect whatsoever
        //l.setFocusable(true); has no effect whatsoever

        l.setAdapter(new LibraryListCityAdapter(groupPosition,childPosition));
        if (cities.get(groupPosition).size() == 1) l.expandGroup(0);



        cache.put(groupPosition*10000 + childPosition, l);
        l.setOnClickListener(new OnClickListener() {

                    @Override
                    public void onClick(View v) {
                        // TODO Auto-generated method stub
                        Log.d("LibraryListAdapter"," group clicked :  " + groupPosition + "childClicked :" + childPosition);

                    }
                });
        return l;
    }

    @Override
    public View getGroupView(int groupPosition, boolean isExpanded,
                             View convertView, ViewGroup parent)
    {
        View row =// convertView != null && convertView instanceof TextView ? convertView :
                getLayoutInflater().inflate(android.R.layout.simple_expandable_list_item_1, parent, false);
        ((TextView) row).setText(states.get(groupPosition));

        row.setOnClickListener(new OnClickListener() {

                    @Override
                    public void onClick(View v) {
                        // TODO Auto-generated method stub
                        Log.d("LibraryListAdapter"," group clicked :  " + groupPosition);

                    }
                });
        return row;
    }
}



public class LibraryListCityAdapter extends LibraryListBaseAdapter
{

    @Override
    public View getChildView(int groupPosition, int childPosition,
                             boolean isLastChild, View convertView, ViewGroup parent)
    {
        //todo: use convertView (but do not want to mix groupView/childView up)
        View row = getLayoutInflater().inflate(R.layout.libraryinlistview , parent, false);
        ((TextView) row).setText(localLibs.get(state).get(city).get(childPosition).get("NAME"));
        row.setOnClickListener(new OnClickListener() {

                    @Override
                    public void onClick(View v) {
                        // TODO Auto-generated method stub
                        Log.d("LibraryListCityAdapter"," group clicked :  " + groupPosition + " childClicked :" + childPosition);


                    }
                });
        return row;
    }


    @Override
    public View getGroupView(int groupPosition, boolean isExpanded,
                             View convertView, ViewGroup parent)
    {
        View row = getLayoutInflater().inflate(R.layout.librarycityinlistview, parent, false);
        ((TextView) row).setText(cities.get(state).get(city));
        rowsetText(localLibs.get(state).get(city).get(childPosition).get("NAME"));
        row.setOnClickListener(new OnClickListener() {

                    @Override
                    public void onClick(View v) {
                        // TODO Auto-generated method stub
                        Log.d("LibraryListCityAdapter"," group clicked :  " + groupPosition );


                    }
                });
        return row;
    }

}

The changes I made was only on the getGroupView and getChildView methods and delete the setOnChildClickListener from the upper Listview

karvoynistas
  • 1,295
  • 1
  • 14
  • 30
  • But there is no listener on the thing created in getChildView and the relevant listener in getGroupView is expandablelistview internal, not set by me. However, I also tried a version with listeners (manually expanding/collapsing on click instead letting the expandablelistview handle it) and there was no change – BeniBela Sep 13 '14 at 12:11
  • what do you mean?? Inside the adapter , you cannot declare before the return view statement? Suppose you will return the View convertView you have to write something like convertView.setOnClickListener(new OnClickListener() { ...} ); – karvoynistas Sep 13 '14 at 12:19
  • There are no `onClickListener`s! Only a `OnChildClickListener` on the nested listview. But since the nested list view sometimes does not expand in the first place, there is no item to click on anyways. – BeniBela Sep 13 '14 at 12:46
-1

I don't know what could be the problem in your case. But I can tell you the possible workaround for this problem.

Try using a library called FloatingGroupExpandableListView which is far more better than Expandable listview.

Using this you have to code less & also it changes the group color when a group view has been diminishing at the time of scrolling.

Vivek Warde
  • 1,936
  • 8
  • 47
  • 77