In this tutorial you will learn about motionlayout and look at examples of how to use it to manage animations in your app.
What is MotionLayout?
But first what is it?
MotionLayout is a layout type that helps you manage motion and widget animation in your app.
This class subclasses and builds upon the flexible ConstraintLayout
. It is thus available starting from API level 14. It bridges the gap between layout transitions and complex motion handling, offering a mix of features between the property animation framework, TransitionManager, and CoordinatorLayout.
You can read more about motionlayout here.
Example 1: MotionLayout – Parallax Scrolling using MotionLayout
This is a simple example that introduces you to motionlayout by implementing Parallax scrolling effect on content.
This is important especially on detail pages to provide a modern experience to users. Here’s a demonstration of what is being built:
Step 1: Dependencies
No third party dependency is needed for this project.
Step 2: Define Motions
You need to define motions as xml resource files. First create folder known as xml
under your res
directory and add the following two scenes:
scene_header.xml
In this file define a root MotionScene
element:
<MotionScene
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:motion="http://schemas.android.com/apk/res-auto">
....
Define a transition, passing in the start, the end as well as duration:
<Transition
motion:constraintSetStart="@+id/start"
motion:constraintSetEnd="@+id/end"
motion:duration="1000"
motion:motionInterpolator="linear">
<OnSwipe
motion:touchAnchorId="@+id/image"
motion:touchAnchorSide="bottom"
motion:dragDirection="dragUp"/>
Here’s the full code:
<?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"
motion:motionInterpolator="linear">
<OnSwipe
motion:touchAnchorId="@+id/image"
motion:touchAnchorSide="bottom"
motion:dragDirection="dragUp"/>
<ConstraintSet android:id="@+id/start">
<Constraint
android:id="@id/image"
android:layout_width="match_parent"
android:layout_height="220dp"
android:alpha="1.0"
motion:layout_constraintTop_toTopOf="parent"/>
<Constraint
android:id="@id/label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
motion:layout_constraintBottom_toBottomOf="@+id/guideline"
motion:layout_constraintStart_toStartOf="parent"/>
<Constraint
android:id="@id/guideline"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
motion:layout_constraintGuide_begin="210dp"/>
</ConstraintSet>
<ConstraintSet android:id="@+id/end">
<Constraint
android:id="@id/image"
android:layout_width="match_parent"
android:layout_height="220dp"
android:alpha="0"
android:translationX="0dp"
android:translationY="0dp"
motion:layout_constraintTop_toTopOf="parent"/>
<Constraint
android:id="@id/label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:scaleX="0.6"
android:scaleY="0.6"
motion:layout_constraintBottom_toBottomOf="@+id/guideline"
motion:layout_constraintStart_toStartOf="parent"/>
<Constraint
android:id="@id/guideline"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
motion:layout_constraintGuide_begin="56dp"/>
</ConstraintSet>
</Transition>
</MotionScene>
scene_main.xml
<?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="300"
motion:motionInterpolator="linear">
<OnSwipe
motion:touchAnchorId="@+id/header"
motion:touchAnchorSide="bottom"
motion:dragDirection="dragUp" />
<ConstraintSet android:id="@+id/start">
<Constraint
android:id="@id/header"
android:layout_width="match_parent"
android:layout_height="220dp"
motion:layout_constraintTop_toTopOf="parent" />
<Constraint
android:id="@id/contents"
android:layout_width="match_parent"
android:layout_height="0dp"
motion:layout_constraintTop_toBottomOf="@+id/header"
motion:layout_constraintBottom_toBottomOf="parent" />
</ConstraintSet>
<ConstraintSet android:id="@+id/end">
<Constraint
android:id="@+id/header"
android:layout_width="match_parent"
android:layout_height="56dp"
motion:layout_constraintTop_toTopOf="parent"
motion:progress="1"/>
<Constraint
android:id="@id/contents"
android:layout_width="match_parent"
android:layout_height="0dp"
motion:layout_constraintTop_toBottomOf="@+id/header"
motion:layout_constraintBottom_toBottomOf="parent" />
</ConstraintSet>
</Transition>
</MotionScene>
Step 3: Create Layouts
The next step is to create your xml layouts. There are three such layouts:
(a). header_scroll.xml
The root element in this layout will be the MotionLayout
:
<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/header"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#000000"
app:layoutDescription="@xml/scene_header">
<ImageView
android:id="@+id/image"
android:layout_width="match_parent"
android:layout_height="220dp"
android:scaleType="centerCrop"
android:src="@drawable/flower"/>
<TextView
android:id="@+id/label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Lorem Ipsum"
android:textSize="30dp"
android:padding="10dp"
android:textColor="#FFFFFF"/>
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guideline"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintGuide_begin="210dp"/>
</androidx.constraintlayout.motion.widget.MotionLayout>
(b). contents_scroll.xml
In the content, place a textview inside a NestedScrollView, this will be the widget to show the details content:
<?xml version="1.0" encoding="utf-8"?>
<androidx.core.widget.NestedScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/contents"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/large_text"
android:padding="10dp"/>
</androidx.core.widget.NestedScrollView>
(c). activity_main.xml
This is a simple layou. Simply include the header_scroll
and content_scroll
inside a MotionLayout
in this layout:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.motion.widget.MotionLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layoutDescription="@xml/scene_main"
tools:context=".MainActivity">
<include layout="@layout/header_scroll"/>
<include layout="@layout/contents_scroll"/>
</androidx.constraintlayout.motion.widget.MotionLayout>
Step : Write Code
The next step is to write your MainActivity
code, in this case in Kotlin Programming language. This will be your launcher activity.
Here’s the full code:
MainActivity.kt
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
}
Step : Run Code
Afetr following the abobe steps run the code in android studio. Scroll above to the description of this project to see the result.
Reference
You can find the download links to the project below:
Number | Link |
---|---|
1. | Download code |
2. | Follow code author |
More Examples
Here are more examples
1. Kotlin Android MotionLayout Carousel Example
This is MotionLayout Carousel step by step example.
This example will comprise the following files:
MainActivity.kt
Step 1: Create Project
- Open your
AndroidStudio
IDE. - Go to
File-->New-->Project
to create a new project.
Step 2: Add Dependencies
No third party dependency is needed for this project.
Step 3: Design Layouts
(a). activity_main.xml
Create a file named activity_main.xml
and design it as follows:
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout 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"
tools:context=".MainActivity">
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/Theme.MotionCarouselExample.AppBarOverlay">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:popupTheme="@style/Theme.MotionCarouselExample.PopupOverlay" />
</com.google.android.material.appbar.AppBarLayout>
<androidx.constraintlayout.motion.widget.MotionLayout
android:id="@+id/constraintLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layoutDescription="@xml/carousel_scene"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<TextView
android:id="@+id/textView0"
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_marginEnd="16dp"
android:text="textView0"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/textView1"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/textView1"
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_marginEnd="16dp"
android:text="textView1"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/textView2"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/textView2"
android:layout_width="150dp"
android:layout_height="150dp"
android:text="textView2"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/textView3"
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_marginStart="16dp"
android:text="textView3"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@+id/textView2"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/textView4"
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_marginStart="16dp"
android:text="textView4"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@+id/textView3"
app:layout_constraintTop_toTopOf="parent" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guideline"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_begin="100dp" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guideline2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_end="100dp" />
<androidx.constraintlayout.helper.widget.Carousel
android:id="@+id/carousel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:carousel_infinite="true"
app:carousel_backwardTransition="@+id/backward"
app:carousel_firstView="@+id/textView2"
app:carousel_forwardTransition="@+id/forward"
app:carousel_nextState="@+id/next"
app:carousel_emptyViewsBehavior="invisible"
app:carousel_previousState="@+id/previous"
app:constraint_referenced_ids="textView0,textView1,textView2,textView3,textView4" />
</androidx.constraintlayout.motion.widget.MotionLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
Step 4: Write Code
Write Code as follows:
(a). MainActivity.kt
Create a file named MainActivity.kt
Here is the full code
package jp.numero.carousel_example
import android.graphics.Color
import android.os.Bundle
import android.view.View
import android.widget.ImageView
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.constraintlayout.helper.widget.Carousel
import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
import org.w3c.dom.Text
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
setSupportActionBar(findViewById(R.id.toolbar))
val list = listOf(
Color.MAGENTA,
Color.RED,
Color.CYAN,
Color.BLUE,
Color.GREEN,
Color.YELLOW,
Color.LTGRAY
).mapIndexed { index, color ->
Item(index.toString(), color)
}.toMutableList()
val carousel = findViewById<Carousel>(R.id.carousel)
carousel.setAdapter(object : Carousel.Adapter {
override fun count(): Int = list.size
override fun populate(view: View, index: Int) {
if (view !is TextView) return
val item = list[index]
//view.text = item.text
view.setBackgroundColor(item.color)
}
override fun onNewItem(index: Int) {
}
})
}
}
data class Item(
val text: String,
val color: Int
)
Run
Simply copy the source code into your Android Project,Build and Run.
Reference
2. Kotlin Android MotionLayout Pager Example
Let us look at a Android MotionLayoutPager using this example. Basically we implement a ViewPager using MotionLayout
.
Here is the demo GIF:
This example will comprise the following files:
MainActivity.kt
Step 1: Create Project
- Open your
AndroidStudio
IDE. - Go to
File-->New-->Project
to create a new project.
Step 2: Add Dependencies
In your app/build.gradle
add dependencies as shown below:
First enable Java8:
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
}
No third party dependency is needed. We use the standard androidx libraries. Make sure to include ConstraintLayout
:
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.core:core-ktx:1.1.0'
implementation 'com.google.android.material:material:1.2.0-alpha03'
implementation 'androidx.constraintlayout:constraintlayout:2.0.0-beta4'
}
Step 3: Design Layouts
(a). activity_main.xml
Create a file named activity_main.xml
and design it as follows:
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout 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"
tools:context=".MainActivity">
<com.google.android.material.appbar.AppBarLayout
style="@style/Widget.MaterialComponents.AppBarLayout.Primary"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar"
style="@style/Widget.MaterialComponents.Toolbar.Primary"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize" />
</com.google.android.material.appbar.AppBarLayout>
<androidx.constraintlayout.motion.widget.MotionLayout
android:id="@+id/motionLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layoutDescription="@xml/motion_list"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<com.google.android.material.card.MaterialCardView
android:id="@+id/leftView"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:cardElevation="4dp">
<TextView
android:id="@+id/leftTextView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:gravity="center"
android:textAppearance="?attr/textAppearanceHeadline5"
android:textColor="?attr/colorOnSurface" />
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.card.MaterialCardView
android:id="@+id/rightView"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:cardElevation="4dp">
<TextView
android:id="@+id/rightTextView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:gravity="center"
android:textAppearance="?attr/textAppearanceHeadline5"
android:textColor="?attr/colorOnSurface" />
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.card.MaterialCardView
android:id="@+id/centerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:cardElevation="4dp">
<TextView
android:id="@+id/centerTextView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:gravity="center"
android:textAppearance="?attr/textAppearanceHeadline5"
android:textColor="?attr/colorOnSurface" />
</com.google.android.material.card.MaterialCardView>
</androidx.constraintlayout.motion.widget.MotionLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
Step 4: Write Code
Write Code as follows:
(a). MainActivity.kt
Create a file named MainActivity.kt
Here is the full code
package com.example.motionlayout_pager_example
import android.annotation.SuppressLint
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import android.view.Menu
import android.view.MenuItem
import androidx.constraintlayout.motion.widget.MotionLayout
import kotlinx.android.synthetic.main.activity_main.*
class MainActivity : AppCompatActivity() {
private var currentPosition = 0
private val itemList = listOf(
1, 2, 3, 4, 5, 6, 7, 8, 9, 10
)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
setSupportActionBar(toolbar)
setupMotion()
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.menu_main, menu)
return true
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) {
R.id.action_settings -> true
else -> super.onOptionsItemSelected(item)
}
}
private fun setupMotion() {
motionLayout.setTransitionListener(object : MotionLayout.TransitionListener {
override fun onTransitionTrigger(motionLayout: MotionLayout?, triggerId: Int, positive: Boolean, progress: Float) {
}
override fun onTransitionStarted(motionLayout: MotionLayout?, startedId: Int, endId: Int) {
}
override fun onTransitionChange(motionLayout: MotionLayout?, startId: Int, endId: Int, progress: Float) {
}
override fun onTransitionCompleted(motionLayout: MotionLayout?, currentId: Int) {
when (currentId) {
R.id.move_left_to_right -> {
if (currentPosition > 0) {
currentPosition--
} else {
currentPosition = itemList.lastIndex
}
motionLayout?.progress = 0F
updateView()
}
R.id.move_right_to_left -> {
if (currentPosition < itemList.lastIndex) {
currentPosition++
} else {
currentPosition = 0
}
motionLayout?.progress = 0F
updateView()
}
}
}
})
updateView()
}
@SuppressLint("SetTextI18n")
private fun updateView() {
centerTextView.text = "Itemn${itemList[currentPosition]}"
rightTextView.text = if (currentPosition == itemList.lastIndex) {
"Itemn${itemList.first()}"
} else {
"Itemn${itemList[currentPosition + 1]}"
}
leftTextView.text = if (currentPosition == 0) {
"Itemn${itemList.last()}"
} else {
"Itemn${itemList[currentPosition - 1]}"
}
}
}
Run
Simply copy the source code into your Android Project,Build and Run.
Reference
Have a good day.