Beautiful animations using Android ConstraintLayout

Jin Cao
Robinhood
Published in
6 min readMay 22, 2017

ConstraintLayout seems to be the hot new thing nowadays. Flattening your view hierarchy, improving performance, supporting arbitrary bounding rules — it promises to fix all of the shortcomings of some previous layout files (*cough* I’m looking at you RelativeLayout *cough*) and more. But there is one other benefit of ConstraintLayout that most people are unaware of and the official documentation curiously doesn’t mention anything about: performing cool animations on your ConstraintLayout views with very little code.

How?

I’m going to assume that you know the basics of ConstraintLayout (e.g. you know what is app:layout_constraintLeft_toLeftOf and its relatives). Most of the tutorials on ConstraintLayout focus on using the newly improved Android Studio layout design panel where you can drag/drop/visualize the various constraints. For the purposes of animations, it’s also good to take a peek at the generated XML to get a better understanding of the various constraint attributes so you know how to manipulate them.

In it’s simplest form, ConstraintLayout gives you the ability to animate between two sets of constraints through the TransitionManager (available on API 19+ or via the support library). Rather than explaining, let’s just walk through an example.

Simple example

This is the end result that we want to achieve

Let’s start with the initial XML layout of this activity.

<!-- activity_main.xml -->
<android.support.constraint.ConstraintLayout ...>

<ImageView
android:id="@+id/image"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
... />

<Button ... />

</android.support.constraint.ConstraintLayout>

So far so good. This is just a basic XML file that defines an ImageView on top of the screen that matches the width of the screen (remember that ConstraintLayout does not support match_parent). Now, let’s define an alternate XML layout with one additional constraint.

<!-- activity_main_alt.xml -->
<android.support.constraint.ConstraintLayout ...>

<ImageView
android:id="@+id/image"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"

... />

<Button ... />

</android.support.constraint.ConstraintLayout>

The only difference here is that the new XML layout sets the height to match the parent’s height. This results in the ImageView centered vertically within the parent.

Now, if I want to animate between the two different sets of constraints (one that doesn’t center the ImageView vertically and one that does), I just have to add the following code to my Activity.

Note: this is written in Kotlin. If you are unfamiliar with Kotlin, I would highly recommend on getting started with it as it provides many benefits of a modern programming language while being completely backward compatible with your Java code. Oh, and it’s now an officially supported language of Android!

override fun onCreate(savedInstanceState: Bundle?) {
...
val constraintSet1 = ConstraintSet()
constraintSet1.clone(constraintLayout)
val constraintSet2 = ConstraintSet()
constraintSet2.clone(this, R.layout.activity_main_alt)

var changed = false
findViewById(R.id.button).setOnClickListener {
TransitionManager.beginDelayedTransition(constraintLayout)
val constraint = if (changed) constraintSet1 else constraintSet2
constraint.applyTo(constraintLayout)

changed = !changed
}
}

We set up constraintSet1 and constraintSet2 to correspond to the non-vertically-centered layout and the vertically-centered layout. The code in bold shows the actual animation logic. First we tell the TransitionManager to begin a delayed transition on the ConstraintLayout. Then we apply a different set of constraints on the ConstraintLayout. The TransitionManager will automatically perform an animation to display the change in constraints.

But duplicating XML layout?

I know what you guys are thinking. This approach requires duplicating the layout file to represent the constraint changes. No one likes duplicated code (remember the DRY principle?).

This actually isn’t as bad as you think. If you are defining the alternate XML file specifically for transition purposes, you can actually omit all of the non-layout styling attributes (e.g. textSize). The ConstraintSet simply captures all of the layout based constraints on each view and discard other attributes. This way, you won’t actually have to maintain a consistent styling across the two files (e.g. if you change the textSize on the original XML, you don’t have to do this in the alternate XML).

If you really want to avoid duplicating XML code, you can also dictate the constraint changes dynamically in code by specifying only the affected elements.

Let’s take the above example and see how we can achieve it with only one layout XML.

override fun onCreate(savedInstanceState: Bundle?) {
...
val constraintSet1 = ConstraintSet()
constraintSet1.clone(constraintLayout)
val constraintSet2 = ConstraintSet()
constraintSet2.clone(constraintLayout)
constraintSet2.centerVertically(R.id.image, 0)


var changed = false
findViewById(R.id.button).setOnClickListener {
TransitionManager.beginDelayedTransition(constraintLayout)
val constraint = if (changed) constraintSet1 else constraintSet2
constraint.applyTo(constraintLayout)
changed = !changed
}
}

In the above code, we are programmatically changing the constraint attribute for constraintSet2 and then performing the animation. This way, we keep all of our other constraints intact and avoid duplicating code.

But I can already achieve this using the transition framework!

You are right in that none of this is new. You can already achieve this with the transition framework or with attributes such as animateLayoutChanges. However, this becomes powerful when the animation you are trying to implement can be nicely specified using specific constraints (e.g. chained elements, guidelines, etc.) that would otherwise take a lot of work to achieve.

Another useful scenario of this is when you are trying to animate many elements. Let’s take a look at this animation.

Robinhood’s create order animation using ConstraintLayout

This is the Robinhood’s (Android, iOS) create order flow animation. The current implementation of this involves manually animating every single element on the page (the card view, the custom keypad, the FAB, etc.). The code is a little gross to read especially considering that we have to manually encode the forward and backward animations.

I created a sample app that uses ConstraintLayout for this animation instead (shown in the GIF). This implementation is way simpler. By specifying an alternate XML layout file with the updated constraints, the animation framework will just animate everything for us. The code that handles this animation in the UI went from ~250 lines to ~30 lines.

There’s more!

Remember this line that we use to start the ConstraintLayout animation?

TransitionManager.beginDelayedTransition(constraintLayout)

You can provide it a custom Transition (or use a built-in transition class) for an optional second argument that allows you to customize the animation even further! For example, I can easily change the animation duration this way.

val transition = AutoTransition()
transition.duration = 1000
TransitionManager.beginDelayedTransition(
constraintLayout, transition)

Minor caveats

After playing around with ConstraintLayout animations, I found a few caveats that you should consider when implementing your own animation.

  • ConstraintLayout will only perform animation on its direct children since it only knows when you change layout parameters and constraints on the children that it handles. This means that it won’t handle nested ViewGroups very nicely. In the above example, the text inside the CardView still needs to be manually animated in code because that text is not handled by the outer ConstraintLayout. This perhaps can be solved with a nested ConstraintLayout and animating both of them, but I didn’t try out that approach.
  • ConstraintLayout only animates layout related changes. You can’t encode other changes in an alternate XML file (e.g. elevation change, text change) and expect the framework to do everything for you. ConstraintSet.clone(...) only copies over the layout/constraint changes and discards everything else.
  • As of version constraint-layout:1.0.2, if you dynamically change a layout based attribute for an element inside ConstraintLayout (e.g. translationY), the animation won’t take the updated attribute into account. This means that when the animation is run, the attribute will instantly revert back to the original value, and then animate to the new value.

Takeaway

When you are implementing animations on a page that uses ConstraintLayout, remember that the transition framework can perform many basic layout change animations for you. This allows you to condense your UI / animation logic in your Activity / Fragment and consolidate them in XML. It also makes for a more readable animation logic (no one likes to read lines and lines of programmatically generated animations).

Oh, and we are hiring!

--

--

Jin Cao
Robinhood

Engineering manager, Android at Robinhood, occasionally blogs about Android stuff. github.com/jinatonic