How to adopt ConstraintLayout animations

Make animating complex view hierarchies easier

At Beat we strive to achieve a great level of user experience. Being able to perform complex animations easily led us to widespread adoption of the ConstraintLayout. Implementing complicated and efficient layouts became much easier. As an added benefit to using ConstraintLayout to structure our views we got the ease of performing animations (more specifically, scene transitions) to multiple elements simultaneously.

Why not use separate layout files for this?

While it is possible to use two (or more) separate xml layouts representing the various states of the view hierarchy that transitions can be performed across, I find it preferable to use just one layout and perform all the transition animations therein for the following reasons:

  • It’s easier to maintain a single file rather than two.
  • When using different layouts for other configurations (like landscape, swdp600 and so on) the amount of effort required for every change can get out of hand easily.


Having said that, using a temporary layout to observe the state (position, visibility, size) of the views after any animation applied has finished, can be often helpful, especially when dealing with complex view hierarchies.

Implementation

Consider the animation illustrated below. Albeit simple, it serves well to showcase the transition (back and forth) between the two states. The code shown here can be found on GitHub.

The key to animating complex layout changes are the methods found in ConstraintSet class. We’ll take a closer look to those that we’ll be using more often.

clear(int viewId, int anchor)

Used to remove an already existing constraint from a view. A clear(int viewId) exists there as well which removes all constraints (including margins).

connect(int startID, int startSide, int endID, int endSide)

We use this method to apply a new constraint. When applying constraints in relation to the parent view, we can use ConstraintSet.PARENT_ID instead of the actual id of the parent. A word of caution though, try to avoid setting ConstraintSet.LEFT (this goes for the RIGHT as well) and prefer ConstraintSet.START instead, as on some occasions constraints that have been set as ConstraintSet.LEFT get ignored.

For example

set.connect(title.id, ConstraintSet.TOP, ConstraintSet.PARENT_ID, ConstraintSet.TOP)

Simply constraints the top of the title view to the parent’s top.

setVisibility(int viewId, int visibility)

Lets us change the visibility property of a view and the visibility argument can be one of the following:

  • ConstraintSet.VISIBLE
  • ConstraintSet.INVISIBLE
  • ConstraintSet.GONE


Combining the knowledge above and determining the state of our view hierarchy after the animation finishes, the final implementation can be like this:

private fun playExpandAnimation() {
    TransitionManager.beginDelayedTransition(container)
    val set = ConstraintSet()
    set.clone(container)

    // Image transition
    // The image is already constraint to the parent on 
    // its END and TOP
    set.clear(image.id, ConstraintSet.BOTTOM)
    set.clear(image.id, ConstraintSet.START)

    // Title transition
    set.clear(title.id, ConstraintSet.BOTTOM)
    set.clear(title.id, ConstraintSet.END)
    set.connect(title.id, ConstraintSet.START,
            ConstraintSet.PARENT_ID, ConstraintSet.START)
    set.connect(title.id, ConstraintSet.TOP,
            ConstraintSet.PARENT_ID, ConstraintSet.TOP)
    // Make the rest of the views visible
    set.setVisibility(subtitle.id, ConstraintSet.VISIBLE)
    set.setVisibility(divider.id, ConstraintSet.VISIBLE)
    set.setVisibility(textDescr.id, ConstraintSet.VISIBLE)
    set.setVisibility(author.id, ConstraintSet.VISIBLE)

    set.applyTo(container)
}

 

The above snippet animates our views to a new position (the first part of the animation depicted). By applying the same technique we can perform the reverse animation. I leave this as an exercise for the reader (or you can check the GitHub repository).

Bonus section: Mind the AutoTransition

When we want to tweak elements of our transition (such as the duration) we have to supply a transition object to our TransitionManager.beginDelayedTransition(ViewGroup, Transition)invocation. A jack-of-all-trades transition is the AutoTransition (it’s used by default if we don’t supply one ourselves), which performs Fade out, ChangeBounds and Fade in animations in sequential order. The not so obvious element is that when setting the duration of this transition, we are in fact setting the duration of each one of the three distinct sets of animations. Of course, we can supply our entirely custom transitions. For more information you can check the TransitionSet documentation or other articles which cover this in greater depth.

By this point I hope that I’ve shed some light to the somewhat poorly lit -yet immensely powerful- aspect of ConstraintSet transitions. Even though the example is simple enough, it can be easily  build upon and expanded to suit the needs of production grade animations. The benefits of using this framework -for heavy lifting animations- become more apparent when refactoring our existing animations away from using the Property Animation framework (where applicable of course).

Leave a reply

Your email address will not be published.
Required fields are marked *

DOWNLOAD THE APP
BECOME A BEAT DRIVER

BECOME A
DRIVER TODAY!

Fill in your mobile number to receive an SMS with the download link of the app and get started. Simple as that.

I have read and accept the Terms and Conditions.