Note: I generally blog about subjects I don’t deal with in my day to day life at work. However, the article below mentions some work I have done at Capitaine Train. As a consequence, I think a disclaimer is needed here: I work for Capitaine Train, but the opinions expressed on my blog or anywhere else (Twitter, Google+, etc.), are my own, and have nothing to do with my employer.
In the past few months, I have been working on developing an Android application from the ground up. This app named after the name of the company, Capitaine Train, can be downloaded on the Google Play Store. Capitaine Train - which can literally be translated as “Captain Train” in English - is a 3-year-old startup born from a simple truth: getting train tickets in Europe was a pain in the ass. We, at Capitaine Train, aim to revolutionize the way people travel all around Europe by simplifying the overall train experience. The release of the Android application clearly represented an important step forward in this direction.
Trying to revolutionize the train experience in Europe is not easy. It requires us to achieve a tremendous amount of work: getting to know the various carriers, learning about the document/reservation requirements for each of them, integrating their price/time tables, binding our servers to their systems, etc. From a user point of view all of this is the hidden, but vital, part of the iceberg. Indeed, a travel need or desire starts from a simple search request: From where? To where? When? Who? Although these questions are simple, the search step is extremely important in the booking process. This is where the trip actually begins after all! We designed the Android app keeping this essential idea in mind by simplifying every bit of the process. In this article, I would like to tell you the story behind the implementation of the search experience in the Android app and how we used animations to enrich the user experience.
From web to mobile
When I arrived at Capitaine Train to work on the Android application, I started looking at all of the current ongoing UI-based projects. Some, such as the iOS app, were private but shaping up rapidly. Some others, the web app for instance, were already public and rather well appreciated from our users. My main job, at that time, was to imagine an Android application that could make users feel they were using the best Android app out there to book train ticket. The app had to reflect both the Capitaine Train essence and the Android look ‘n feel. Because the web app was the only public app at this time, I obviously based most of my drafts on top of it. Here is what the search form looks like on capitainetrain.com1:
While the two-panes (search form + options) design works perfectly on desktop we rapidly faced an issue on mobile: we did not have enough space to put both the form and the options panes on the same screen. Because mobile screens are small, we had no other choice than falling-back to a master/detail pattern of some kind. Two well-known and simple options were available to us: the master/detail pattern and the edition dialogs pattern. But we were not satisfied by these patterns. Indeed, dialogs completely breaks the user flow and would have been extremely annoying when filling at least 4 fields in the form (i.e. 4 dialogs). On the other end, opening a fullscreen “option” Activity for each field edition would have lost the user in an extremely complex screen hierarchy and app structure. I seriously thought none of these patterns were effective nor a good fit for the Capitaine Train Android app.
We definitely wanted to replicate the simplicity and obviousness of the desktop search so we finally ended up with a nice approach. Rather than opening a modal screen for each edited form fields, we managed to merge the form pane and the options pane into a single screen. By default, the application displays a search form with all of the available fields. Tapping on a field switches the screen to an “edit mode” where the edited field is visible on top and the rest of the form disappears to reveal the options available on the field. The video below shows an entire search flow use case:
The user flow demonstrated above works very nicely because of the transitions we designed. Indeed, none of this would have been usable without them2. Adding transitions into your application is the best way to enrich user experience by making your users understand the consequences of their actions. As Newton said, to every action there is a reaction: transitions explain what is between two UI states. They also reduce the impression of “stacking screens” when navigating from one screen to another. It makes the user feel the application is made of a single screen where UI elements animate to show and/or dismiss some parts of the app. In other words, transitions break barriers and transform app navigation into a natural flow.
Splitting the transition apart
Transitions are generally quick and barely noticeable. In order to better understand, create and/or reverse-engineer them it is interesting to consider slowing them down. In case you are in control of the application’s code, you can obviously switch all animation durations to some greater values. If you’re not, you can screencast the application and watch the resulting video frame by frame or in slow motion. Fortunately, Android comes with another extremely useful technique: a developer option called “Animator duration scale”. As its name states, this options scales all animation durations system-wide with the chosen scale.
In order to better understand what is happening when transitioning between the search form and the date/time edition mode, let’s use the aforementioned technique. The screencast below shows what the transition looks like at a 10x scale:
Looking at the slowed down video, we can look at the edition mode transition in details. More specifically, you may have noticed the final transition is actually divided into several sub-animations that are played in parallel with the exact same timing properties (duration, interpolator, etc.):
- The focus animation consists of translating towards the top the edited field (i.e. the one the user tapped on) and all fields on top of it. The translation distance is the difference between the focused field’s top and the container’s top. Translating the focused field using this distance results in having the focused field stick to the ActionBar’s bottom.
- The fadeOutToBottom animation consists of dismissing all fields below the “focused field” to the bottom while fading them out away at the same time. The main purpose of this animation is to demonstrate the dismissed fields are not useful in the edition mode we are entering in.
- The slideInToTop animation translates the options/edition panel in. It reveals the edition panel by translating it into the screen and fading it in at the same time.
- The stickTo animation is optional and depends on the edited field. Because the “From/To” and “Depart/Return” are grouped, focusing on “From” or “Depart” requires hiding/overlaying the “To”/“Return” counter parts with a gray band. stickTo is just a y-axis-based translation of the gray band so that its top sticks to the focused field bottom.
The previously described sub-animations composed together creates the search form to edition mode transition. The counter part transition (i.e edition mode to search form) is not described here as it mainly consists on reversing the animations: unfocus, fadeInToTop, slideOutToBottom and unstickFrom.
Back to the code
Prior deep diving into the implementation details, it is important to point out Capitaine Train Android is compatible with Android 4.0+. I personally choose this minimum requirement in order to have full access to the ActionBar features as well as the new property-based animation framework. I obviously could have chosen to target a lower API level but this would have implied multiple code paths (ActionBarCompat VS built-in ActionBar) and the use of support libraries (ActionBarCompat, NineOldAndroids, etc.). I clearly thought we couldn’t match our quality minimum requirements targeting pre-4.0 Android releases. Finally targeting older releases of Android wouldn’t have helped us targeting our rather “tech-familiar” clients. As a side note, at the time of the writing, more than 50% of our install base run the lastest version of Android (4.4) while the official Android dashboard indicates only 8.5%.
Implementing the entire search form flow was a nice challenge. Indeed, we wanted the application to run as greatly as possible on every devices. Thus, we had do deal with a mammoth amount of screen sizes, densities and orientation. While it is generally not a problem at all with Android, it may start to become a small one when you create a fairly complex design. We mainly solved these issues by using a
ScrollView as the root
ViewGroup, using orientation-dependent field height and developing orientation-dependent layouts (for instance the date/time picker looks different in landscape).
From a developer point of view, Capitaine Train Android search form is part of a quite complex
HomeActivity is clearly the first and main screen of the application. It is where 80% of our trip information can be found.
HomeActivity is built on top of a
ViewPager featuring 3
TicketsFragment. Each of these
Fragments is represented by a tab in the UI.
As you can easily understand,
SearchFragment is where most of the code lies.
SearchFragment is made of a fairly complex View hierarchy that can be reduced to the simple layout below:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
SearchFragment is made of two distinct layouts. The first one,
@id/normal_mode_container is the actual search form as you can see it when opening the application while the second one,
@id/edit_mode_container is a simple container the field-dependent options pane will be added to.
Now that we know what the layout actually looks like, let’s finally focus on how the overall transition is performed. Whenever a field is tapped,
SearchFragment adds (or replaces) a new
@id/edit_mode_container, switches the ActionBar to an
ActionMode and starts animating to the “edition mode” using the animations described earlier. The newly added
Fragment depends on the edit mode the user is entering in:
PassengersFragment. Just like we can put
ViewGroup, we put
Fragments inside other
Fragments have been introduced in JellyBean MR2 and are a great way of making sure your code is safely modularized and maintainable3. Although nested
Fragments are API 17+, they have been back-ported back to API 4 and are available through the support library.
Animating search form UI elements is done thanks to the property-based animation framework introduced in Android 3.0. Because we wanted to use a simple and fluent API, we used
ViewPropertyAnimator let’s you run optimized animations of select properties on View objects. However,
ViewPropertyAnimator was not enough in some cases. Indeed, we sometimes had to manually compute the translation distance. For instance the “focus” animation requires the computation of the tapped field top to the root container top distance. If the focused field was a direct child of the container, we could have used the
getTop() method. Unfortunately, this was not always the case. Fortunately, the framework comes with some handy methods that can offset View coordinates into a ancestor coordinate system. The trick consists of retrieving the View drawing rectangle (i.e. in its parent coordinate system) with
View#getDrawingRect(Rect) and translating it into the ancestor coordinate system with
ViewGroup#offsetDescendantRectToMyCoords(View, Rect). This is what the “focus” animation looks like in code (note that you can decide to animate or not - animation-less transitions are used when restoring the UI state after a configuration change):
1 2 3 4 5 6 7 8 9 10 11 12 13
The fadeOutToBottom animation translates the View from half the height of
@id/edit_mode_container. Note that precomputing the “half height” of
@id/edit_mode_container requires the entire
View hierarchy to be laid out. In order to do so, Capitaine Train Android relies on the
OnLayoutChangeListener and its
1 2 3 4 5 6 7 8
Animating the edition panel in is done thanks to the slideInToTop animation:
1 2 3 4 5 6 7
Finally the stickTo animation consists on translating a gray bar according to the focused field bottom:
1 2 3 4 5 6 7 8 9 10 11
I have not explained how Capitaine Train Android relies on
ActionMode to switch the
ActionBar to a contextual
ActionBar. Doing so is fairly straight-forward and you only have to rely on the
ActionBar APIs to do so.
ActionModes are used extensively in
SearchFragment in order to display a title and some optional actions that either describe or are in relationship with the displayed options pane. For instance, when selecting passengers, the
ActionBar displays a “Passengers” title and give the user the opportunity to create new passengers.
Performance improvements tips
When everything was finally working perfectly I started to give a closer look at how smooth animations were. While animations were running almost correctly on a Nexus 5 running KitKat, I wasn’t satisfied at all when I switched to a plain old Galaxy Nexus running Android 4.3. Depending on the device, animations were sometimes always janky, sometimes only lagging once, sometimes not janky at all. Investigating the code, I managed to tweaked the animation a little bit and get an almost jank-free transition.
As described earlier, the search form transitions heavily rely on alpha animations. When switching from a normal mode to the edit mode, the edition pane fades in and some search form field fades out at the same time. Because the system can’t directly draw the alpha animated elements on screen, it uses an offscreen buffer to render the frame and then draws the frame on screen with the alpha value of the current interpolation. The offscreen rendering mechanism is a mandatory (at least 95% of the time, the other 5% are addressed by the
View#hasOverlappingRendering() method) and expensive process.
In order to avoid offscreen rendering on each animation frame, you can enable hardware layers on the animated View hierarchy for the duration of the animation. Enabling hardware layers basically asks the system to render the View hierarchy into an offscreen layer that can be considered as a rasterized bitmap copy of the actual View. With hardware layers on, all subsequent View property changes (translation, alpha, scale, etc.) are forwarded directly to the layer itself rather than invalidating the whole View and redrawing it.
Due to the offscreen rendering phase, hardware layers are generally enabled only during the time frame of the animation. Indeed, keeping hardware layers on when a View invalidates itself, requires the system to redraw its backing layer entirely prior compositing it on screen. To prevent such a performance drop, we created a special
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
LayerEnablingAnimatorListener is simply set as a listener to the
ViewPropertyAnimators described above with by calling
Flattened View hierarchy
The early alpha (internal-only) releases of Capitaine Train was based on a calendar library from Square called TimeSquare. Although TimeSquare was a library that nicely fit our needs, it was also completely screwing our transitions up. Indeed, TimeSquare’s
CalendarPickerView is a
ListView made of several
CalendarGridView (months) containing several
CalendarRowView (weeks) in turn composed of several
CalendarCellView (day). Because of the complex View hierarchy, we sometimes were displaying more than 400
Views at once. Inflating such a huge amount of
Views requires a lot of time we don’t had. The first time the
SuggestionsFragment were displayed inflation was taking around 300ms on my Nexus 5, completely wasting the 333ms-long transition.
The trick here was simply to flatten the View hierarchy. We completely dropped TimeSquare and designed a calendar from scratch. The current
CalendarView implementation is also based on a
ListView but where each
MonthView draws directly on the
Canvas (i.e. a single
View renders a complete month)
SearchFragment allow users to set 5 different search properties. Nested
Fragments are all added to the
onCreate. As discussed earlier, inflating
View hierarchy can slow down the renderer waiting for completion. We minimized this issue by simply reusing
Fragments whenever possible. As a consequence, “From” and “To” both use the same instance of “SuggestionsFragment” and “Depart” and “Return” also both rely on the same instance of
DateTimePickerFragment. In addition to reducing inflation UI thread pauses, it also reduced memory consumption.
Furture improvements tracks
Being kind of a maniac person, I don’t consider the current release public release of Capitaine Train as perfect. I spent a lot of time tweaking the Capitaine Train application prior to the initial release but couldn’t do everything I had in my mind. Lack of time and startup reality just struck me. As an engineer, I simply made the best I could from the various components I had (time, design, code quality, performance, etc.). Here are some of the improvements I still have in mind to make things a little bit smoother:
- The current implementation adds and hides edition
Fragments in the
onCreatemethod. When starting an edition mode, we show the corresponding
Fragment. Internally, the system switches the
VISIBLE. Because all nested
Fragments uses a
ListView, a bunch of
Viewinflation happens the first time a
Fragmentis shown. In fact,
ListViewpopulates itself after it has been laid out. We could force the
ListViewto inflate its items as soon as the field is touched by using
MotionEvent.ACTION_UP. This could save us the amount of time between these two events (around 40 to 60ms).
SearchFragmentmake an extensive use of
ViewPropertyAnimator. When transitioning to the edition mode, a bunch of
ViewPropertyAnimatorare started and run in parallel. We could prevent the animation system from managing all animations independently and use a single
ValueAnimatorof our own.
With the introduction of the new property-based animation framework and
Fragments in Android 3.0, the framework provides developers with all the necessary tools to create wonderful and meaningful UIs while still keeping a maintainable and modularized code. Animating
Fragments is generally a single
ViewPropertyHolder API call away and may drastically improve the way users understand your application. Designing an application is not only about creating a nice static design. It is also about moving graphical elements in a way it is meaningful to users. Transitions both give life to an application and enrich user experience.
1: Feel free to register and have fun with the Capitaine Train web application. Just like the Android app, it is available in English, French, German and Italian.
2: The best way to understand the importance of transitions is to disable them temporarily. You can do so by disabling animations system-wide in the developer settings. Open the Settings application, go to “Developer options” and set the “Animator duration scale” to “Animation off”. Note that it may be required to restart the application so that the setting takes effect.
3: Since their introduction, Fragments have been overwhelmingly used. They also have been overwhelmingly criticized for their complexity. Their lifecycle is extremely complex, they are quite verbose, they have several “modes” (created via code or via XML inflation), etc. Nested Fragments have been even more criticized. The purpose of this article is not to tell you how to develop your own application. Fragments and nested Fragments are complex indeed but once you control and master them, you can start enjoying them. Using them is a great way to create independent portion of code inside your application.