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

Leave a rating/review
Download materials
Save for later
Share
You are currently viewing page 2 of 4 of this article. Click here to view the first page.

ConstraintSet

Finally, you use ConstraintSet definitions to define starting and final constraints, for each of the Views 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.