In android, the ListView is one of my favorite widgets due to its beautiful implementation, it’s high performance and it’s flexibility. However, the ExpandableListView (which extends ListView) is much more primitive. A much needed feature that I was looking for is missing and the solutions offered online seemed less then ideal. The feature that I’m talking about is the animating of expanding and collapsing groups. Disappointed that there were no great solutions to this I decided to write my own.
The problem with the popular solution
There is a very popular solution floating online which is to use a ListView instead of an ExpandableListView and populates the ListView with custom views which are animated to expand and contract. Although this solution does work if the number of children of a group is small, it will miserably fail when the child is more complex and large. This is because this solution uses a ListView therefore although group items will enjoy the benefits of being in a ListView, child items will not meaning they will take up more memory (so if you have 100 child views for one group, all 100 child views will get loaded into memory) and all child views will not be optimized (for instance if you have 100 child views, they will be iterated over every time the screen is drawn). So the main draw back here is performance when the number of children of a group is sufficiently large.
A better implementation
A better implementation does exist, the main drawback being it is a bit more difficult to implement correctly but once implemented will still offer all the benefits of an ExpandableListView while providing great animations. However, we can ignore the drawback since I’ve already done the implementation part, all you need to do is include it in your project! You can view my implementation and a sample application here:
https://github.com/idunnololz/AnimatedExpandableListView
How does it (the AnimatedExpandableListView) work?
Well one thing you will need to offer all of the benefits of an ExpandableListView is to have an ExpandableListView, so the AnimatedExpandableListView does just that and extends ExpandableListView. The rest, however, is a bit complicated. I have done my best to put a lot of comments in my source, so you may find you’ll learn a lot from reading the source. However, to summarize how it all works:
/** A detailed explanation for how this class works:** Animating the ExpandableListView was no easy task. The way that this* class does it is by exploiting how an ExpandableListView works.** Normally when ExpandableListView.collapseGroup(int) or* ExpandableListView.expandGroup(int) is called, the view toggles* the flag for a group and calls notifyDataSetChanged to cause the ListView* to refresh all of it’s view. This time however, depending on whether a* group is expanded or collapsed, certain childViews will either be ignored* or added to the list. Creating the effect of group expansion or collapse.** Knowing this, we can come up with a way to animate our views by using a* custom adapter which will supply dummy views which we will animated. For* instance for group expansion, we tell the adapter to animate the* children of a certain group. We then expand the group which causes the* ExpandableListView to refresh all views on screen. The way that* ExpandableListView does this is by calling getView() in the adapter.* However, since the adapter knows that we are animating a certain group,* instead of returning the real views for the children of the group being* animated, it will return a fake dummy view. This dummy view will then* draw the real child views within it’s dispatchDraw function. The reason* we do this is so that we can animate all of it’s children by simply* animating the dummy view. After we complete the animation, we tell the* adapter to stop animating the group and call notifyDataSetChanged. Now* the ExpandableListView is forced to refresh it’s views again, except this* time, it will get the real views for the expanded group.** So, to list it all out, when expandGroupWithAnimation(int) is* called the following happens:** 1. The ExpandableListView tells the adapter to animate a certain group.* 2. The ExpandableListView calls expandGroup.* 3. ExpandGroup calls notifyDataSetChanged.* 4. As an result, getChildView is called for expanding group.* 5. Since the adapter is in “animating mode”, it will return a dummy view.* 6. This dummy view draws the actual children of the expanding group.* 7. This dummy view’s height is animated from 0 to it’s expanded height.* 8. Once the animation completes, the adapter is notified to stop* animating the group and notifyDataSetChanged is called again.* 9. This forces the ExpandableListView to refresh all of it’s views again.* 10.This time when getChildView is called, it will return the actual* child views.** For animating the collapse of a group is a bit more difficult since we* can’t call collapseGroup from the start as it would just ignore the* child items, giving up no chance to do any sort of animation. Instead* what we have to do is play the animation first and call collapseGroup* after the animation is done.** So, to list it all out, when collapseGroupWithAnimation(int) is* called the following happens:** 1. The ExpandableListView tells the adapter to animate a certain group.* 2. The ExpandableListView calls notifyDataSetChanged.* 3. As an result, getChildView is called for expanding group.* 4. Since the adapter is in “animating mode”, it will return a dummy view.* 5. This dummy view draws the actual children of the expanding group.* 6. This dummy view’s height is animated from it’s current height to 0.* 7. Once the animation completes, the adapter is notified to stop* animating the group and notifyDataSetChanged is called again.* 8. collapseGroup is finally called.* 9. This forces the ExpandableListView to refresh all of it’s views again.* 10.This time when the ListView will not get any of the child views for* the collapsed group.*/
And that pretty much sums up how this thing works. Enjoy!
Alex Harmash says:
May 20, 2014 at 4:05 pm
I use your lib for animate expandable list. My child view has border (left, rigth, bottom), that i’ve maden using ninepatch format as view background. When expandable list start animate – my bottom border disappeared, but my right and left border still visible. When animation finished, my bottom border set visible. Please, can you tell me why my bottom border doesn’t draw with other elements of the view ? Also in this library, you redrawing divider every time, cause it also has such effect. Thank you!
Hiren says:
June 24, 2014 at 11:45 am
Hi, your code for expandable listview works great, is it possible animated expandable listview for three level, i have tried to implement using your code, but animation doesn’t work proper if i used animated expandable listview in getReadChildView() method. Any suggestions, points to consider while implementing three level listivew. Thanks.
idunnololz says:
June 24, 2014 at 6:29 pm
Hmm, from past experience, I have reason to believe that remeasure is not carried out inside the children of an expendable list view in the same way that one would expect. Due to this, you might need to do some extra work in the animation method for the inner animated expendable list view. I’m not sure if it is as easy as a call to relayout on every step of the animation or not, but it’s worth a try.
Anyways on a higher level, I would assume if you wanted to implement three level listviews that the most obvious approach is to nest an expandable list view inside another expandable list view and I assume this is what you are doing.
Rogier says:
July 10, 2014 at 8:45 am
Hi there,
Your code works nicely, thanks for the extensive documentation.
Two quick questions:
– When expanding, my adapter’s getRealChildView is called twice. The second time, convertView is a DummyView, not our my custom view. Does that mean I have to inflate a new view every time getRealChildView is called?
– When collapsing, your adapter’s getChildView is called. At that time, convertView is given one of our own own custom views, created by my adapter’s getRealChildView when expanding. As convertView is not null, no new DummyView replaces it, and line 38 (final DummyView dummyView = (DummyView) convertView) throws a ClassCastException. What am I doing wrong?
Thanks for any help!
Kind regards,
Rogier
Rogier says:
July 10, 2014 at 2:26 pm
ps. By line 38 I mean line 38 of the AnimatedExpandListAdapter’s getChildView method.
idunnololz says:
July 28, 2014 at 9:45 pm
Sorry for the late reply, it’s the end of the term and I’m getting hammered with assignments plus I need to study for finals. Hmm… This sounds like an interesting problem. In theory, what I expect to happen is that the ExpandableListView will call getChildType first to determine the type of convertview to supply in the getChildView call. In which case, if we are animating I would return the id 0 (for the dummy view id) or whatever value you supply in getRealChildType + 1 (to resolve Id conflicts). So in theory you should not be getting a dummy view and the AnimatedExpandableViewAdapter should not be getting your view. Hmm, can you double check your code to make sure that you are returning a non negative value in getRealChildType?
ivolianer says:
July 19, 2014 at 4:02 am
Hello, your widget is very good. Thanks for your sharing.
I really like this one.
By the way, when the child view just include a textview, the listview performance well.
When the child view include imageview or other more complicated structrue, its performance is not satisfactory.
Although I use the converView and childHolder like you do, the speed of rendering is not fast enough.
idunnololz says:
July 28, 2014 at 9:47 pm
Hmm, interesting. I will do a few tests with more complicated structures once I get time (in about a week). Sorry to hear that the library could not meet your requirements though.
Brendan says:
July 19, 2014 at 4:42 am
Thank you so much for this! This was a breeze to incorporate into my project.
I only seem to have one issue. When I expand the last group, the animation either does not happen or it glitches and expanding child blinks halfway through. Also when sometimes when I expand another group, as the list expands the bottom item (whether it be a group or expanded child) will flash.
If it helps, my lists do not take up the entire layout space and there is empty space at the bottom. Thanks for your time!
ivolianer says:
July 22, 2014 at 9:11 am
Hi, i am unhappy, because you delete my reply.
I got back to tell you, when you use android:layout_gravity=”center_vertical” or android:gravity=”center_vertical”
in the chlid view.
The listview performance worst.
P Kuijpers says:
July 28, 2014 at 1:07 pm
The animations work great, although I’ve experienced some bugs in specific situations. I’m wondering if it’s something I have to repair myself, or whether the bug described below is something that could be fixed in the AnimatedExpandableListView code:
Situation: I’ve got a list with 5 groups, of which the first and second (index 0 and 1) are expandable. If the group (with index) 0 (G0) is already expanded, and I click on group 1 (G1), then G0 should collapse simultaniously, so I call collapseGroupWithAnimation(0) followed by expandGroupWithAnimation(1).
This works fine in a screen that is scrolled to the top (top of G0 is top of listview). But when G0 is expanded, I scroll down until G0 (including children) is moved (just) off the screen, and THEN I try to expand G1, and it crashes (nullpointer in expandGroupWithAnimation(1), where v = null, because of an incorrect childIndex: it’s -1 while it should be 0. Because of this, G1 is considered to be offscreen because “… the user is not going to be able to see the animation” as the comments explain? In my case expandable animations are (I guess) always visible / onscreen, so I’ve added a temporary workaround by checking if v != null before calling v.getBottom(), which prevents the crash.
Is this something you could improve in the code, or am I doing something wrong myself?
Note: in the described situation, collapseGroupWithAnimation(0) works fine (although the childIndex is -1 as well, the animation is correctly ignored in collapseGroupWithAnimation() because it is offscreen).
idunnololz says:
July 28, 2014 at 9:50 pm
Hmm, thanks for bringing this up. I will have to investigate this more once I am done with my school work however that will be in a week or two. I’ll try to get this fixed as soon as I can.
Jacob says:
October 16, 2014 at 4:59 pm
This is exactly what I’ve been looking for! I’m brand new to Android development (and development in general) and I was able to implement your list with very few hiccups. I even edited it to include all my information. One question I have though is: The title of my app has now changed to AnimatedExpandableListView instead of my actual title in the ActionBar, and I can’t seem to figure out why that is. Is there something I changed that caused that?
Jacob says:
October 18, 2014 at 11:36 pm
I solved the issue, apparently you just need to edit the strings.xml to the whatever your app name is.
idunnololz says:
October 19, 2014 at 2:56 am
The default string used in the action bar or title bar is determined by the value of android:name of your activity as defined in AndroidManifest.xml.