Motion Layout – Animations made easy

According to the Maslow’s Hierarchy of Needs, after our physical and social needs are fulfilled, we feel the need to be proud of something, something that we achieved or accomplished. This need can be fulfilled by creating something of value to others. And value can be defined in many different ways. One of its definition can be extracted from aesthetics; the sense of beauty, symmetry, harmony. If a person can create something that others find beautiful, he/she just made something of value. He/she will be praised by others, fulfilling his/her need of pride.

In software development, aesthetics play an important role. User experience, along with soothing colors and balanced animations, can make a software more natural, more interactive and friendlier than its competitors, driving more users to it and ultimately increasing revenue. The case of Android is not different than this.

Getting Started

Working on animations has always been a challenging task in Android. There are multiple options available to work with, classes like Animation, AnimationUtils, AnimationSet, R.anim.fade_in to name a few, and is difficult for a learner to choose from. And there are many things to understand in order to make sense of what is going on. For instance, look the code below:

val textView = findViewById(R.id.textView)
val animation = TranslateAnimation(0.0F, 100.0F, 0.0F, 100.0F)
animation.duration = 1000
textView.startAnimation(animation)

At first, it looks very simple. We’re making an animation object that is describing how the animation will proceed, and we’re setting that animation in a text view. The text view is supposed to animate from point (0,0) to point (100,100) in one second. But if you were to execute this code, after animation is completed, the text view will jump back to its original position. The reason behind this is that in Android, translating an item doesn’t change the actual position of that item. So after animation is ended, item returns back to where it was.

Motion Layout

Point being, animating views in Android is not easy. Or should I say was not easy. Because Android has come up with something that has made animating views effortless. Motion Layout, introduced at Google I/O event in 2019, is an extension of Constraint Layout so it comes bundled inside with Constraint Layout 2.0. With motion layouts, extreme animations can be made using only XML. No Java or Kotlin code is required. One enthusiast made this using motion layout:

But don’t worry, I will keep things simple to better understand how motion layouts work and how can we make complicated animations using just the basic tools. In this article, I will only cover the basics of motion layout and how to make simple animation using it.

First thing that you need to understand is that MotionLayout extends from ConstraintLayout. In order to use MotionLayout, you must implement following dependency in your gradle file:

implementation ‘androidx.constraintlayout:constraintlayout:2.0.0-beta4’

If you don’t know what ConstraintLayout is or how it works, I suggest you learn it first. If you already are familiar with it, then the following layout code will make sense:

<androidx.constraintlayout.widget.ConstraintLayout 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"> <TextView android:id="@+id/textView1" android:layout_width="300dp" android:layout_height="300dp" android:background="#3F51B5" android:gravity="center_vertical" android:padding="10dp" android:text="@string/textView1Str" android:textColor="@android:color/white" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> </androidx.constraintlayout.widget.ConstraintLayout>

This is our activity_main.xml file and it will create a text view and will keep it in center of the screen like this:

Let’s make the text view a little pretty:

<TextView android:id="@+id/textView1" android:layout_width="300dp" android:layout_height="300dp" android:background="#3F51B5" android:gravity="center_vertical" android:padding="10dp" android:text="@string/textView1Str" android:textColor="@android:color/white" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" />

We’ve added padding, set a background and text color to it, now it looks much better:

As we’ve established earlier, that MotionLayout is extension of ConstraintLayout, replacing ConstraintLayout with MotionLayout should work, courtesy of polymorphism. So let’s try that:

<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"> <TextView android:id="@+id/textView1" android:layout_width="300dp" android:layout_height="300dp" android:background="#3F51B5" android:gravity="center_vertical" android:padding="10dp" android:text="@string/textView1Str" android:textColor="@android:color/white" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> </androidx.constraintlayout.motion.widget.MotionLayout>

But if you run the application now, it will give an error. Not because polymorphism failed but because MotionLayout has one prerequisite. Every MotionLayout must have a MotionScene file associated with it. MotionScene file is an XML file and it contains the animations’ code. Create a folder in res directory of your Android project and create an XML file inside it. For our demo, we will be creating a folder by the name of xml and MotionScene file by the name of scene_1.xml.

Now that we’ve created our file, here is how we associate it with a 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/scene_1"> <TextView android:id="@+id/textView1" android:layout_width="300dp" android:layout_height="300dp" android:background="#3F51B5" android:gravity="center_vertical" android:padding="10dp" android:text="@string/textView1Str" android:textColor="@android:color/white" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> </androidx.constraintlayout.motion.widget.MotionLayout>

By mentioning scene_1 in app:layoutDescription=”@xml/scene_1″ in MotionLayout tag, we’re specifying this MotionLayout a MotionScene. Now our motion layout is all ready for animations. Let’s dive into scene_1.xml file and write some animations.

MotionScene

MotionScene is where your animation code lives. In XML terms, MotionScene is a tag, with its children Transition, ConstraintSet and StateSet. We will be focusing on Transition and ConstraintSet because these two are responsible for animations. MotionScene can contain multiple Transition tags and ConstraintSet tags.

ConstraintSet holds the information of the resting state of view. By resting state, I mean position, rotation angle, alpha etc., the building blocks of animations, at a time when either animation is not yet started or is finished. Each ConstraintSet expresses the resting state of the views mentioned inside it. Following code describes how ConstraintSet holds that information:

<ConstraintSet android:id="@+id/end"> <Constraint android:id="@+id/textView1" android:layout_width="300dp" android:layout_height="300dp" android:rotation="-90" android:translationX="-280dp" motion:layout_constraintBottom_toBottomOf="parent" motion:layout_constraintStart_toStartOf="parent" motion:layout_constraintTop_toTopOf="parent" /> </ConstraintSet>

The id with ConstraintSet gives it a name, something we can use to call it later on. How this id will be used is coming up shortly.

Now a question arises that what about the state of views mentioned in the layout file, the file actually hosting these views. Its answer would be that, if there is a view inside MotionLayout whose name is also mentioned in MotionScene inside ConstraintSet then its state information in layout file will be overridden with the state information in MotionScene file. For example, if width and height of textView1 is set to 200 in activity_main.xml but they are set to 300 in scene_1.xml then they will be of 300 width and height. The information inside scene_1.xml overrides the information in activity_main.xml.

Now, to explain that id, I’ll first explain the other child of MotionScene tag, Transition.  Transition holds the information of start and end state of a view. Basically, it holds two ConstraintSets in it. Following code will help you understand:

<Transition motion:constraintSetEnd="@+id/end" motion:constraintSetStart="@+id/start"> </Transition>

Whereas end and start are two different ConstraintSets. These two ConstraintSets are following:

start :

<ConstraintSet android:id="@+id/start"> <Constraint android:id="@+id/textView1" android:layout_width="300dp" android:layout_height="300dp" android:rotation="0" motion:layout_constraintBottom_toBottomOf="parent" motion:layout_constraintEnd_toEndOf="parent" motion:layout_constraintStart_toStartOf="parent" motion:layout_constraintTop_toTopOf="parent" /> </ConstraintSet>

end :

<ConstraintSet android:id="@+id/end"> <Constraint android:id="@+id/textView1" android:layout_width="300dp" android:layout_height="300dp" android:rotation="-90" android:translationX="-280dp" motion:layout_constraintBottom_toBottomOf="parent" motion:layout_constraintStart_toStartOf="parent" motion:layout_constraintTop_toTopOf="parent" /> </ConstraintSet>

Now we know why id is used in ConstraintSet. Now we have two resting states of textView1 and we have a Transition tag explaining which resting state is initial state and which resting state is final one. Now we need a trigger, an action upon which this animation should start. Luckily, that trigger can be mentioned inside Transition tag. There can be two trigger types, OnClick and OnSwipe. And if I have to explain them too, then maybe you are reading a wrong article. Following code explains how they are mentioned inside Transition tag.

a. OnClick:

<Transition motion:constraintSetEnd="@+id/end" motion:constraintSetStart="@+id/start"> <OnClick motion:clickAction="toggle" motion:touchRegionId="@id/textView1" /> </Transition>

OnClick has two attributes, clickAction and touchRegionId. touchRegionId is the id of view to be clicked and clickAction has the information of what to do if that view is clicked. You can see that we’ve mentioned toggle inside clickAction, because we want our animation to toggle from start to end and from end to start if this view is clicked.

b. OnSwipe:

<Transition motion:constraintsetend="@+id/end" motion:constraintsetstart="@+id/start"> <OnSwipe motion:dragdirection="dragLeft" motion:touchregionid="@id/textView1"> </OnSwipe> </Transition>

OnSwipe also has two attributes, dragDirection and touchRegionId. touchRegionId is the same and dragDirection has the information of how this view should be dragged or swiped for animation to start. In dragDirection, we’ve set dragLeft, because we want our animation to start when user starts to drag textView1 to left. For purpose of this article, we’ll be using OnSwipe instead of OnClick.

Now our code is ready to be executed. We’ve created a text view in MotionLayout, we’ve set its initial and final state using ConstraintSet and using Transition, we’ve set how animation will start. If we execute our code right now, you’ll see this:

The beauty of motion layout is that you only have to set the initial resting state and final resting state and the rest is handled by motion layout itself. Like here, we didn’t have to calculate the angle of rotation at a given time during animation; we only said that in its final state, it should be rotated -90. Furthermore, what you will notice is that the reverse animation, the animation from final state to initial state, is automatically created by motion layout.

Conclusion

Now this is only the most basic demonstration of the power of motion layout. With motion layout, one can create complex animations without ever-invoking Java or Kotlin. In next article, we will go through some other tools available in motion layout like:

  • CustomAttribute

  • KeyFrameSet

  • KeyPosition and KeyAttribute

And create better, more complex animations. I encourage you to experiment with this demonstration. Its code is available on GitHub with branch name Article_1.


Share this blog
Group Meeting

This field cannot be blank.

Talk to us and

Get your project moving!