MotionLayout is a powerful layout type in Android that allows you to create complex animations and transitions in a declarative way. It’s part of Android’s ConstraintLayout library and builds upon its flexibility, adding the capability to animate between different constraint sets. One of the core components of MotionLayout is the MotionScene, defined in an XML file, which dictates the animations and transitions.
What is a MotionScene?
A MotionScene is an XML resource file that defines the animation sequences for a MotionLayout. It contains details about how different constraints (start and end states) animate, triggers for transitions, and custom attributes for animation.
Why Use a MotionScene?
- Declarative Animations: Defines animations in XML rather than programmatically, making it easier to manage complex transitions.
- Lifecycle-Aware: Integrates well with Android’s lifecycle.
- Constraint-Based Animations: Leverages ConstraintLayout to easily animate changes in constraints.
- Reduced Code Complexity: Simplifies animation logic compared to traditional animation methods.
How to Create a MotionScene for MotionLayout in Kotlin/XML
To implement MotionLayout animations using a MotionScene, follow these steps:
Step 1: Add the Dependency
Ensure that you have ConstraintLayout and AppCompat dependencies in your build.gradle file:
dependencies {
implementation("androidx.constraintlayout:constraintlayout:2.1.4")
implementation("androidx.appcompat:appcompat:1.6.1")
}
Note that MotionLayout functionalities are bundled within the ConstraintLayout library, so having it is sufficient.
Step 2: Create a MotionLayout Layout File
First, create a MotionLayout XML layout file. This is where you’ll declare your views and specify which MotionScene to use.
<androidx.constraintlayout.motion.widget.MotionLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/motionLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layoutDescription="@xml/scene_01">
<View
android:id="@+id/viewA"
android:layout_width="64dp"
android:layout_height="64dp"
android:background="@color/design_default_color_primary"
android:contentDescription="View A"/>
</androidx.constraintlayout.motion.widget.MotionLayout>
Here, app:layoutDescription="@xml/scene_01" links this layout file to the MotionScene.
Step 3: Create a MotionScene XML File
Next, create the MotionScene XML file (e.g., res/xml/scene_01.xml). This file will define the animation states and transitions.
<MotionScene xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<ConstraintSet android:id="@id/start">
<Constraint android:id="@id/viewA">
<PropertySet android:alpha="0.5" />
<Layout
android:layout_width="64dp"
android:layout_height="64dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"/>
</Constraint>
</ConstraintSet>
<ConstraintSet android:id="@id/end">
<Constraint android:id="@id/viewA">
<PropertySet android:alpha="1.0" />
<Layout
android:layout_width="128dp"
android:layout_height="128dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_marginEnd="8dp"
android:layout_marginBottom="8dp"/>
</Constraint>
</ConstraintSet>
<Transition
android:id="@+id/transition"
app:constraintSetStart="@id/start"
app:constraintSetEnd="@id/end"
app:duration="1000">
<OnSwipe
app:dragDirection="dragRight"
app:touchAnchorId="@id/viewA"
app:touchAnchorSide="right" />
</Transition>
</MotionScene>
In this file:
ConstraintSetwithandroid:id="@id/start"defines the starting state.ConstraintSetwithandroid:id="@id/end"defines the ending state.Transitionlinks the start and end states, specifying animation details such as duration and touch-based triggers.OnSwipedefines a swipe gesture as a trigger to start this animation
Step 4: Link the MotionScene in Kotlin
In your Activity or Fragment, reference the MotionLayout and trigger the transition.
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.constraintlayout.motion.widget.MotionLayout
import androidx.constraintlayout.widget.ConstraintSet
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val motionLayout: MotionLayout = findViewById(R.id.motionLayout)
// Optionally, programmatically trigger transitions
// motionLayout.transitionToEnd() // or transitionToStart(), transitionToState()
}
}
The MotionLayout in your layout will automatically apply the animations specified in scene_01.xml when the layout is inflated. If you need to control the animation programmatically (like in response to a button click), you can use methods like transitionToEnd() or transitionToStart().
Step 5: Adding Custom Attributes
Custom attributes let you animate properties that are not directly supported by default in MotionLayout. You can define custom attributes inside the Constraint element.
For example, animating a custom view property named `customProgress`:
<ConstraintSet android:id="@id/start">
<Constraint android:id="@id/viewA">
<CustomAttribute
app:attributeName="customProgress"
app:customFloatValue="0.0"/>
<Layout
android:layout_width="64dp"
android:layout_height="64dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"/>
</Constraint>
</ConstraintSet>
<ConstraintSet android:id="@id/end">
<Constraint android:id="@id/viewA">
<CustomAttribute
app:attributeName="customProgress"
app:customFloatValue="1.0"/>
<Layout
android:layout_width="128dp"
android:layout_height="128dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_marginEnd="8dp"
android:layout_marginBottom="8dp"/>
</Constraint>
</ConstraintSet>
In your custom view, you’ll need to handle this attribute using a setter method. For example, in Kotlin:
class CustomView(context: Context, attrs: AttributeSet?) : View(context, attrs) {
var customProgress: Float = 0f
set(value) {
field = value
invalidate() // Redraw the view when the property changes
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
// Use customProgress to draw based on the animation state
val paint = Paint().apply {
color = Color.BLUE
}
canvas.drawRect(0f, 0f, width * customProgress, height.toFloat(), paint)
}
}
Complete Example XML Layout:
<?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:id="@+id/motionLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layoutDescription="@xml/scene_01"
tools:context=".MainActivity">
<CustomView
android:id="@+id/viewA"
android:layout_width="64dp"
android:layout_height="64dp"
android:background="@color/design_default_color_primary"
android:contentDescription="View A"/>
</androidx.constraintlayout.motion.widget.MotionLayout>
Conclusion
Using MotionScene in Kotlin/XML Android development enables the creation of sophisticated animations with relative ease. By declaring animation states and transitions in XML, you reduce code complexity and leverage the declarative approach to define interactive and dynamic user interfaces. The MotionLayout and MotionScene combo enhances your Android applications with visually rich experiences that were traditionally cumbersome to implement.