Static screens are boring. In a world of fluid, gesture-driven interfaces, users expect apps to feel alive. But creating complex, interactive animations in Android has historically been a painful process, often involving multiple XML animators, state management, and tricky calculations in code.
Enter MotionLayout. It's not just another animation API; it's a complete framework for building fully declarative, interactive, and physics-based motion. It allows you to describe complex transitions between layouts entirely in XML, making it one of the most powerful tools for creating stunning UIs.
What is MotionLayout and Why is it Different?
MotionLayout is a subclass of ConstraintLayout
. This is key. It builds on the power of constraints to animate layout changes over time. Instead of thinking "how do I animate from A to B?", you simply define what the layout looks like at state A and what it looks like at state B. MotionLayout handles the entire animation between them.
The magic happens in a separate XML file called a MotionScene. This file describes:
- ConstraintSets: The start and end states of your layout.
- Transitions: How to get from one state to another, including duration, easing, and triggers.
- KeyFrames: Intermediate points in the animation to create more complex, non-linear motion paths (like arcs).
- OnSwipe/OnClick Handlers: How user gestures, like swiping or tapping, can drive the animation.
Let's Build: A Simple Collapsing Header
Let's create a common animation: a header that shrinks and fades as the user scrolls up. This is notoriously tricky to do manually, but straightforward with MotionLayout.
Step 1: The Layout File
Your layout file will use a MotionLayout
as the root. Inside, you define all the views that will be part of the animation.
<androidx.constraintlayout.motion.widget.MotionLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layoutDescription="@xml/activity_main_scene">
<ImageView
android:id="@+id/header_image"
android:layout_width="0dp"
android:layout_height="200dp"
android:src="@drawable/header_background"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<TextView
android:id="@+id/header_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Full Title"
android:textSize="24sp"
android:textColor="@android:color/white"
app:layout_constraintBottom_toBottomOf="@id/header_image"
app:layout_constraintStart_toStartOf="@id/header_image"
android:layout_margin="16dp" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintTop_toBottomOf="@id/header_image"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
</androidx.constraintlayout.motion.widget.MotionLayout>
Notice the line app:layoutDescription="@xml/activity_main_scene"
. This links our layout to the MotionScene file.
Step 2: The MotionScene File
Create a new XML file in your res/xml/
directory named activity_main_scene.xml
. This is where we define the animation itself.
<MotionScene xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:motion="http://schemas.android.com/apk/res-auto">
<!-- A transition describes an animation between two states -->
<Transition
motion:constraintSetEnd="@+id/end"
motion:constraintSetStart="@id/start"
motion:duration="1000">
<!-- This links the animation progress to the scroll of the RecyclerView -->
<OnSwipe
motion:touchAnchorId="@id/recycler_view"
motion:dragDirection="dragUp" />
</Transition>
<!-- The "start" state (header fully expanded) -->
<ConstraintSet android:id="@+id/start">
<!-- We can leave this empty to use the constraints from the layout file -->
</ConstraintSet>
<!-- The "end" state (header collapsed) -->
<ConstraintSet android:id="@+id/end">
<Constraint
android:id="@+id/header_image"
android:layout_width="0dp"
android:layout_height="56dp" <!-- Make the header smaller -->
motion:layout_constraintTop_toTopOf="parent"
motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintEnd_toEndOf="parent" />
<Constraint
android:id="@+id/header_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:alpha="0.0" <!-- Fade out the title -->
motion:layout_constraintBottom_toBottomOf="@id/header_image"
motion:layout_constraintStart_toStartOf="@id/header_image"
android:layout_margin="16dp" />
</ConstraintSet>
</MotionScene>
And that's it! With just these two files, you have a fully interactive, scroll-driven animation. The OnSwipe
tag automatically links the user's scroll gesture on the RecyclerView
to the progress of the animation. As they scroll up, MotionLayout will smoothly animate all the properties (height, alpha, etc.) from the start
ConstraintSet to the end
ConstraintSet.
MotionLayout opens up a new world of possibilities for Android UIs. It allows you to build the kind of delightful, physics-based interactions that were previously reserved for only the most expert developers. Start simple, explore the visual editor in Android Studio, and begin bringing your app to life.