Transition X — Declarative Kotlin DSL for choreographing Android Transitions

Material Design’s announcement at Google IO 2014 redefined Android UX. New emphasis were given to motion and the guidelines encouraged using motion as a tool to be expressive and adding character to your app.

From Material.io:

Motion provides meaning Motion focuses attention and maintains continuity, through subtle feedback and coherent transitions. As elements appear on screen, they transform and reorganize the environment, with interactions generating new transformations.

On the technical side of things, for the first time Android Lollipop used a separate thread (RenderThread) to handle animations even if there are delays in main thread. Introduction of RenderThread allowed things like Circular Reveal Animation, AnimatedVectorDrawables, Ripple Feedback without noticeable slowdowns.

Transitions Framework

Before Android Lollipop, Kitkat introduced Scenes & Transitions API which were an abstraction on top of Animations to reduce effort required for simple animations. Basically it was possible to give a start and end layout and the framework would animate the difference.

https://developer.android.com/training/transitions/

https://developer.android.com/training/transitions/

In order to let the framework animate simple changes, all we must do is to call TransitionManager.beginDelayedTransition(veiwGroup) before changing the layout and the framework would take care of animating the difference. I recommend reading this article by Andrey Kulikov, which covers different types of animations you could do with the framework.

Transition X — Better transitions with Kotlin

What Kotlin has to do with transitions and Material Motion? Let’s find out.

Material Choreography provides certain guidelines on how these animations should play; namely ordering, easing, duration and type.

Choreography

Complex layout changes use shared transformation to create smooth transitions from one layout to the next. Elements are grouped together and transform as a single unit, rather than animating independently, avoiding multiple transformations that overlap and compete for attention.

The transition framework is already capable of this. But the boilerplate required for choreographing detailed transition were immense. Let’s take an example. Android lets us create TransitionSet instance by using XML as shown below.

Android studio has auto completion for common properties.

Android studio has auto completion for common properties.

It is no argument that XML takes lot of words to convey structure and that grows linearly when you want to choreograph complex transitions. Later in the code you would use TransitionInflater to get an instance then call TransitionManager.beginDelayedTransition. This has high chance of getting repeated all over the place.

Using XML also breaks type safety when working with custom transition instances.

I set to solve this problem using Kotlin by taking advantage of lamdas with receivers feature and reducing much of the boilerplate. To achieve the same transition, Transition X code would look like below:

Transition X provides a prepareTransition extension method on ViewGroup classes which can be used to construct complex transition instances. After prepareTransition you typically apply the layout changes for kick starting the transition. Scheduling call to TransitionManager is handled automatically, you only need to concentrate on declaring different transitions and their characteristics and the library handles the rest.

Transition X library features

  • Declarative — Easily declare variety of transitions and specify their properties. No math required. Transition X has variety of methods for inbuilt transitions from support library like ChangeBounds, ChangeTransform, ChangeClipBounds etc.

  • Rich tooling support — Transition X inherits all of Kotlin’s powerful IDE tooling integrations and provides you with auto complete for all available transition and related properties.

IDE autocomplete for all available transitions and properties

IDE autocomplete for all available transitions and properties

  • Extensible — Have a custom transition? Transition X can work with them too, thanks to Kotlin’s reified inline types.
  • Natural syntax — The syntax is simple to understand and reduces cognitive load needed to follow the animation choreography. Lifecycle methods like onEnd, onStart provide greater clarity and control over how rest of the code reacts to transition.

The remainder of the article will explore some common transitions and demonstrate how they are accomplished with Transition X.

Examples

Snackbar transition

Snackbar + Fab animation is one of the first things that caught our eye when material design principles were introduced. The real implementation however used CoordinatorLayout and bunch of math to mimic the moving the behavior. With ConstraintLayout and Transition X, it becomes simple as ever.

Here the FAB has a bottom constraint attached to the SnackbarMessage layout. When the SnackbarMessage becomes visible, the FAB moves to the top because of the constraints.

Breaking down the code above:

  • moveResize: Internally adds a ChangeBounds transition which animates View’s bounds. By using + operator overload, we can ask this transition to only affect fab and nothing else.

  • slide: Internally adds a Slide animation which triggers when view visibility changes. Like fab this transition is exclusively set to affect snackbarMessage alone.

  • decelerateEasing: Adds a LinearOutSlowInInterpolator on this transition.

Advanced Choreography:

At most times, it is not beneficial to simply move resize items on screen without context. Material Choreography recommends greater control over which elements move/fade and how. In the following example, there are several things happening.

  1. The card size changes to accommodate larger images. As per material choreography items resizing should have FastOutSlowInInterpolator.

  2. Notice the Calm your mind... text does not simply move to the bottom, but instead fades away and emerges naturally under expanded images.

  3. Images’ scale type and bounds changes.

  4. Collapse/Expand text transition waits for the resize animation to end and does not change together with the resize animation.

The code for the above transition:

Notice that it has become considerably easier to pull off this transition with no math or calculations, just clever declarations with different targets and the TransitionManager takes care of animating the rest.

Why not ObjectAnimators?

Transitions internally use object animators and they simply are an abstraction over Animators with start and end values. It is possible to use Animators directly but there are certain concerns.

  • Performance: Object animators are one of the powerful ways to handle animations, but the transition framework has some important optimizations which make them desirable. Namely the ChangeBounds transition. ChangeBounds internally uses a hidden private API called ViewGroup.suppressLayout which temporarily pauses all invalidate() calls to the layout which means less pressure on the CPU to constantly remeasure moving items on the screen.

  • Increased complexity: Object animators requires calculation and knowing end value before layout changes whereas Transition framework automatically gets the information after the layout has changed.

In this example, the ImageView’s scale and gravity alone is changed. By using moveResize and ArcMotion it is possible perform this hero transition without needing to calculate where the ImageView would be when thelayoutchanges are applied.

frameLayout.prepareTransition {
    moveResize {
        pathMotion = ArcMotion()
        +userIconView
    }
}

With ObjectAnimator however, we would need to calculate the end location of the ImageView beforehand to run this transition.

Gotchas

Like any framework, Transition does have some limitations. All of them can considerably overcome using Transition X DSL. For example, to exclude all RecyclerView from participating in the transition it is sufficient to call exclude<RecyclerView>() and all the RVs inside current layout will not participate in the transition.

Conclusion

I strongly believe Transition X style DSL reduces complexity and simplifies workflow when working with transitions. The aim of this library is to increase exposure to the Transition framework and unlock their potential using Kotlin’s language features. The entire library and a sample app with basic transitions are available in this repo.

If you have any feedback on the project, I would be happy to hear from you in the comments below or on Twitter or on Google+.


Comments