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:

Android MotionLayout Example

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

  1. Open your AndroidStudio IDE.
  2. 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

  1. Download code or Browse here.
  2. Follow code author.

Read Individually.


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:

Android MotionLayoutPager Example

This example will comprise the following files:

  • MainActivity.kt

Step 1: Create Project

  1. Open your AndroidStudio IDE.
  2. 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

  1. Download code or Browse here.
  2. Follow code author.

Read Individually.


Have a good day.