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:
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!