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 3 of 4 of this article. Click here to view the first 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 KeyPositions, 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.