Getting started with Motion Layout in Android

Kruti Kamani
Mindful Engineering
6 min readNov 16, 2021
Image Credit [https://awesomeopensource.com]

MotionLayout is the new layout in Android, to create amazing interactive animations. It is a subclass of ConstraintLayout that helps to manage motion and widget animation. ConstraintLayout allows MotionLayout support to older devices from API level 14.

We can define constraints just like we would do in ConstraintLayout. The difference is that MotionLayout builds upon its capabilities and we can now describe layout transitions and animate view properties changes.

MotionLayout is intended to move, resize, and animate UI elements with which users interact, such as buttons and title bars.

The amazing thing about MotionLayout is that it’s fully declarative. All transitions and animations might be described purely in XML.

Why MotionLayout?

The Android framework already provides several ways of adding animation in our application, such as:

  • Animated Vector Drawable
  • Property Animation framework
  • LayoutTransition animations
  • Layout transitions with TransitionManager
  • CoordinatorLayout

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

It lets you describe the transition between two layouts (like TransitionManager), but can also animate any properties (not just layout attributes). It supports touch handling and keyframes, allowing developers to easily customize transitions to their own needs.

The other key difference is that MotionLayout is fully declarative, you can fully describe in XML a complex transition, no code is expected (if you need to express motion by code, the existing property animation framework already provides a great way of doing it).

Adding MotionLayout to your project

  • Add the ConstraintLayout dependency: To get started with MotionLayout, you need to make sure we have the latest version of ConstarintLayout in your build.gradlefile.
implementation ‘androidx.constraintlayout:constraintlayout:2.1.1’
  • Create a MotionLayout file: MotionLayout is a subclass of ConstraintLayout, so you can transform an existing ConstraintLayout into a MotionLayout is as easy as replacing the class name from:
<android.support.constraint.ConstraintLayout …/>

to MotionLayout:

<androidx.constraintlayout.motion.widget.MotionLayout …/>
  • Create a MotionScene: A MotionScene is an XML resource file that contains all of the motion descriptions for the corresponding layout.
Image Credit[https://miro.medium.com]

The main difference between ConstraintLayout and MotionLayout at the XML level is that the actual description of what MotionLayout will do is not necessarily contained in the layout file.

Rather, MotionLayout keeps all this information in a separate XML file (a MotionScene). This way, the layout file can contain only the Views and their properties, not their positioning or movement.

MotionLayout uses a mechanism called Motion Scene to create all animations. If you are using MotionLayout, then you should be familiar with 4 important terms i.e. MotionScene, Transition, KeyFrameSet, ConstraintSet.

  • MotionScene: A MotionScene is an XML resource file that contains all of the motion descriptions for the corresponding layout. In MotionScene, we write all the animations that we want to add to our project.
  • Transition: A transition describes the change from state A to state B. You can add the start constraint using app:constraintSetStart and the final one using app:constraintSetEnd.
  • KeyFrameSet: You can think of an animation as a sequence of frames. Suppose, if we want to move from A to B, then the shortest path will be considered during the transition.
  • ConstraintSet: Finally, you use ConstraintSet definitions to define starting and final constraints, for each of the Views you want to animate.

Now let’s try to make the animation like below using Motion Layout.

First, we need to add ConstraintLayout dependencies to our build.gradle.

Now let’s create an activity with RecyclerView for playlist and container to add PlayerFragment for the player screen.

Now for the next step, let’s create our PlayerFragment and open it on the playlist item click.

Here is the PlayerFragment layout that will be used to create the motion.

The most important parameter here is app:layoutDescription. It references a MotionScene. In this scene, we will define our layout transitions.

Create Motion Scene: This is the file that defines the type of motion/animation we want and what the starting/ending constraints are. (placed in res/xml/player_motion_scene.xml)

<MotionScene xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:motion="http://schemas.android.com/apk/res-auto">

<Transition
motion:constraintSetEnd="@id/collapsed"
motion:constraintSetStart="@id/expanded"
motion:duration="300"
motion:motionInterpolator="easeInOut">

<!--<OnSwipe
motion:dragDirection="dragUp"
motion:maxAcceleration="200"
motion:touchAnchorSide="top"
motion:touchAnchorId="@id/playerBackground" />-->

<OnSwipe
motion:dragDirection="dragDown"
motion:maxAcceleration="200"
motion:touchRegionId="@id/playerBackground" />

</Transition>

</MotionScene>

Here we defined a Transition with duration & start/end constraintSet to the layout. It defines how our widget will move across the screen.

we use the motion:constraintSetStart to set where the transition starts and motion:constraintSetEnd declare the position where the transition will stop. We can also set how long the transition or animation will take using motion:duration.

<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:touchRegionId Limits the region that the touch can be started into the bounds of this view (even if the view is invisible).
  • 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.

Before Alpha 5, MotionLayout tracked touches on the whole MotionLayout and then used the provided touchAnchorId to calculate the progress of the transition, based on the touchAnchorSide . This didn’t cover use cases where you would want to track only swipes in a specific region or on specific views.

To solve the above issue, MotionLayout Alpha 5 introduces a new feature called Touch Regions.

Now create two ConstraintSets, one for the first position (collapsed), and a second one for the second position (expanded).

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

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

Now we will add the KeyFrameSet for animation. KeyFrameSet is a child element of Transition and it can contain KeyPosition, KeyAttribute, and KeyCycle elements for defining middle states of animation.

  • KeyPosition is used to add a position through which the target element passes when it is animated.
  • KeyAttribute is used to add values for attributes for the middle state. You can assign values for standard attributes by directly using the name of properties as attributes of the KeyAttribute element.
  • KeyCycle is used to add oscillations during animation. You can specify the point on the animation at which you want oscillation by using the framePosition attribute.
<KeyFrameSet>

<KeyAttribute
android:alpha="1.0"
motion:framePosition="0"
motion:motionTarget="@id/artistName"/>

<KeyAttribute
android:alpha="0.0"
motion:framePosition="70"
motion:motionTarget="@id/artistName"/>

<KeyAttribute
android:alpha="1.0"
motion:framePosition="100"
motion:motionTarget="@id/artistName"/>

<KeyAttribute
android:alpha="1.0"
motion:framePosition="0"
motion:motionTarget="@id/audioName"/>

<KeyAttribute
android:alpha="0.0"
motion:framePosition="70"
motion:motionTarget="@id/audioName"/>

<KeyAttribute
android:alpha="1.0"
motion:framePosition="100"
motion:motionTarget="@id/audioName"/>

</KeyFrameSet>

Here is the full layout of the player_motion_scene.xml file.

Now, we have a problem here: MotionLayout covers the whole screen so when the MotionLayout content is collapsed, the recyclerview behind the collapsed layout will not screen due to touch. So we want it to react to only playerBackground touches and not to the whole content.

To resolve the above issue, we need to create SingleViewTouchableMotionLayout to handle only the touches of playerBackground.

So now we will use SingleViewTouchableMotionLayout instead of MotionLayout in our fragment_player.xml.

<com.example.motionlayoutdemoapp.SingleViewTouchableMotionLayout
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/audioMotionLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layoutDescription="@xml/player_motion_scene">
</com.example.motionlayoutdemoapp.SingleViewTouchableMotionLayout>

Now our MotionLayout will handle touches only from playerBackground.

MotionLayout Attributes for development

MotionLayout itself has a few attributes which we want to specify during the development.

  • app:layoutDescription =”reference” has to point the motion scene XML file
  • app:applyMotionScene =”boolean” apply or not the motion scene
  • app:showPaths =”boolean” display or not the motion paths
  • app:progress =”float” to specify the transition progress from 0 to 1
  • app:currentState =”reference” force a specific constraint set

Custom attribute

We can take advantage of this to specify transition on attributes that are not related to the position only. For richer animations, we often need to transition other things (for example, a background color).

In the above example, when the view collapsed the background color will change and the layout’s corners will be rounded. You can specify the attribute directly in XML:

<Constraint
android:id="@+id/playerBackground"
android:layout_width="0dp"
android:layout_height="100dp"
android:layout_marginStart="10dp"
android:layout_marginEnd="10dp"
android:layout_marginBottom="20dp"
motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintStart_toStartOf="parent">

<CustomAttribute
motion:attributeName="cardBackgroundColor"
motion:customColorValue="@color/light_pink" />

<CustomAttribute
motion:customDimension="20dp"
motion:attributeName="radius" />

</Constraint>

CustomAttributes are specified with the attributeName, which needs to match the getter/setter methods of an object such that:

  • getter: getName (e.g. getBackgroundColor)
  • setter: setName (e.g. setBackgroundColor)

The value type also needs to be specified:

  • customColorValue
  • customIntegerValue
  • customFloatValue
  • customStringValue
  • customDimension
  • customBoolean

Note: When defining a custom attribute, you need to define it in the both the start and the end ConstraintSet.

Thank you for reading. Hope you enjoyed it!

--

--