Oh, no! A new animation framework for Android, again? We have quite a few already, do we really need a new one? First, let's see the previous approaches we used to create animations in our applications!

Animation solutions so far

ObjectAnimator

This subclass of ValueAnimator provides support for animating properties on target objects. We can define it in XML files:

<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"  
	android:duration="1000"  
	android:propertyName="y"  
	android:repeatCount="1"  
	android:repeatMode="reverse"  
	android:valueTo="200"  
	android:valueType="floatType" />

Or we can use it from code:

ObjectAnimator.ofFloat(view, "translationX", 100f).apply {  
	duration = 2000  
	start()  
}

ObjectAnimator uses reflection to set the property given by the name as String. Or we can avoid reflection by using the inbuilt wrapper property instead of hardcoding the property name.

ObjectAnimator.ofFloat(view, View.TRANSLATION_X, 100f).apply {  
	duration = 2000  
	start()  
}

This way the property value changes using the setter directly.

The biggest problem besides reflection is that you need a new ObjectAnimator for every view you want to animate, because it doesn't support simultaneous changes of several objects. However, there's definitely a pro for ObjectAnimator too: it can animate a property of any type.

The Animation class and its descendants

The biggest downside here is that you can animate only one property at once. You can use RotateAnimation, AlphaAnimation, ScaleAnimation, etc., to animate basic properties. And still, you need multiple instances of the Animation class for each property to play an AnimationSet. The other thing is, that it can be used only on View descendants.

ViewPropertyAnimator

Finally, we can animate multiple properties at once! :) ViewPropertyAnimator was created to replace ObjectAnimator, and it can modify the defined properties simultaneously. Furthermore, it's more effective due to optimized method calls. Last but not least, the syntax is much cleaner and readable.

This is how we use ObjectAnimator to animate multiple properties, making use of an AnimatorSet:

val animX = ObjectAnimator.ofFloat(view, "x", 50f)  
val animY = ObjectAnimator.ofFloat(view, "y", 100f)  
AnimatorSet().apply {  
    playTogether(animX, animY)  
    start()  
}

And the same using the ViewPropertyAnimator framework:

view.animate().x(50f).y(100f)

ValueAnimator

ValueAnimator allows us to animate any number of objects of any type at the same time using one instance of it.

ValueAnimator.ofFloat(0f, 3f).apply {  
    duration = 3000  
    addUpdateListener { animation -> 
        view.translationX = animation.animatedValue as Float 
    }  
    repeatCount = 5  
}.start()

So, these are the most commonly used animation solutions, which are still good, but for different cases.

Let's jump into that new one.
♪Here come the Men in Black...♪ Here comes the MotionLayout.

MotionLayout

MotionLayout was introduced in 2018 at Google I/O. At the time of writing this article, it's on version 2.0.0 Beta 3.

dependencies {  
	implementation 'androidx.constraintlayout:constraintlayout:2.0.0-beta3'  
}

As we can see from the dependency, MotionLayout is a kind of ConstraintLayout, that's why it's included in the ConstraintLayout library. More precisely, it's a descendant of ConstraintLayout, that extends the parent's functionality with animation capability.

Layout structure

To get started, in the screen's layout XML file we define a MotionLayout, and we add all the necessary views to the layout. In this layout file, we constrain only those views which are not animated. The rest - which are animated - are constrained in the MotionScene file. That's where the magic (at least the definition of the animations) happens, and we'll look at it in a moment.

That's why it's important to link the MotionScene to the MotionLayout by setting the layoutDescription attribute of the MotionLayout.

<androidx.constraintlayout.motion.widget.MotionLayout
	xmlns:android="http://schemas.android.com/apk/res/android"  
	xmlns:app="http://schemas.android.com/apk/res-auto"  
	android:layout_width="match_parent"  
	android:layout_height="match_parent"  
	app:layoutDescription="@xml/motion_scene"
	app:motionDebug="SHOW_ALL">

There's also a debug option here (the motionDebug attribute) to help visualize the paths of the animated views and the progress of the animation.

Motion debug

MotionScene

The MotionScene has three important parts:

  • two ConstraintSet instances which define the start and the end state of the animation,
  • the Transition that defines the transition (surprise, surprise) between the start and the end state.

ConstraintSet

This class allows you to define a set of constraints programmatically to be used with ConstraintLayout. If we define constraints in the layout file as well as in the MotionScene, the latter overrides the former.

We have two options to define the ConstraintSet descriptors:

  • If the scene has a lot of views to animate, it can be clearer to define the constraints in separate layout files. Just create the layout as you would using a ConstraintLayout as the root layout.
  • Alternatively, just define constraints inside MotionScene, in ConstraintSets.
<ConstraintSet android:id="@+id/start">  
	<Constraint  
		android:id="@id/imageView"  
		android:layout_width="80dp"  
		android:layout_height="80dp"  
		app:layout_constraintBottom_toBottomOf="parent"  
		app:layout_constraintStart_toStartOf="parent" />  
</ConstraintSet>

<ConstraintSet android:id="@+id/end">  
	<Constraint  
		android:id="@id/imageView"  
		android:layout_width="80dp"  
		android:layout_height="80dp"  
		app:layout_constraintBottom_toBottomOf="parent"  
		app:layout_constraintEnd_toEndOf="parent" />  
</ConstraintSet>

Here we can define some basic attributes beside constraints:

  • width, height,
  • visibility, alpha, elevation, rotation, scale, translation
    (At the time of writing this article, based on my experience, the elevation attribute isn't working.)

If we need other attributes apart from these basic ones, we have to define a CustomAttribute like this:

<Constraint
	android:id="@+id/button"
	android:layout_width="64dp"
	android:layout_height="64dp"
	android:layout_marginStart="8dp"
	app:layout_constraintBottom_toBottomOf="parent"
	app:layout_constraintStart_toStartOf="parent"
	app:layout_constraintTop_toTopOf="parent">
		<CustomAttribute
			app:attributeName="backgroundColor"
			app:customColorValue="#D81B60"/>
</Constraint>

Transition

The Transition in the MotionScene will define how we get from one state to the other. There are two main attributes here:

app:constraintSetStart="@+id/start"
app:constraintSetEnd="@+id/end"  

These define the start and the end state of the animation. If we chose to define the constraints in layout files, we need to add the IDs of the layout files here, otherwise, the IDs of the ConstraintSets. There are also other attributes for setting the duration and the interpolator of the transition.

Inside the Transition tag we can define multiple elements to customize the transition's behavior (so, to define the animated view's movement).

<Transition  
    app:constraintSetEnd="@+id/end"  
    app:constraintSetStart="@+id/start"  
    app:duration="3000">

</Transition>

First of all, we can define two kinds of event handlers in case we want to control the animation.

  • OnClick: The animation will be played as the user clicks on the target view. A clickAction can be defined as well to modify the behaviour of the click event.
<Transition  
    app:constraintSetEnd="@+id/end"  
    app:constraintSetStart="@+id/start"  
    app:duration="3000">
    
    <OnClick  
        app:clickAction="toggle"  
        app:targetId="@id/imageView" />
</Transition>
  • OnSwipe: This event handler catches drag events on the defined view. We can set the following attributes:
    • dragDirection: the progress direction of the drag, for example, app:dragDirection="dragRight" means that progress increases as you drag to the right,
    • touchAnchorId: the ID of the view we want to drag,
    • onTouchUp: what happens with the animation when the user releases the dragged view.
<Transition  
    app:constraintSetEnd="@+id/end"  
    app:constraintSetStart="@+id/start"  
    app:duration="3000">
    
    <OnSwipe  
      app:dragDirection="dragRight"  
      app:onTouchUp="stop"  
      app:touchAnchorId="@id/imageView" />
</Transition>

To customize the paths or attributes of the animated views, we can define KeyPositions, KeyAttributes, and CustomAttributes inside KeyFrameSet tag.

With KeyPosition we can define a specific point on the screen, and the percentage when the animation has to reach that point. This point can be defined in three coordinate systems:

  • parentRelative
  • pathRelative
  • deltaRelative

The official documentation explains them in detail.

Besides path points, we can define the attributes which will be animated. For basic attributes we use the KeyAttribute tag, and set a framePosition, a motionTarget, and the animated attribute. If we need some other view parameter to animate, we can insert a CustomAttribute tag inside KeyAttribute like this:

<KeyAttribute  
	app:framePosition="25"  
	app:motionTarget="@+id/imageView">

    <CustomAttribute  
		app:attributeName="BackgroundColor"  
		app:customColorValue="@color/brand_alpha" />  
</KeyAttribute>

We have to specify the attribute's name as a string value and depending on the type of the attribute, specify the corresponding customTypeValue parameter with the value as seen above.

Motion Editor

Starting in Android Studio 4.0 Canary 1, we have a new tool to create and edit MotionLayout on a graphical user interface. This is basically the layout designer view for MotionLayout.

Motion Editor

On the left side, there's an editable preview window, the same that we know from the ConstraintLayout editor. To its right, the top half of the screen is for the visualization of the MotionLayout, showing the start and the end states we've defined (ConstraintSets) and the transition between them. Depending on what you select from these components (the selected item will be highlighted), the content will change below.

When...

  • the MotionLayout is selected, we can see the views already added to the layout and their attributes.
  • one of the ConstraintSets is selected, we can check if the views are constrainted, or set the necessary constraints and set custom attributes.
  • the transition is selected, a timeline appears, where we can define the KeyFrameSet's child tags (KeyPosition, KeyAttribute, etc.)
    Transition editor

Finally, we can preview the defined motion frame by frame or played back and forth.
enter image description here

Conclusion

We've taken a look at the main features of MotionLayout, the new animation framework on Android. It's the best solution for complex motion handling. In addition to describing transitions between layouts, MotionLayout lets you animate all layout properties simultaneously. The best part of it is that it inherently supports seekable transitions. This means that you can instantly show any point within the transition based on some condition, such as touch input.

So let's make our applications more spectacular with some awesome interactive animation!

*All images in the MotionEditor section are from developer.android.com