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
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.
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:
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.