Table of Contents
Introduction to MotionLayout
For a long time, Android developers have suffered hardships when trying to create animations in our apps. Native Android libraries didn’t make it easy to create simple animations, like the “swipe” when you delete an element from a list. This forces us to create a personalized view that will detect the user’s movement while we must use that “input” to progressively move the element accordingly and simultaneously apply the logic that, at a specific time, the element must disappear completely.
During the past years, with the incorporation of Material Design, there’ve been some changes to libraries, from CoordinatorLayout, which let’s us have a view of an easily collapsible section, to ConstraintLayout, where we can assign anchors to a view to extremes of both the screen and other elements of said view, avoiding having it grouped in blocks.
With this last input, since late 2018, the native library got updated to its 2.0 version and with that came MotionLayout, a class extending from ConstraintLayout that allows us to animate our elements inside the “layout” through a scene, a XML file, where we’ll describe the animation’s behavior from start to finish, like a vector animation in a design program, and which events will start this animation (clicking, swiping, dropping…)
Step by Step
In many applications, you might have seen the classic profile or category view, where we have a big image and an associated text that, after scrolling the screen, reduces its size to a corner with the text at its side.
Before, doing that action was very tiresome. With MotionLayout we’ll see an easy way to do that using scenes.
We start by creating the project and adding the constraintLayout
library to our app.gradle archive using one of these lines:
// Support library
implementation 'com.android.support.constraint:constraint-layout:2.0.0-beta2'
// or AndroidX packages
implementation 'androidx.constraintlayout:constraintlayout:2.0.0-beta2'
In this example we’ll use the AndroidX library, but you can use that which suits you best.
After applying the library the next step will be to create the view which we want to animate. As I mentioned above, we’ll create a view anchored to a list, a header, where its elements will move based on the scroll done on the list. To that end, in our view we’ll make MotionLayout the parent group and we’ll add the associated elements.
<?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">
<View
android:id="@+id/background"
android:layout_width="match_parent"
android:layout_height="320dp"
android:background="@color/colorPrimary"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="@+id/imageView"
android:layout_width="180dp"
android:layout_height="180dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:src="@tools:sample/avatars" />
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:textSize="24sp"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/imageView"
tools:text="@tools:sample/full_names" />
<TextView
android:id="@+id/textView2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:textSize="18sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/textView"
tools:text="@tools:sample/cities" />
</androidx.constraintlayout.motion.widget.MotionLayout>
For now, we have a layout applying some constraints exactly like we’d do with a ConstraintLayout, only we’re using MotionLayout, which will allow us to associate a scene to it. These constraints will work as an initial state of our view before starting the action that will execute the animation.
Now we’ll add a list; for that, I’ll use RecyclerView, which is included in the Material Design library, and we’ll add it with one of these lines:
// Support library
implementation 'com.android.support:design:28.0.0'
// or AndroidX
implementation 'com.google.material:material:1.1.0'
We’ll include the list into our previously created MotionLayout, and with that we’re ready to create our animation. To do that, we’ll create a scene where the root will be MotionScene, an XML file where we’ll specify our elements transition from the starting to the ending position during that gesture (in our case, the list scroll).
<MotionScene xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<Transition
app:constraintSetEnd="@id/end"
app:constraintSetStart="@id/start"
app:duration="1000">
<OnSwipe
app:dragDirection="dragUp"
app:touchAnchorId="@id/recyclerView"
app:touchAnchorSide="top" />
</Transition>
</MotionScene>
The constraintSetStart
and constraintSetEnd
attributes are a reference to the initial and final states that we want for our animation.
The OnSwipe
tag will allow us to assign that the transition will be executed with this type of gesture. The dragDirection
attribute will indicate the swipe direction and the touchAnchorId
and touchAnchorSide
attributes reference the list where we’ll apply the scroll and from which area the gesture begins, in our case top
.
We’ll also have to create a set of constraints for our animation’s initial and final states, where each constraint will reference our element’s id in the layout.
<ConstraintSet android:id="@+id/start">
<Constraint android:id="@+id/background">
<PropertySet android:alpha="1" />
</Constraint>
</ConstraintSet>
<ConstraintSet android:id="@+id/end">
<Constraint
android:id="@+id/background"
android:layout_height="80dp"
android:alpha="0"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Constraint
android:id="@+id/imageView"
android:layout_width="64dp"
android:layout_height="64dp"
android:layout_marginStart="8dp"
app:layout_constraintBottom_toBottomOf="@id/background"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
...
</ConstraintSet>
On each set, we’ll need to identify which element we want to modify through the Constraint
tag and assign the desired attribute to it, for example, our imageView
, which in its final state should reduce its size, plus it no longer has the layout_constraintEnd_ToEndOf
attribute so it sticks to the left end of the screen.
We can also modify visibility states using the PropertySet
tag inside the Constraint
tag, where we can assign the alpha
attribute to our header’s background.
And done! With MotionLayout we can create a list with its collapsed header in an efficient manner and animate its elements.
To see the complete example, https://github.com/raeve/motionlayout-example, and if you want to learn more, here’s the complete documentation: https://developer.android.com/training/constraint-layout/motion-layout