MotionLayout Tutorial For Android: Getting Started
Learn how to use the new ConstraintLayout subclass MotionLayout to add effects such as translation animations and alpha/color changes. By Filip Babić.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
MotionLayout Tutorial For Android: Getting Started
25 mins
Meaningful motion is an important aspect of every modern application. Using animations, transitions, and text or view transformations, you can improve the UX (user experience) of your application and make your users happy. Unfortunately, this is not always simple. Android provides many different ways to animate objects on the screen and it’s always a challenge to find the right one. Sometimes you imagine complex animations, but you have to give up implementing them because of the time requirements.
Now you can solve all these problems with MotionLayout!
MotionLayout was been announced by Google at Google I/O 2018 and it’s a solution for mitigating the difficulty of creating complex animations. It provides a declarative, smart and clean way of building animations, without a single line of code.
As you’ll see below, MotionLayout extends ConstraintLayout, and so inherits all of its features. Through the internal use of ConstraintLayout and its helpers, MotionLayout allows you to animate color changes, alpha transitions and views, using as little as a single XML file.
In this tutorial you’ll see how you can combine a few animations, without much work, using MotionLayout. You’ll learn how to:
- Animate alpha changes
- Translate views
- Interpolate color changes
- Combine MotionLayout with other Android components
Note: This tutorial assumes you’ve got good knowledge of Android development, and that you’ve had previous experience with animations in Android apps. If you’re new to Android, please check out our Beginner Android series and the Android Animations tutorial
Getting Started
Before you begin, download the tutorial materials from the Download Materials button at the top or the bottom of the page. You’ll see the starter and final projects, which you’ll use throughout the tutorial.
Next, open up the build.gradle file under the app folder, and change the ConstraintLayout dependency version to 2.0.0-alpha2
:
dependencies {
- -
// Support Libraries
implementation "com.android.support:appcompat-v7:$support_lib_version"
implementation 'com.android.support.constraint:constraint-layout:2.0.0-alpha2'
- - -
}
Finally, open up the onboarding_view.xml
file, and change the root layout’s type to MotionLayout
.
<android.support.constraint.motion.MotionLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/onboardingRoot"
android:layout_width="match_parent"
android:layout_height="match_parent">
- - -
</android.support.constraint.motion.MotionLayout>
The goal of this tutorial is to create an on-boarding screen custom View
, which will have a few animations. You’ll animate the next, skip and finish buttons, as well as the background color, like so:
In order to do this you need to understand what MotionScene
, KeyFrameSet
and the KeyAttribute
are and how to use them. So let’s get started! :]
Introducing MotionLayout
If you look at the build.gradle file you updated earlier, you’ll see that there is no dependency for MotionLayout. This is because the MotionLayout is actually extends ConstraintLayout and the constraint-layout-2.0.0-alpha2
dependency includes everything you need.
MotionLayout uses a mechanism called the MotionScene
to create all of the underlying animations. It is similar to Scene
definitions which you can write to animate between screen states. They also define states in which a screen, and its elements, can be. Each of the scenes can have Transition
, KeyFrameSet
and ConstraintSet
definitions. Let’s look at what each of them does.
MotionScene
With MotionLayout you can create your animations in a declarative way through the definition of a MotionScene
in an XML file in the res/xml folder. This is where you can define everything the framework needs in order to run your animations. Thanks to the nature of ConstraintLayout, you can define the different states of the View
components in the animation using different Constraint
definitions, into a ConstraintSet
element.
Animating doesn’t mean just moving objects but, in general, changing their properties in a time dependent fashion. You can move from state A to state B in many different ways with the definition of a Transition
. Even if you know what your initial and final states are, you also need to define how this should happen and what are the possible intermediate states. This can be done using a KeyFrameSet
definition.
It’s important to note that you define the MotionLayout in a file that is different from the one that contains the layout. You can apply the same MotionScene
to different layout definitions, enhancing reusability. Applying an animation to a layout is as easy as assigning its reference as the value of the app:layoutDescription
XML attribute of the MotionLayout
element in the layout.
Transition
The name here is pretty self-explanatory. A Transition
describes a change from state A to state B. You configure the start constraint set using the app:constraintSetStart
attribute and the final one using app:constraintSetEnd
. Then, when the progress
of the Motionscene
increases, the layout starts the transformation to the end constraint. Progress is just a number, ranging from 0 to 100, saying what percentage of the animation has currently been played.
The total duration of the animation is usually split in 100 different parts which are played, one after the other, at constant rate. If you need a different distribution in time of the animation, you need a custom interpolator that you can then set using the app:interpolator
XML attribute.
KeyFrameSet
You can think of any animation as a sequence of frames you render on the screen during a given interval of time. If you want to animate a specific property from the value A to the value B, the framework chooses, by default, the shortest way. For instance, if A and B are positions on the screen, you’ll see the items moving through a straight line with constant speed.
You’ve just learned that if you want to change the way the frames are displayed in time you can use a different interpolator, but what if you want to change the path? Using the KeyFrameSet
element, it’s possible to add some intermediate constraints that must be satisfied at some given times. For instance, you can tell the system to go from A to B through a path which contains C at time 20 and D at time 80.
To do this you can use the KeyAttribute
or CustomAttribute
elements, depending on what you need. You can use the former in case of more common View
properties like position or alpha, but in the case of a component with custom properties, you need the latter. If you use CustomAttribute
s, you can use a custom String
identifier, but it has to match the set method on the view. A good example is backgroundColor
, as you can call setBackgroundColor()
on a view.
ConstraintSet
Finally, you use ConstraintSet
definitions to define starting and final constraints, for each of the View
s you want to animate. It’s important to note that a MotionScene doesn’t need to define a ConstraintSet
for every View
in the layout, but just for those that should be animated.
That being said, if you don’t provide an end constraint for a view, it will disappear. This happens because the library doesn’t know which constraints it should apply at the end of the animation. However, if you do provide start and end constraints, the views will move from the start to the end, or they won’t move at all if the start constraints match the end ones.
Working with MotionLayout
To start off working with MotionLayout, you first have to create a MotionScene
in an XML file. Head over to res/xml, right-click it and choose New > XML resource file, using onboarding_scene as the name.
Next, prepare the content of the file using the MotionScene
XML element like in the following snippet:
<MotionScene xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
</MotionScene>
Note: Currently the XML editor doesn’t work well with motion scenes, and all the tags you can use with it. Sometimes you won’t have autocomplete, so make sure to follow the code snippets from the tutorial.
Before you head out to animations, first you have to define the constraints for all the views you’ll be animating. Open up the onboarding_view.xml file, to see the layout structure that you’ll animate. There’s the Previous, Next and Finish buttons, the background, and a view pager with an indicator. If you launch the application you’ll see something like this:

The initial layout
Don’t worry if the Next and Finish buttons are overlapping on the bottom right part of the screen: you’ll fix everything very soon.
Our idea is to animate the previous button in, when you’ve moved away from the first page. Additionally, you should simultaneously animate the finish and next buttons, so that the finish button moves into the screen, while the next button moves out of the screen, when you swipe to the last page.
Thinking about this, you don’t have to change the constraints of any views. You can simply translate them in and out, or change the views’ alpha.
So let’s start by defining the start and end constraints.
Defining ConstraintSets
Each MotionScene
should have two sets of constraints: a starting one and a final one. Usually, the former will be whatever you declared in the layout file, and the latter will define how you want the layout to change. When you don’t have to change the constraints, you can have both of the sets nearly the same.
To add a set, you just create a ConstraintSet
XML element. In it, you can create as many constraints as you want, although they usually depend on the number of views in the layout.
Inside the onboarding_scene.xml file you created earlier, add this ConstraintSet
definition as a child of the MotionScene
<ConstraintSet android:id="@+id/start">
<Constraint
android:id="@id/onboardingRoot"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<Constraint
android:id="@id/previousButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<Constraint
android:id="@id/nextButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<Constraint
android:id="@id/finishButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
</ConstraintSet>
These are the starting constraints you use in the layout file. Also notice that the set has an id
. You use this to differentiate the start from the end constraints. You’ll create a transition and connect these later on.
Now, you’ll add the end version of the constraints. These should be the same, so you can copy and paste the last snippet, but change the id
to @+id/end
. It should look like this:
<ConstraintSet android:id="@+id/end">
<Constraint
android:id="@id/onboardingRoot"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<Constraint
android:id="@id/previousButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<Constraint
android:id="@id/nextButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<Constraint
android:id="@id/finishButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
</ConstraintSet>
Now that you have the constraints ready, all that you have to do to is to apply this MotionScene
to the layout file.
Open up onboarding_view.xml again, and add the following attribute to the layout root: app:layoutDescription="@xml/onboarding_scene"
. The MotionLayout tag should now look like this:
<android.support.constraint.motion.MotionLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/onboardingRoot"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layoutDescription="@xml/onboarding_scene">
- - -
</android.support.constraint.motion.MotionLayout>
Finally, you have to connect these two sets using a Transition
. Add the following snippet to the top of the onboarding_scene.xml file:
<Transition
app:constraintSetEnd="@id/end"
app:constraintSetStart="@id/start" />
Build and run the application and you should see the initial layout. And if you look to the bottom-right corner of the app, to the Next and Finish buttons, you’ll see that they still overlap. This is because you’re still missing the animations. You’ll worry about that in a moment, when you start animating views that move them around.
Animating attributes
You haven’t really animated anything yet, so let’s change that. There are a few things you can animate in a MotionLayout. First off, you’ll add the button animations, so they don’t overlap, and they appear at the right time. Additionally, to make all the animations run you have to setup progress changes with the ViewPager
.
Open up the OnboardingView class in the view
package, and go to the onPageScrolled
function. It looks something like this:
override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {
if (numberOfPages > 1) {
val newProgress = (position + positionOffset) / (numberOfPages - 1)
//update progress
}
}
All you have to do to change the progress of the MotionScene
is to add the following line under the comment:
onboardingRoot.progress = newProgress
So whenever you scroll the pager, you will update the animation’s progress.
As mentioned before, you will make the Previous button appear once you leave the first page. So a good way to do this is to set its alpha to 0 at the start, and change it to 1 in the end. Go back to the onboarding_scene.xml file. Add the android:alpha="0"
attribute to the previousButton
start constraint.
<ConstraintSet android:id="@+id/start">
- - -
<Constraint
android:id="@id/previousButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:alpha="0"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent" />
- - -
</ConstraintSet>
Also add android:alpha="1"
to the end constraint counterpart.
<ConstraintSet android:id="@+id/end">
- - -
<Constraint
android:id="@id/previousButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:alpha="1"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent" />
- - -
</ConstraintSet>
Build and run the code. You should see the Previous button fade in and out. You’ve made your first animation! :]
But you can do better. Rather than changing alpha from 0 to 1 across all the pages, it would be better if it went to full alpha when you leave the first page. To do this, you have to use a KeyFrame
. You have to tell MotionLayout to go to full alpha in 20% of the animation – when you scroll to the second page.
Defining animation progress
Once again, open up the onboarding_scene.xml file. Add the following code snippet within the Transition
XML tag:
<KeyFrameSet>
<KeyAttribute
android:alpha="0"
app:framePosition="0"
app:target="@id/previousButton" />
<KeyAttribute
android:alpha="1"
app:framePosition="20"
app:target="@id/previousButton" />
</KeyFrameSet>
This snippet makes the animation go from no alpha to full alpha, in the first 20% of the progress
. Once the View
reaches full alpha, it will stay full, unless you go back to the first page, where it will fade out. Simple as that. You can build and run the project again, and it should work smoothly now.
How about you solve those overlapping buttons now?
Changing constraints
One of the best things MotionLayout allows you to do is change constraints. Without using much code, you can translate your UI all around, and build fun motion constructs. But there is a problem. You can’t define constraint framePosition
, like you did with the alpha. Let’s see how you can solve the problem of overlapping buttons, with constraints.
Change the nextButton
and finishButton
start and end constraints to the following values.
Start:
<Constraint
android:id="@id/nextButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<Constraint
android:id="@id/finishButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="parent" />
And end:
<Constraint
android:id="@id/nextButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<Constraint
android:id="@id/finishButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
With these changes you moved the finishButton
start at the end of the screen. When the progress changes, it will start moving inside the layout, and the nextButton
will move outside of the same layout.
Build and run the project, and you should see the buttons moving in a linear manner, rather than on a specific page.
Because of this constraint, you will instead move the buttons using the translationX
and translationY
properties. You’ll see how in the next section.
Translating Buttons
Because of the issue previously described, you’ll translate the Next button out of the layout, and simultaneously move the Finish button in.
Undo the last changes you made, regarding constraint sets, in the onboarding_scene.xml file. You now need to define start and end states in the ConstraintSets
for the new translation values.
First, add the following tags to the Next and Finish buttons: android:translationY="0dp"
to Next and android:translationX="70dp"
to Finish for the starting constraints, and android:translationY="50dp"
to Next and android:translationX="0dp"
to Finish for the ending constraints.
Build and run the app, and you should see the buttons move as you move through pages.
Once again, you have to define steps for the animations. Since the change has to happen at the very last page, you’ll have to keep the translation
up to about the 75% of the animation. Add the following snippet of code, within the KeyFrameSet
:
<KeyAttribute
android:translationY="0dp"
app:framePosition="75"
app:target="@id/nextButton" />
<KeyAttribute
android:translationY="50dp"
app:framePosition="100"
app:target="@id/nextButton" />
<KeyAttribute
android:translationX="70dp"
app:framePosition="75"
app:target="@id/finishButton" />
<KeyAttribute
android:translationX="0dp"
app:framePosition="100"
app:target="@id/finishButton" />
If you build and run the app again, you’ll see the same animations at play. However, they will run only when switching between the last two pages.
Your onboarding is starting to look pretty good. :]
Animating custom attributes
To finish off the on-boarding, you will animate a custom attribute of the OnBoardingView
. Your pages are pretty blank and you’ll add a color interpolation between them. Now, this might sound complicated, but in reality, it’s not. Moreover, you can achieve it using only KeyAttributes
, as well.
Add the following code to the onboarding_scene.xml file within the Transition
tag:
<KeyFrameSet>
<KeyAttribute
app:framePosition="20"
app:target="@id/background"
app:transitionEasing="accelerate">
<CustomAttribute
app:attributeName="backgroundColor"
app:customColorValue="#A5C63A" />
</KeyAttribute>
<KeyAttribute
app:framePosition="40"
app:target="@id/background"
app:transitionEasing="accelerate">
<CustomAttribute
app:attributeName="backgroundColor"
app:customColorValue="#C3E6E6" />
</KeyAttribute>
<KeyAttribute
app:framePosition="60"
app:target="@id/background"
app:transitionEasing="accelerate">
<CustomAttribute
app:attributeName="backgroundColor"
app:customColorValue="#464E69" />
</KeyAttribute>
<KeyAttribute
app:framePosition="80"
app:target="@id/background"
app:transitionEasing="accelerate">
<CustomAttribute
app:attributeName="backgroundColor"
app:customColorValue="#AF6DA2" />
</KeyAttribute>
<KeyAttribute
app:framePosition="100"
app:target="@id/background"
app:transitionEasing="accelerate">
<CustomAttribute
app:attributeName="backgroundColor"
app:customColorValue="#FBBF1D" />
</KeyAttribute>
</KeyFrameSet>
There’s quite a bit of code, but it follows a common pattern. For each step, in your case each page, you define an attribute you want to change. Of course, all the attributes – background color and the target – are the same. The only thing that changes is the framePosition
and the backgroundColor
. Custom attributes work using the set
function, matching the attribute name from the View
. So, as you’re swiping, the setBackgroundColor
function will be called, with the calculated value.
Build and run the app, and you should see the pages brought to life. You now have a fully-working onboarding process, which is nice and smooth. :]
Defining view transitions
You still haven’t used many Transition
options for the onboarding screen. Since they don’t really fit in there, you’ll learn more about transitions and their properties on a separate screen.
First, right-click on the xml folder and create the transition_scene.xml file, just like you did before for the other scene. Then, paste the following code inside:
<?xml version="1.0" encoding="utf-8"?>
<MotionScene xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<Transition
app:constraintSetEnd="@id/end"
app:constraintSetStart="@id/start">
<OnSwipe
app:touchAnchorId="@id/happyAndroid"
app:touchAnchorSide="top" />
</Transition>
<ConstraintSet android:id="@+id/start">
<Constraint
android:id="@id/happyAndroid"
android:layout_width="100dp"
android:layout_height="100dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</ConstraintSet>
<ConstraintSet android:id="@+id/end">
<Constraint
android:id="@id/happyAndroid"
android:layout_width="100dp"
android:layout_height="100dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</ConstraintSet>
</MotionScene>
I’ll explain what’s going on in this XML code below.
Additionally, just like you did before, change the root layout to MotionLayout
in the activity_transition xml, and add app:layoutDescription="@xml/transition_scene"
as an attribute.
Finally, you have to switch the activity which launches after the splash screen.
Open up the SplashActivity.kt file, and navigate to the following code:
Handler().postDelayed({
// Start activity
startActivity(Intent(this, MainActivity::class.java))
// Animate the loading of new activity
overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out)
// Close this activity
finish()
}, 2000)
Change the target activity for the startActivity
function to TransitionActivity::class.java
. That line of code should look like this now:
startActivity(Intent(this, TransitionActivity::class.java))
Now build and run the application. If you try swiping the Bugdroid image, it should move from the top to the bottom:
So how does this animation work? The magic is in the OnSwipe
XML tag. It creates a standard swipe handler which anchors the touch start from the top of the happyAndroid
element. When you swipe, the Bugdroid will move between the start and end constraints.
Although the Bugdroid can bounce up and down, we can make him a bit more interesting, by adding KeyPositions. Open up the transition_scene.xml file again, and add the following code within the Transition
tag:
<KeyFrameSet>
<KeyPosition
app:framePosition="20"
app:keyPositionType="parentRelative"
app:percentX="0.2"
app:percentY="0.3"
app:target="@id/happyAndroid" />
<KeyPosition
app:framePosition="50"
app:keyPositionType="parentRelative"
app:percentX="0.8"
app:percentY="0.7"
app:target="@id/happyAndroid" />
</KeyFrameSet>
By adding KeyPosition
s, you changed the linear path for the Bugdroid. It will now move in a spiral, relative to the parent’s percentage positions. To see what this looks like, build and run the app. You should see something like this:
The Bugdroid has some moves now! Each KeyPosition
is like a checkpoint through which the view passes. They can have an X-axis percentage and a Y-axis percentage, which can be relative to the path or the parent View
. You can learn more about this in a tutorial by Google.
Where to Go From Here?
MotionLayout is still some ways from being stable. Once tools like the KeyFrame
editor come out, it will be much easier to build and preview animations. However, this doesn’t mean that you can’t start building awesome animations with MotionLayout.
Check out these articles and links which also explain how to use the new API, and what you can build with it:
- Introduction to MotionLayout by Nicolas Roard
- Defining motion paths in MotionLayout by Nicolas Roard
- The official documentation
I hope you enjoyed this tutorial on MotionLayout. Please join in the discussion below with any comments or questions!