When I started to develop the first Android version of MyNextBus in London, I had to think about how implement the section management of different items types into a list. It was a great opportunity, at the time of the first writing, to use the framework’s widget called ExpandableListView. If you have never heard about that before, a quick look should solve any lack and make you more comfortable with what you’ll read below.
First Solution (v < 2.0.0)
As I said earlier the first attempt was based onto the ExpandableListView that manages for you, through an ExpandableListAdapter, the sections management using an instantaneous expansion/compression of the sections. That’s quite good because it
saves you a lot of time that should be spent to implement the logic used to keep track of the sections and to distinguish between children and sections. If you check more in deep in the documentation, you can also managed indicators for both the items types but that goes far from this tutorial.
The first problem arose some days after the first build when my friend Massimo, that during the same period was developing the iOS version, showed me that, in just few lines of code, his app was able to expand and collapse smoothly with an animation, hiding/showing the indicator. So, at that time I tried to implement the same logic for the indicators using an ObjectAnimator that animates between 0-1 and viceversa the alpha value of the indicator.
The expansion and compression of the cells was quite messy because I had to keep track of the expanded element, the number of children and a lot of other stuff without having the entire control on the adapter and the way it cooperates with the ListView.
When the ExpandableListAdapter calls the getGroupView(…) it passes into a boolean value that lets you know if the group is expanded or not and that happens after the user interaction. Therefore, you cannot perform something like:
compress-animating-open => expand-animating-closed as a single transaction
So, after having tried a lot, I had fed up and I decided to avoid any animation because the management was making my code a lot dirtier.
MyNextBus has continue to grow in the last months without an expand/compress animation that has left in me a kind of sense of un-finished.
Refactoring (v >= 2.0.0)
Just few weeks ago, speaking again with my friend and
complaining about the fact that on Android it’s a nightmare to perform animations, he said me something like “Stop to complain, do it!”. Hence, I decided to rebuild the entire application due to some other changes and I seized the opportunity to implement finally a proper expand and collapse with a simple ListView and a custom Adapter.
If you check the video you’ll notice two main parts:
- Normal Animation Time: as you can notice, since the data comes through, the app collapses the shown items and then expands the new ones magically and smoothly in their section (300ms).
- Slow Down Time: in the bookmarks section, as soon as I start to click one of the sections, the animation time slows down to 1200 ms and you are able to notice how the items smoothly collapse fading out and expand fading in.
Let’s have a look at some hints and snippets to manage the expansion and compression of list items.
- ListView: I don’t have to explaing why obviously :)
- CustomAdapter: maps sections to children and provides expand and collapse methods
- ObjectAnimators(fade-in, fade-out, compress height to 1 and expand 1 to height)
- BinarySearch: have you ever had an opportunity to use it? That’s the right time!
- ViewTreeObserver.OnPreDrawListener: at the time of the item creation your view is not yet measured and if you try to obtain the layout params you’ll end up with an NullPointerException.
Mapping Sections to Children:
WHY? because as soon as you fill the data to expand or collapse the adapter snapshot is no longer valid and needs to be refreshed.
Discovering a section:
Arrays.binarySearch(sectionPositions, positionToCheck) < 0 ? TYPE_CHILD : TYPE_SECTION
Collapsing a section:
Expanding a section:
What I’m doing here can be recycled to compress the children that at the moment of the compression are out of the screen but those will appear during the animation.
Hence in your getView(…)
What’s missing now?
If you try to develop a sample ListView with a custom adapter that performs similar stuff you’ll certainly notice that sometimes, especially when you have got lods of children, the new section that you are going to open disappears. This is due to the fact that at the time of the compression, the section that you have just clicked is shown below all the children and in order to fit the place that the will leave empty it will go out of the screen.
This can be fixed with some maths, scrolling your ListView to the position that grants the user to understand where he is. (I’ll definitely do that in one of the next versions of the app).
Thanks for your attention.
If you liked this post why don’t you spend a couple of mins sharing it with friends and colleagues? ;)