Manage motion and widget animation with MotionLayout

MotionLayout is a layout type that helps you manage motion and widget animation in your app. MotionLayout is a subclass of ConstraintLayout and builds upon its rich layout capabilities. As part of the ConstraintLayout library, MotionLayout is available as a support library and is backwards-compatible to API level 14.

Figure 1. Basic touch-controlled motion.

MotionLayout bridges the gap between layout transitions and complex motion handling, offering a mix of features between the property animation framework, TransitionManager, and CoordinatorLayout.

In addition to describing transitions between layouts, MotionLayout lets you animate any layout properties, as well. Moreover, it inherently supports seekable transitions. This means that you can instantly show any point within the transition based on some condition, such as touch input. MotionLayout also supports keyframes, enabling fully customized transitions to suit your needs.

MotionLayout is fully declarative, meaning that you can describe any transitions in XML, no matter how complex.

Design considerations

MotionLayout is intended to move, resize, and animate UI elements with which users interact, such as buttons and title bars. Motion in your app should not be simply a gratuitous special effect in your application. It should be used to help users understand what your application is doing. For more information on designing your app with motion, see the Material Design section on Understanding motion.

Getting started

Follow these steps to start using MotionLayout in your project.

  1. Add the ConstraintLayout dependency: To use MotionLayout in your project, add the ConstraintLayout 2.0 dependency to your app's build.gradle file. If you're using AndroidX, add the following dependency:

    Groovy

    dependencies {
        implementation "androidx.constraintlayout:constraintlayout:2.2.0-alpha10"
        // To use constraintlayout in compose
        implementation "androidx.constraintlayout:constraintlayout-compose:1.1.0-alpha10"
    }
    

    Kotlin

    dependencies {
        implementation("androidx.constraintlayout:constraintlayout:2.2.0-alpha10")
        // To use constraintlayout in compose
        implementation("androidx.constraintlayout:constraintlayout-compose:1.1.0-alpha10")
    }
    
  2. Create a MotionLayout file: MotionLayout is a subclass of ConstraintLayout, so you can transform any existing ConstraintLayout into a MotionLayout by replacing the class name in your layout resource file, as shown in the following examples:

    AndroidX

    <!-- before: ConstraintLayout -->
    <androidx.constraintlayout.widget.ConstraintLayout .../>
    <!-- after: MotionLayout -->
    <androidx.constraintlayout.motion.widget.MotionLayout .../>
              

    Support library

    <!-- before: ConstraintLayout -->
    <android.support.constraint.ConstraintLayout .../>
    <!-- after: MotionLayout -->
    <android.support.constraint.motion.MotionLayout .../>
              

    Here's a full example MotionLayout file that can be used to create the motion in figure 1:

    AndroidX

    <?xml version="1.0" encoding="utf-8"?>
    <!-- activity_main.xml -->
    <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:id="@+id/motionLayout"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layoutDescription="@xml/scene_01"
        tools:showPaths="true">
    
        <View
            android:id="@+id/button"
            android:layout_width="64dp"
            android:layout_height="64dp"
            android:background="@color/colorAccent"
            android:text="Button" />
    
    </androidx.constraintlayout.motion.widget.MotionLayout>
            

    Support library

    <?xml version="1.0" encoding="utf-8"?>
    <!-- activity_main.xml -->
    <android.support.constraint.motion.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:id="@+id/motionLayout"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layoutDescription="@xml/scene_01"
        tools:showPaths="true">
    
        <View
            android:id="@+id/button"
            android:layout_width="64dp"
            android:layout_height="64dp"
            android:background="@color/colorAccent"
            android:text="Button" />
    
    </android.support.constraint.motion.MotionLayout>
            
  3. Create a MotionScene: In the previous MotionLayout example, the app:layoutDescription attribute references a MotionScene. A MotionScene is an XML resource file that contains all of the motion descriptions for the corresponding layout. To keep layout information separate from motion descriptions, each MotionLayout references a separate MotionScene. Note that definitions in the MotionScene take precedence over any similar definitions in the MotionLayout.

    Here's an example MotionScene file that describes the basic horizontal motion in figure 1:

    <?xml version="1.0" encoding="utf-8"?>
    <MotionScene xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:motion="http://schemas.android.com/apk/res-auto">
    
        <Transition
            motion:constraintSetStart="@+id/start"
            motion:constraintSetEnd="@+id/end"
            motion:duration="1000">
            <OnSwipe
                motion:touchAnchorId="@+id/button"
                motion:touchAnchorSide="right"
                motion:dragDirection="dragRight" />
        </Transition>
    
        <ConstraintSet android:id="@+id/start">
            <Constraint
                android:id="@+id/button"
                android:layout_width="64dp"
                android:layout_height="64dp"
                android:layout_marginStart="8dp"
                motion:layout_constraintBottom_toBottomOf="parent"
                motion:layout_constraintStart_toStartOf="parent"
                motion:layout_constraintTop_toTopOf="parent" />
        </ConstraintSet>
    
        <ConstraintSet android:id="@+id/end">
            <Constraint
                android:id="@+id/button"
                android:layout_width="64dp"
                android:layout_height="64dp"
                android:layout_marginEnd="8dp"
                motion:layout_constraintBottom_toBottomOf="parent"
                motion:layout_constraintEnd_toEndOf="parent"
                motion:layout_constraintTop_toTopOf="parent" />
        </ConstraintSet>
    
    </MotionScene>
        

    Note the following:

    • <Transition> contains the base definition of the motion.

      • motion:constraintSetStart and motion:constraintSetEnd are references to the endpoints of the motion. These endpoints are defined in the <ConstraintSet> elements later in the MotionScene.

      • motion:duration specifies the number of milliseconds that it takes for the motion to complete.

    • <OnSwipe> lets you control the motion via touch.

      • motion:touchAnchorId refers to the view that you can swipe and drag.

      • motion:touchAnchorSide means that we are dragging the view from the right side.

      • motion:dragDirection refers to the progress direction of the drag. For example, motion:dragDirection="dragRight" means that progress increases as you drag to the right.

    • <ConstraintSet> is where you define the various constraints that describe your motion. In this example, we define one ConstraintSet for each endpoint of our motion. These endpoints are centered vertically (via app:layout_constraintTop_toTopOf="parent" and app:layout_constraintBottom_toBottomOf="parent"). Horizontally, the endpoints are at the far left and right sides of the screen.

    For a more detailed look at the various elements that MotionScene supports, see the MotionLayout examples.

Interpolated attributes

Within a MotionScene file, ConstraintSet elements can contain additional attributes that are interpolated during transition. In addition to position and bounds, the following attributes are interpolated by MotionLayout:

  • alpha
  • visibility
  • elevation
  • rotation, rotationX, rotationY
  • translationX, translationY, translationZ
  • scaleX, scaleY

Custom attributes

Within a <Constraint>, you can use the <CustomAttribute> element to specify a transition for attributes that aren't simply related to position or View attributes.

<Constraint
    android:id="@+id/button" ...>
    <CustomAttribute
        motion:attributeName="backgroundColor"
        motion:customColorValue="#D81B60"/>
</Constraint>

A <CustomAttribute> contains two attributes of its own:

  • motion:attributeName is required and must match an object with getter and setter methods. The getter and setter must match a specific pattern. For example, backgroundColor is supported, since our view has underlying getBackgroundColor() and setBackgroundColor() methods.
  • The other attribute you must provide is based on the value type. Choose from the following supported types:
    • motion:customColorValue for colors
    • motion:customIntegerValue for integers
    • motion:customFloatValue for floats
    • motion:customStringValue for strings
    • motion:customDimension for dimensions
    • motion:customBoolean for booleans

Note that when specifying a custom attribute, you must define endpoint values in both the start and end <ConstraintSet> elements.

Example: Change background color

Building on our previous example, let's have the view change colors as part of its motion, as shown in figure 2.

Figure 2. The view changes its background color as it moves.

Add a <CustomAttribute> element to each ConstraintSet elements, as shown below:

<ConstraintSet android:id="@+id/start">
    <Constraint
        android:id="@+id/button"
        android:layout_width="64dp"
        android:layout_height="64dp"
        android:layout_marginStart="8dp"
        motion:layout_constraintBottom_toBottomOf="parent"
        motion:layout_constraintStart_toStartOf="parent"
        motion:layout_constraintTop_toTopOf="parent">
        <CustomAttribute
            motion:attributeName="backgroundColor"
            motion:customColorValue="#D81B60" />
    </Constraint>
</ConstraintSet>

<ConstraintSet android:id="@+id/end">
    <Constraint
        android:id="@+id/button"
        android:layout_width="64dp"
        android:layout_height="64dp"
        android:layout_marginEnd="8dp"
        motion:layout_constraintBottom_toBottomOf="parent"
        motion:layout_constraintEnd_toEndOf="parent"
        motion:layout_constraintTop_toTopOf="parent">
        <CustomAttribute
            motion:attributeName="backgroundColor"
            motion:customColorValue="#9999FF" />
    </Constraint>
</ConstraintSet>

Additional MotionLayout attributes

In addition to the attributes in the example above, MotionLayout has other attributes that you might want to specify:

  • app:applyMotionScene="boolean" indicates whether to apply the MotionScene. The default value for this attribute is true.
  • app:showPaths="boolean" indicates whether to show the motion paths as the motion is running. The default value for this attribute is false.
  • app:progress="float" lets you explicitly specify transition progress. You can use any floating-point value from 0 (the start of the transition) to 1 (the end of the transition).
  • app:currentState="reference" lets you specify a specific ConstraintSet.
  • app:motionDebug lets you display additional debug information about the motion. Possible values are "SHOW_PROGRESS", "SHOW_PATH", or "SHOW_ALL".

Additional resources

For more information on MotionLayout, see the following links: