MotionLayout is an official Google framework for creating transition animations in Android. It can be used to easily make some more complex animation effects.

Because MotionLayout is based on ConstraintLayout, it involves some basic knowledge about ConstraintLayout. Click on the table of this article, who is not familiar with ConstraintLayout, you can view this blog by Hongyang.

ConstraintLayout MotionLayout is a subclass of ConstraintLayout, and ConstraintLayout was added to the library when ConstraintLayout grew to 2.0. The dependencies used in this article are

implementation 'androidx. Constraintlayout: constraintlayout: 2.0.0 - beta 2'
Copy the code

Let’s get down to business and take a look at a Demo I made using MotionLayout.

In this example, when the Login button is clicked, the length of the Login button is continuously reduced. When it is reduced to a certain size, the outer ProgressBar is gradually changed from invisible to visible. Meanwhile, the words on the Login button fade in and out of the animation.

MotionLayout doesn’t just do that, it can do other transitions that are even more fun. Now let’s learn.

Transition animation, as the name implies, is the animation effect of transition between states to prevent the effect of teleportation in the View of the page. The whole point of MotionLayout is state. All you need to do is define the relative position of the View and its properties, and MotionLayout will automatically add motion to the View.

How can such a simple effect be made?

First we need to create a new resource folder named XML under the resource folder res. Then we need to create an XML file whose root node is MotionScene. In the demo, the XML file is named login_animator.

The following is a transition animation for the Login button.

<?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/a_login_end"
        app:constraintSetStart="@id/a_login_start"
        app:duration="1000">
        <OnClick
            app:clickAction="toggle"
            app:targetId="@id/tv_action_login" />

    </Transition>

    <ConstraintSet android:id="@+id/a_login_start">
        <Constraint android:id="@+id/tv_action_login">
            <Layout
                android:layout_width="match_parent"
                android:layout_height="48dp"
				android:layout_marginTop="30dp"
                android:layout_marginStart="30dp"
                android:layout_marginEnd="30dp"
                app:layout_constraintTop_toBottomOf="@id/et_passwd" />
        </Constraint>

    </ConstraintSet>

    <ConstraintSet android:id="@+id/a_login_end">
        <Constraint android:id="@+id/tv_action_login">
            <Layout
                android:layout_width="48dp"
                android:layout_height="48dp"
                android:layout_marginTop="30dp"
                app:layout_constraintEnd_toEndOf="@+id/et_account"
                app:layout_constraintStart_toStartOf="@+id/et_account"
                app:layout_constraintTop_toBottomOf="@id/et_passwd" />
        </Constraint>

    </ConstraintSet>

</MotionScene>
Copy the code

Take a closer look at the information. Most of it is familiar to us. It is nothing more than conventions about the relative position of the View or the properties of the View itself.

Let’s look at the structure of this file. The root node is MotionScene, and the Transition node has two ConstraintSet nodes, and the Transition node has two properties, ConstraintSetStart and constraintSetEnd, the values of these two attributes are exactly the IDS of the two ConstraintSet nodes, The attribute targetId in the Transition OnClick node indicates that the current Transition animation is applied to a specific View.

As you might expect, you can animate a Transition between two states of a View by specifying different properties in Transition, and display them in Translation by combining different animation events. Such as click-generated animation (OnClick), swiping animation (OnSwipe) and KeyFrameSet animation (KeyFrameSet), which can change the effect of a particular frame of animation.

Once we’ve defined the properties and animations in the initial and final states, we need to go back to our layout file, Change the parent layout of the View that you want to animate to MotionLayout and add a layoutDescription value to it that is referenced by the XML file we just created.

<?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/login_animator">.</androidx.constraintlayout.motion.widget.MotionLayout>
Copy the code

This is one of the most simple use of MotionLayout to achieve the transition animation example, it and the beginning of the demo I wrote no difference, nothing more than the demo transform View number and how many different attributes.

In this example, we animate clicks by defining an OnClick child node in Transition. Where, targetId is the ID of the target View to generate the animation effect; ClickAction specifies whether to animate at start or end, toggle specifies whether to animate at start or end, and transitionToStart and transitionToEnd specify only at start or end. Anyone interested can try it.

In addition to OnClick, we can also define the OnSwipe node in Translation, which is used to handle on-screen sliding events to animate the transition with the specified View.

Add motionDebug=”SHOW_PATH” to MotionLayout to see the path of the View’s transition animation.

By specifying the View’s start state (near the left side of the screen) and end state (near the right side of the screen), and then declaring a sliding event in Translation.

<?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/v_swipe_end"
        app:constraintSetStart="@id/v_swipe_start"
        app:duration="1000">
        <OnSwipe
            app:dragDirection="dragRight"
            app:touchRegionId="@id/v_swipe" />

    </Transition>
    
    <ConstraintSet android:id="@+id/v_swipe_start">
        <Constraint android:id="@+id/v_swipe">
            <Layout
                android:layout_width="48dp"
                android:layout_height="48dp"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toTopOf="parent" />
        </Constraint>

    </ConstraintSet>

    <ConstraintSet android:id="@+id/v_swipe_end">
        <Constraint android:id="@+id/v_swipe">
            <Layout
                android:layout_width="48dp"
                android:layout_height="48dp"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintTop_toTopOf="parent" />
        </Constraint>

    </ConstraintSet>

</MotionScene>
Copy the code

In OnSwipe, there are two attributes: dragDirection, which represents the slide direction, and touchRegionId, which indicates that the slide region to listen on is the Slide region of the View. If it works on the View slide area, can it also work on the entire screen slide area? That’s right, touchAnchorId represents all the sliding areas. OnSwipe also has a number of other attributes, such as: touchAnchorSide indicates which area of the View to listen on, if not set, all areas outside the View; OnTouchUp represents the action of the animation (return to start, return to end, autocomplete, stop, etc.) when the finger is raised during the slide.

To be honest, I was intimidated by OnSwipe when I first tried MotionLayout, but when I took KeyFrameSet a step further, I shouted 666. And the reason for that is because KeyFrameSet makes it look a lot cooler.

KeyFrameSet is the key frame used in the transition animation process, by specifying the state of the animation key process to achieve different effects. For example, the current View slide is a straight line, and I want one to slide up and then down to the far right of the screen in this way.

The start and end states of the View are not changed, but the coordinates of the View are changed in the midpoint region of the transition animation.

<Transition
    app:constraintSetEnd="@id/v_swipe_end"
    app:constraintSetStart="@id/v_swipe_start"
    app:duration="1000">
    <OnSwipe
        app:dragDirection="dragRight"
        app:touchAnchorId="@id/v_swipe"
        app:touchAnchorSide="bottom" />

    <KeyFrameSet>
        <KeyPosition
            app:framePosition="50"
            app:keyPositionType="parentRelative"
            app:motionTarget="@+id/v_swipe"
            app:percentY="0.3" />
    </KeyFrameSet>

</Transition>
Copy the code

FramePosition indicates that 50% of the motion has been moved. This value ranges from 0 to 100. MotionTarget indicates the View being applied. KeyPositionType and percentY together determine the direction of radians in the trajectory. KeyPositionType controls how the coordinate system within percentY works, and it has three values. ParentRelative, deltaRelative, pathRelative. PercentY ranges from 0 to 1, allowing negative numbers and values greater than 1.

ParentRelative indicates that coordinates are processed according to the coordinates of the parent layout. The maximum values of both X and Y axes are 1. The X axis is positive to the right and negative to the left, and the Y axis is positive to the downward and negative to the upward.

DeltaRelative indicates that the center point of the starting state is the origin of the coordinate system, the maximum value of X and Y axes is 1, X axis is positive to the right and negative to the left, Y axis is negative to the downward and positive to the upward.

PathRelative indicates that the center point of the starting state is the origin of the coordinate system, and the X-axis is the straight line formed by two shaped center points. The maximum value of X and Y axis is 1, the end direction of X axis is positive, the beginning direction is negative, the down direction of Y axis is negative, the up direction is positive.

The description diagrams for the three keyPositionType attributes are all from CodeLab

KeyPostition also has some other interesting properties, such as curveFit, which controls whether the movement is smooth or straight, and transitionEasing, which controls whether it speeds up or slows down. Here is not an example.

Furthermore, multiple keyframes can be present simultaneously to control the animation effect.

<KeyFrameSet>
    <KeyPosition
        app:framePosition="50"
        app:keyPositionType="parentRelative"
        app:motionTarget="@+id/v_swipe"
        app:percentY="0.3" />
    <KeyAttribute
        android:alpha="0"
        app:framePosition="50"
        app:motionTarget="@+id/v_swipe" />
</KeyFrameSet>
Copy the code

KeyAttribute is an attribute used to control the View during transition animation. For example, when the animation is 50% executed, the View’s alpha value is 0. The MotionLayout automatically changes the state of the View based on its execution time.

That’s all about animation itself. In fact, MotionLayout provides more ways to change the state of a View than simply specifying a Layout in ConstraintSet to change the relative position of the View. It also provides richer ways to change the state of a View, such as:

Motion is used to change the effects of the animation, such as speeding up, slowing down, or moving horizontally or vertically first

CustomAttribute Is used to change custom attributes.

PropertySet is used to change several properties specific to the View;

Transform is used to change the properties of the View involved in the property animation, such as the rotation, scaleX, etc. The usage is also very simple, like Layout declaration can be.

<ConstraintSet android:id="@+id/v_swipe_start">
    <Constraint android:id="@+id/v_swipe">
        <CustomAttribute
            app:attributeName="backgroundColor"
            app:customColorValue="@color/colorAccent" />
        <Transform
            android:scaleX="1.0"
            android:scaleY="1.0" />
        <Layout
            android:layout_width="48dp"
            android:layout_height="48dp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />
    </Constraint>

</ConstraintSet>

<ConstraintSet android:id="@+id/v_swipe_end">
    <Constraint android:id="@+id/v_swipe">
        <CustomAttribute
            app:attributeName="backgroundColor"
            app:customColorValue="@color/colorPrimary" />
        <Transform
            android:scaleX="3.0"
            android:scaleY="3.0" />
        <Layout
            android:layout_width="48dp"
            android:layout_height="48dp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintTop_toTopOf="parent" />
    </Constraint>

</ConstraintSet>
Copy the code

Due to large magnification, the display is abnormal at the end of the state.

MotionLayout is a little bit more fun than you thought, but it’s not easy to debug. Every time you debug it, you need to run it.

This article was first published on my blog. The full source code of the article has been uploaded to GitHub, and the code branch is motionLayout. Like the trouble point 🌟.

Recommended study site: CodeLab.

Cover image: Photo by NASA on Unsplash