fbpx
Upcoming and OnDemand Webinars View full list

Creating Android animations with MotionLayout and MotionEditor

Dan Nesfeder

At Google I/O 2018, MotionLayout was announced promising to ease difficulty for developers when creating complex animations. Recently, at Android Dev Summit 2019, the Android Studio and MotionLayout teams showed off a new MotionEditor tool that aims to even further simplify creating complex animations with MotionLayout. We are going to explore both during this post.

We are going to work on an animation that transitions a search box for a destination to two search boxes for both destination and origin locations.

Download Android Studio 4.0 Preview and add dependencies

To take advantage of the new MotionEditor features within Android Studio, we will need to download the latest 4.0 Preview.

Starting from a new “Empty Activity” project, we need to open our app/build.gradle to add the dependency needed for MotionLayout:

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
    implementation 'androidx.appcompat:appcompat:1.1.0'
    implementation 'androidx.core:core-ktx:1.1.0'

    implementation 'androidx.constraintlayout:constraintlayout:2.0.0-beta3'
    ...
}

MotionLayout is actually a ConstraintLayout which allows us to animate layouts between various states.

Creating a new MotionLayout

We can now open the activity_main.xml file:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

We can see that with the project template, Android Studio has already generated a ConstraintLayout which we can use to convert to MotionLayout. With the layout file open, look for a new set of buttons in the top right of Android Studio and click the Design button:

With this viewing mode selected, we can now right-click on ConstraintLayout in the Component Tree section and click “Convert to MotionLayout”:

Android Studio is going to convert our ConstraintLayout to MotionLayout, generate a MotionScene file (res/xml/activity_main_scene.xml), and display the new Motion Editor tooling that will let us build our animations.

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.motion.widget.MotionLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:layoutDescription="@xml/activity_main_scene"
    tools:context=".MainActivity">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.motion.widget.MotionLayout>

Note: the conversion automatically added the attribute app:layoutDescription="@xml/activity_main_scene" to our MotionLayout, which links it to our MotionScene.

Add UI Components

For this example, we want to create a swipe-able sheet with two EditTexts and a FloatingActionButton. Before we start working on the MotionScene that will coordinate and animate the UI, we need to add the components to our activity_main.xml (there are String and Drawable resources referenced here that you will need to add from this project):

https://github.com/bignerdranch/BNR-blog-motionlayout/blob/master/app/src/main/res/layout/activity_main.xml

Note: We didn’t add constraints to the views in this layout file. Views that are animated should have their constraints set in the motion scene file instead.

If a view should not be animated, its constraints should be set in the layout XML file. We also did not include any attributes that adjust visibility or opacity. We will see in just a moment how these attributes are controlled and adjusted in the MotionScene.

Define UI Constraints

Now that we have our UI components added to our activity_main.xml, we can shift focus to activity_main_scene.xml where we will work on adding two ConstraintSets to define a “start” and “end” of our MotionScene. Let’s start by visually defining how we would like to organize the UI before and after the animation.

If we open up activity_main_scene.xml, we can see that when we converted our ConstraintLayout to a MotionLayout, Android studio generated a start and end ConstraintSet for us, but left it empty:

<MotionScene>
    <Transition
        motion:constraintSetEnd="@+id/end"
        motion:constraintSetStart="@id/start"
        motion:duration="1000">
        ...
    </Transition>

    <ConstraintSet android:id="@+id/start">
    </ConstraintSet>

    <ConstraintSet android:id="@+id/end">
    </ConstraintSet>
</MotionScene>

Now we can define our start:

https://github.com/bignerdranch/BNR-blog-motionlayout/blob/3372cd6f3d4563f9396374a76bb1e7ef15d6b2e4/app/src/main/res/xml/activity_main_scene.xml#L60-L129

And end ConstraintSet:

https://github.com/bignerdranch/BNR-blog-motionlayout/blob/3372cd6f3d4563f9396374a76bb1e7ef15d6b2e4/app/src/main/res/xml/activity_main_scene.xml#L131-L198

Add swipe handling for bottom sheet

One of the first updates we can make to our Transition is the ability to handle user interaction to start our animation. Fortunately, with MotionLayout, we can add handling right in the activity_main_scene.xml with a few lines of code:

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

    <OnSwipe
        motion:dragDirection="dragUp"
        motion:touchAnchorId="@id/bottomSheet"
        motion:touchRegionId="@id/bottomSheet" />
        ...
</Transition>

By adding OnSwipe with the defined dragDirectiontouchAnchorId, and touchRegionId we will now be able to handle a user swiping on our bottom sheet. Note here that the touchRegionId allows us to scope the swipe handling to just the bottom sheet View. This way, users can still interact with the map behind the bottom sheet.

Define KeyFrames for KeyFrameSet

With our Constraints defined, we get to tackle the fun part – let’s use the new MotionEditor to define our KeyFrameSet within the Transition. We can open activity_main.xml again and make sure we have the “Design” tab clicked in the top right corner.

Let’s work on our FloatingActionButton first. As the bottom sheet expands, we can add KeyFrames to animate the button off of the screen to the right:

To achieve this, we are going to add two KeyFrames with the MotionEditor. The first, to adjust the button’s alpha (notice how it fades) and the second to adjust the position of the button.

Let’s make sure we have our activity_main.xml open with the Design button selected. We also want to have the Transition panel open in the bottom right window. Click the arrow in between the start and end screen if this isn’t showing:

We can then click the icon in the top right of the Transition panel:

We can then click KeyAttribute > ID: fabMyLocation > Position: 20 > Attribute: alpha:

Let’s repeat the same process with KeyPosition > ID: fabMyLocation > Position: 20 > Type: parentRelative (take a look at this post) for more on position type) > Percent X: 1.2. After we’ve added both of these KeyFrames, we can take a look at activity_main_scene.xml and see where these have automatically been added:

<KeyFrameSet>
    <KeyPosition
        motion:framePosition="20"
        motion:keyPositionType="parentRelative"
        motion:motionTarget="@+id/fabMyLocation"
        motion:percentX="1.2" />
    <KeyAttribute
        android:alpha="0.0"
        motion:framePosition="20"
        motion:motionTarget="@+id/fabMyLocation" />
        ...
</KeyFrameSet>

Note: we have adjusted the android:alpha to 0.0 as we want the FloatingActionButton to be invisible by frame 20 of the Transition.

At this point, although we haven’t added the KeyFrames for our EditTexts, let’s build our app and try it out with just the button animation.

We can now finish the rest of the KeyFrames for the origin and destination EditTexts. We want the origin to be hidden until the animation has finished as well as the origin and destination icons. We also want the search icon to fade as it is replaced with the origin and destination icons. Try adding these KeyFrames yourself – if you get stuck, reference the finished scene code below.

One great thing about the MotionEditor is that it allows you to preview and fine-tune your KeyFrames without having to rebuild the app each time:

https://github.com/bignerdranch/BNR-blog-motionlayout/blob/master/app/src/main/res/xml/activity_main_scene.xml

Build the app and check out your finished animation! MotionLayout now combined with the new MotionEditor Android Studio tooling makes it much easier to create complex animations for your project.

Not Happy with Your Current App, or Digital Product?

Submit your event

Let's Discuss Your Project

Let's Discuss Your Project