Tuesday, February 22, 2011

3-level expandable lists

I got a question in a comment whether 3-level expandable list would be possible. I responded that the ExpandableListView implementation is wired to have two levels but then another comment mentioned that it may be possible by embedding one ExpandableListView into another. I was busy then but now I found time to take the challenge and to implement a test program.

Click here to download the example program.

Our three-level list is a variation of an earlier example program but I inserted another level (somewhat without reason :-)).


The principle of the implementation is simple. The first-level list is backed by an ExpandableListAdapter. The child views generated by this adapter are ExpandableListAdapters themselves that provide the second-level groups and the eventual child elements. Listeners are set for the group events (expand/collapse) of the second-level lists. When a group event occurs on a second-level list, the layout of the first-level list is also recalculated to accomodate for the size changes of second-level expandable lists.

This sounds simple but there are some tricky points that made me spent more time on this prototype than I expected. The first issue is with the individual rows of the second-level lists and the view recycler. Check out getChildView() in ColorExpListAdapter.java. When the layout of the list is recalculated, the old views are offered to the list adapter for reuse. The getChildView() method gets the old view instance in the convertView parameter. If convertView is not null, the adapter has the option of reusing the old view instance. In this case the adapter does not instantiate a new view but sets the old view instance appropriately, decreasing garbage (Click here if you want to read more about the importance of less garbage collection in mobile environments). Now our problem is that we cannot just set up an ExpandableListView instance without destroying its internal state, i.e. the expanded/collapsed state of the groups. For this reason, it is extremely important to preserve the views and to prevent the first-level ExpandableListView of rearranging the second-level lists (handing out a second-level ExpandableListView in a certain position as a convertView at a different position). For this reason I implemented a cache in ColorExpListAdapter that makes sure that only one second-level ExpandableListView is generated for each child position in the first-level list and that second-level ExpandableListView instance is consistently returned for the same position. This guarantees that the collapsed/expanded state of the second-level views are preserved when the layout of the first-level list is recalculated.

The other tricky issue is the size of the second-level lists. The layout recalculation of the first-level list is triggered by a group event of a second-level list. Due to the way expandable lists are implemented, when this event occurs, the items of the second-level list which was clicked are not yet added/removed according to the expand/collapse action. This means that the size of the list will be incorrect when the first-level list layout is recalculated.

To solve this problem, observe the row count calculation in ColorExpListAdapter's calculateRowCount method and the way the row count is used in DebugExpandableListView's onMeasure method. DebugExpandableListView originally started to exist so that I can observe the layout of the second-level lists then it turned out that it has an important function: override the onMeasure method of the original ExpandableListView (actually inherited from ListView). Lazily, I kept the class name and the debug messages in it so that you can observe the rather complex operation of the layout process if you feel like.

57 comments:

Anonymous said...

The code doesnt seem complete. When checking there is a reference to the method "getExpandableListView()" when creating a new adapter in OnCreate() but I cant find that method. Also cant find the method setListAdapter.

Anything I have done wrong or is it something that is missing?

//TitanSky

Gabor Paller said...

TitanSky, those methods are inherited from ExpList3's base class, ExpandableListActivity (fully qualified name is android.app.ExpandableListActivity). Does the code compile correctly? (I have just tried, it does for me)

Unknown said...

Nice blog. I'm just getting started with some Android development so will check your blog often

Anonymous said...

Well, i have situation like this i hope you are definitely going to help me..i want to replace all the strings in 2nd levels's content i.e #D3D3D3, #696969, #EAEAEA,#1C86EE...etc with check boxes and implement on-click listener for those contents for checking whether corresponding check box is checked or not..hope you got me..

Gabor Paller said...

vgsl-manju, there are two posts about this situation this is the second and the first one is linked from it. These posts build on each other, read both.

sri said...

can i have code for dynamic expandable list view

Gabor Paller said...

sri, you may want to check this post.

Anonymous said...

First of all thanks a lot, your tutorial helped me a lot.
Now i wanted to know if groupview has no child then there should not be a icon or there should be a empty icon. How to do the same?

Gabor Paller said...

Anonymous, do you mean that if there is no entry for a certain second-level group then there should not be an expansion icon on the first level either?

Anonymous said...

How can i add list view as child?? i would like to add n number of expandablelistview depending on the requirements..any suggestions on how to do this.
In your example you have specified the size of the array ...i dont want to hardcode that value but would rather change depending on the tree size.

JS said...

Thanks for the great tutorial. Can you please tell me how can i make a n level tree?
Thanks
J

Bratin said...

Good code man.Nice blog.

www.androidcookers.co.cc

Woody NaDobhar said...
This comment has been removed by the author.
Woody NaDobhar said...

Good tutorial, but I'm having some trouble I hoped you might have some thoughts on. My array of values isn't static, it's based of off some html parsing. I can't figure out how to populate the contents[][][][] string array with my data. In my content, h1's and h2's are my groups (respectively), and the rest of the stuff is the 'colors'.

Anonymous said...

How can we handle click of last child?

Manish said...

Thank you very much. I've been struggling very much the whole day. I got everything hooked up but the children were not getting aligned properly. I stumbled upon this and that one magic line which has been evading me all day:

v = inflater.inflate(R.layout.group_row, parent, false);

I was specifying the wrong parent.

Thank you! I really appreciate it.

Anonymous said...

Excellent pieces. Keep posting such kind of information on your blog. I really impressed by your blog.

Rakesh said...

Thank you very much for the post. I have question how customize group indicator at second level? I wan to disable group indicator at second level. I am able to add image there but could disable default indicator at second level.

Rakesh said...

Thank you very much for the post. I have a question how to customize group indicator at second level? I want to disable group indicator at second level. I am able to add image there but i am not able to disable default indicator at second level.

Gabor Paller said...

Rakesh, does this post help?

Rakesh said...

Yes, Gabor paller this post helped me. i got answer for the question i asked...Now iam try to set click listener to child view of level 2

Linh Le said...

I want to create multi level list view for my Product category. But i seem that your example fix level 1 and 2. There isn't any way to do it more general,is it? I get stuck now. I hope you will reply soon

Gabor Paller said...

Linh, I am not sure 3-level lists are such a great idea. For me, even 2-level lists are somewhat confusing. But you can use the same concept as used in this example program: embed expandable lists as child views of upper-level expandable lists. Then you can create expandable lists of any depth.

Linh Le said...

All right, i will try thinking that way. Thank you so much.

Schilling D. said...

First of all, nice code! gz! Your post helped me a lot, thank you.

I have a question regarding the child node. Lets say that I want to start a new activity from the child row selection. Im aware of the onChildClick() method but Im having trouble to adapt it to your code. Could you throw me an example?

Anonymous said...

how can i add Upto 4 level expandable treeview in Android. I try this code but not add fourlevel plz help me
thanks

Gabor Paller said...

Rupal, just follow the example, it is easy. You have to add another expandable list views as children of the second-level expandable list views.

Anonymous said...

i try but not solve plz hel me

Anonymous said...

i try but not done plz help me how to add 4-level in Expandable listview

Anonymous said...

Hi,
Excellent tutorial.But i want to set different heights for my child 1 and child 2.How can i do it?plz help...Thanks in advance

Gabor Paller said...

Rupal, here you are.

But don't come with new requirements! :-)

Gabor Paller said...

Anonymous, there you find the following line in DebugExpandableListView.java:

Method:
onMeasure

setMeasuredDimension( getMeasuredWidth(), rows*ROW_HEIGHT );

As you can see, this simple program calculates every row with the same height. You need to implement more sophisticated logic here to support rows with different height.

Anonymous said...

can we implement checkbox with each group subgroup and child.. please help.. because i am developing tree structure with checkbox on the right side of each element.. and if checkbox can be implemented how to implement oncheckedchange listener on each checkbox..

krishnaveni said...

thank you for the code,it helps me a lot... But the scroll view is not working properly,, the child items in the third level are not scrolling,,, once check it please.... Thanks in advance

Gabor Paller said...

krishnaveni, you mean that you added a scroll view to the third level? I never tried that.

Unknown said...

Hi, i want to know if with your code i could create an infinite lvl expandablelistView ? i want to make a file explorer using this method.

In the getChildView i will return another expandablelistview if the file is a directory.

If you can help me answering by email

THE_KING_OF_BLOG said...

Hi I am trying to implement a expandable list with edit text and text views as child elements.In short I want form elements as child elements of group like edit text ,spinner etc .Please help me to implement this kind of code

Unknown said...

Hello,

Thanks for your explanation. I tried it and it goes fine.

I would like to adapt this to dynamically calculate the height of the "embedded" ExpandableListView because all the rows within it could have different height... So i can't count the number of rows and set "height = count * row_height".

I tried to getHeight() from each getChildView() and getGroupView() of my adapter but 0 is always returned...

I think it's because :

"Be aware not to call getWidth() or getHeight() too early before the view have been laid out on the screen, otherwise "0" will be returned."


Any idea of to fix that ?

kishore said...

Hi, is there anyway to create expandable list with different layout for each group, and different child layouts according to their parents.

Unknown said...

How you define the ROW_HEIGHT in DebugExpandableListView. Where did you get this number?

Gabor Paller said...

Hi, Ricardo,

ROW_HEIGHT is the height of a list row in pixels. Need to be changed if you change child3_row.xml. The value is used to calculate the size of the 2nd level list if that particular child is open.

Probably there's a more elegant way to measure the height of the second-level lists, this one is pretty naive.

mutkan said...

Hi, First of all, thank you for the dynamic expendablelist for android.

I customized your code and add to checkbox feature, but there is a problem and i'm going to be crazy to solve it.

I want to check all checkbox from the parent root group but it does check one.

Could you help me on this issue?

Thank you so much.

The Customize Android project link : https://mega.co.nz/#!tw8gjJ5Y!XDYFvTYBObRJUVa2dLAGxTMtXBcwYpOvY4nfejt7U40

Pelle said...

Thanks for the sample, but when I switch to landscape mode the second level lists are automatically closed... :(
Do you have any suggestion to solve this problem?

Anonymous said...

Hi, a very useful tutorial! I'm new at android and I wonder if it is possible to use a xml file for the list instead of puttin it in the java file.

Thanks a lot!

Gabor Paller said...

Anonymous, you can read the data items from an asset file.

Unknown said...

Hi,
First of all thanks for this nice project. It helped me in my project.
Now I am facing one issue.
I am creating 3 level expandable list with swipe functionality at 3rd level. For swiping, I am using "47deg/android-swipelistview" https://github.com/47deg/android-swipelistview/ library. Based on your code, I have used second level expandablelistview and inside that I have added swipelistview (inherited from listview) as child.
I have used caching concept at getview function of swipelistview.
With caching on, I get very good scrolling performance but at that time, swiping functionality is not working at third level swipelistview. If caching is disabled, swiping feature works but while scrolling, performance is down.
If I use caching at 1st level expandable listview, swiping feature works properly.
Can you please suggest the reason for the same.

Anonymous said...

Hi Gabor. This is a great tutorial.
But I've found a bug, it happens when you have opened all the 2 level groups, and if you'd start randomly closing and opening groups, at some point(usually takes 30~ taps) 2 level groups would not respond. Not all of the 2nd level groups, but again randomly. Sometimes simply closing parent group would make it go away, but sometimes you'd need close and open other sub groups. I was trying to pin that bug down, but was unable to. It all seems to be random. One thing that is not changing is that it only happens to 2nd level groups. I also have noticed when a 2nd group becomes unresponsive on press an orange highlight will appear. I've tested with jellybean 4.1.2 Do you have any idea what this could be related to? Anyway great tutorial :) Cheers.

Gabor Paller said...

Anonymous, any hint in the debug log? This code logs quite much.

lokeshp said...

Please explain the fields in listdesc array.I want to have my own group , sub group and child names rather than color , sub color and grey codes.
Plz help

Indrasena said...

Could you please suggest, how can we assign the data(colors=> groups and child's) in DYNAMIC instead of static.

can you reply as soon as possible it's very urgent for me... Thank you so much in Advance

Indrasena said...

can you suggest, how to initialize dynamic data into this example...

can we initialize the data to 4Dimensional array using dynamic data.

could share this as soon as possible

thank you so much

Handsome Jack said...

Hey @GaborPaller, I must say, this is a genius implementation,as far as I am concerned. I'm still struggling with adding a Spinner inside an ExpandableListView. Would you mind helping me with it? I'm confused as to where do I write the code for setting the Spinner adapter, and what to write in getChild() and other such methods of BaseExpandableAdapter. A sample program would be a much needed help! You can email me at handsomejack436@yahoo.com.

Anonymous said...

This seems to be nice blog to follow. But I have been trying to solve one problem, I wish if readers can help on that.
I am populating ExpandableListView dynamically and on every child item I have a spinner (which is again dynamic). The problem here is, I am not able to differentiate between which child item is clicked with respective spinner. Does anyone have their views here?

abhi said...

sir, could you plz tell how we should give ROW_HEIGHT as wrap content.thanks in advance..

abhi said...

sir, could you plz tell how we should give ROW_HEIGHT as wrap content because i want to give ROW_HEIGHT dynamically as child of 2nd view list is hiding.Thanks in advance..

Gabor Paller said...

abhi, I am not sure I understand. ROW_HEIGHT is a somewhat simplistic solution for the problem of the expandable list to estimate the height of a child expandable list. Sadly, when this value is needed, the child expandable list is not yet created so its size cannot be measured like a normal view. Hence this constant.

Anonymous said...

Thanks a lot for your example. it works 100 %