Skip to content

MotionLayout Example

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

How to Animated Profile Page with MotionLayout

Android Sample to use MotionLayout written in Kotlin..

Here is the demo GIF:

MotionLayout Tutorial

Let us look at a full MotionLayout Example code:

Step 1. Dependencies

We need to add some dependencies in our app/build.gradle file as shown below:

(a). build.gradle

Our app-level build.gradle.

We Prepare our dependencies as shown below. You may use later versions.

At the top of our app/build.gradle we will apply the following 3 plugins:

  1. Our com.android.application plugin.
  2. Our kotlin-android plugin.
  3. Our kotlin-android-extensions plugin.

We then declare our app dependencies under the dependencies closure, using the implementation statement. We will need the following 5 dependencies:

  1. Our Kotlin-stdlib-jdk7 library.
  2. Material - Collection of Modular and customizable Material Design UI components for Android.
  3. Appcompat - Allows us access to new APIs on older API versions of the platform (many using Material Design).
  4. Core-ktx - With this we can target the latest platform features and APIs while also supporting older devices.
  5. Constraintlayout - This allows us to Position and size widgets in a flexible way with relative positioning.

Here is our full app/build.gradle:

apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'

android {
    compileSdkVersion 29
    buildToolsVersion "29.0.2"

    defaultConfig {
        applicationId "ir.alirezaiyan.profile"
        minSdkVersion 16
        targetSdkVersion 29
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }

}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
    implementation 'com.google.android.material:material:1.2.0-alpha04'
    implementation 'androidx.appcompat:appcompat:1.1.0'
    implementation 'androidx.core:core-ktx:1.1.0'
    implementation 'androidx.constraintlayout:constraintlayout:2.0.0-beta3'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'androidx.test.ext:junit:1.1.1'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
}

Step 2. Create Motion XML

Create a directory known as xml inside your res directory and add the following profile_motion transition animations:

(a). profile_motion.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:constraintSetEnd="@id/end"
        motion:constraintSetStart="@id/start">

        <OnSwipe
            motion:dragDirection="dragUp"
            motion:touchAnchorId="@id/recyclerview"
            motion:touchAnchorSide="top" />

        <KeyFrameSet>

        </KeyFrameSet>
    </Transition>

    <ConstraintSet android:id="@+id/start">

        <Constraint
            android:id="@+id/recyclerview"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            motion:layout_constraintEnd_toEndOf="parent"
            motion:layout_constraintStart_toStartOf="parent"
            android:translationY="-25dp"
            motion:layout_constraintTop_toBottomOf="@+id/avatarImageView">

        </Constraint>


        <Constraint
            android:id="@+id/avatarImageView"
            android:layout_width="match_parent"
            android:layout_height="250dp"
            android:elevation="2dp"
            android:scaleX="1"
            android:scaleY="1"
            motion:layout_constraintEnd_toEndOf="parent"
            motion:layout_constraintStart_toStartOf="parent"
            motion:layout_constraintTop_toTopOf="parent">

            <CustomAttribute
                motion:attributeName="CornerRadius"
                motion:customIntegerValue="20" />

        </Constraint>

    </ConstraintSet>

    <ConstraintSet android:id="@+id/end">

        <Constraint
            android:id="@+id/recyclerview"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            motion:layout_constraintEnd_toEndOf="parent"
            android:translationY="0dp"
            motion:layout_constraintStart_toStartOf="parent"
            android:layout_marginTop="250dp"
            motion:layout_constraintTop_toTopOf="parent">

        </Constraint>

        <Constraint
            android:id="@+id/avatarImageView"
            android:layout_width="250dp"
            android:layout_height="250dp"
            android:elevation="0dp"
            android:scaleX="0.5"
            android:scaleY="0.5"
            motion:layout_constraintEnd_toEndOf="parent"
            motion:layout_constraintStart_toStartOf="parent"
            motion:layout_constraintTop_toTopOf="@id/recyclerview"
            motion:layout_constraintBottom_toTopOf="@id/recyclerview">

            <CustomAttribute
                motion:attributeName="CornerRadius"
                motion:customIntegerValue="500" />

        </Constraint>

    </ConstraintSet>

</MotionScene>

Step 3. Design Layouts

For this project let's create the following layouts:

(a). item1.xml

Our item1 layout.

This layout will represent our 's layout. Specify androidx.cardview.widget.CardView as it's root element then inside it place the following widgets:

  1. RelativeLayout
  2. ImageView
  3. TextView
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView 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="wrap_content"
    app:cardBackgroundColor="@color/colorPrimary"
    android:layout_marginBottom="10dp"
    app:cardCornerRadius="12dp">

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="200dp">

        <ImageView
            android:layout_width="32dp"
            android:layout_height="32dp"
            android:layout_alignParentRight="true"
            android:layout_centerVertical="true"
            android:layout_marginRight="40dp"
            android:background="?selectableItemBackgroundBorderless"
            android:padding="4dp"
            android:src="@drawable/ic_baseline_star_border_24" />

        <TextView
            android:id="@+id/username"
            style="@style/TextAppearance.AppCompat.Light.SearchResult.Title"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerHorizontal="true"
            android:layout_marginTop="64dp"
            android:text="Alfred cervantes"
            android:textColor="#FFF"
            tools:ignore="HardcodedText" />


        <TextView
            android:id="@+id/phonenumber"
            style="@style/TextAppearance.AppCompat.Small"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_below="@+id/username"
            android:layout_centerHorizontal="true"
            android:layout_marginTop="8dp"
            android:textColor="#FFF"
            tools:ignore="HardcodedText"
            tools:text="(800) 555-8501" />

        <ImageView
            android:id="@+id/voicecall"
            android:layout_width="32dp"
            android:layout_height="32dp"
            android:layout_alignParentBottom="true"
            android:layout_margin="16dp"
            android:background="?selectableItemBackgroundBorderless"
            android:padding="4dp"
            android:src="@drawable/ic_baseline_voicemail_24" />


        <ImageView
            android:id="@+id/videocall"
            android:layout_width="32dp"
            android:layout_height="32dp"
            android:layout_alignBaseline="@id/voicecall"
            android:layout_alignParentBottom="true"
            android:layout_margin="16dp"
            android:layout_toRightOf="@id/voicecall"
            android:background="?selectableItemBackgroundBorderless"
            android:padding="4dp"
            android:src="@drawable/ic_baseline_duo_24" />


    </RelativeLayout>

</androidx.cardview.widget.CardView>

(b). item2.xml

Our item2 layout.

It has the same widgets as the first layout:

  1. RelativeLayout
  2. ImageView
  3. TextView
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView 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="wrap_content"
    android:layout_marginBottom="10dp"
    app:cardBackgroundColor="@color/colorPrimary"
    app:cardCornerRadius="12dp">

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="200dp">

        <ImageView
            android:id="@+id/videocall"
            android:layout_width="48dp"
            android:layout_height="48dp"
            android:layout_alignParentRight="true"
            android:layout_margin="16dp"
            android:background="?selectableItemBackgroundBorderless"
            android:padding="4dp"
            android:src="@drawable/ic_baseline_duo_24" />


        <TextView
            android:id="@+id/phonenumber1"
            style="@style/TextAppearance.AppCompat.Title"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_below="@+id/videocall"
            android:layout_centerHorizontal="true"
            android:textColor="#FFF"
            tools:ignore="HardcodedText"
            android:text="(800) 555-8501" />


        <TextView
            android:id="@+id/phonenumber2"
            style="@style/TextAppearance.AppCompat.Title"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_below="@+id/phonenumber1"
            android:layout_centerHorizontal="true"
            android:layout_marginTop="8dp"
            android:textColor="#FFF"
            tools:ignore="HardcodedText"
            android:text="(800) 555-8502" />


    </RelativeLayout>

</androidx.cardview.widget.CardView>

(c). item3.xml

Our item3 layout.

  1. RelativeLayout
  2. ImageView
  3. TextView
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView 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="wrap_content"
    android:layout_marginBottom="10dp"
    app:cardBackgroundColor="@color/colorPrimary"
    app:cardCornerRadius="12dp">

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="200dp">

        <ImageView
            android:id="@+id/videocall"
            android:layout_width="48dp"
            android:layout_height="48dp"
            android:layout_alignParentRight="true"
            android:layout_margin="16dp"
            android:background="?selectableItemBackgroundBorderless"
            android:padding="4dp"
            android:src="@drawable/ic_baseline_contact_mail_24" />


        <TextView
            android:id="@+id/address1"
            style="@style/TextAppearance.AppCompat.Title"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_below="@+id/videocall"
            android:layout_centerHorizontal="true"
            android:textColor="#FFF"
            tools:ignore="HardcodedText"
            android:text="[email protected]" />


        <TextView
            android:id="@+id/address2"
            style="@style/TextAppearance.AppCompat.Title"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_below="@+id/address1"
            android:layout_centerHorizontal="true"
            android:layout_marginTop="8dp"
            android:textColor="#FFF"
            tools:ignore="HardcodedText"
            android:text="[email protected]" />


    </RelativeLayout>

</androidx.cardview.widget.CardView>

(d). activity_main.xml

Our activity_main layout.

This layout will represent our Main Activity's layout. Specify androidx.constraintlayout.motion.widget.MotionLayout as it's root element then inside it place the following widgets:

  1. RecyclerView
  2. ImageView
  3. AvatarView
<?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/profileLayout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/colorPrimaryDark"
    android:fitsSystemWindows="true"
    app:layoutDescription="@xml/profile_motion"
    tools:context=".MainActivity"
    tools:motionProgress="0.0"
    tools:showPaths="true"
    tools:visibility="visible">


    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recyclerview"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:background="@color/colorPrimaryDark"
        android:clipToPadding="true"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/avatarImageView"
        tools:itemCount="50"
        tools:listitem="@layout/item1" />

    <ImageView
        android:id="@+id/newrelease"
        android:layout_width="32dp"
        android:layout_height="32dp"
        android:layout_margin="8dp"
        android:background="?selectableItemBackgroundBorderless"
        android:padding="4dp"
        android:src="@drawable/ic_baseline_new_releases_24"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <ImageView
        android:id="@+id/menu"
        android:layout_width="32dp"
        android:layout_height="32dp"
        android:layout_margin="8dp"
        android:background="?selectableItemBackgroundBorderless"
        android:padding="4dp"
        android:src="@drawable/ic_baseline_more_horiz_24"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toTopOf="parent" />


    <ir.alirezaiyan.profile.AvatarView
        android:id="@+id/avatarImageView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:scaleType="fitXY"
        android:src="@drawable/avatar"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        tools:targetApi="lollipop" />


</androidx.constraintlayout.motion.widget.MotionLayout>

Step 4. Write Code

Finally we need to write our code as follows:

(a). AvatarView.kt

Our AvatarView class.

Create a Kotlin file named AvatarView.kt and add the necessary imports. Here are some of the imports we will be using: 1. BitmapDrawable from the android.graphics.drawable package. 2. ColorDrawable from the android.graphics.drawable package. 3. Drawable from the android.graphics.drawable package. 4. AttributeSet from the android.util package. 5. AppCompatImageView from the androidx.appcompat.widget package.

Then extend the AppCompatImageView and add its contents as follows:

First override these callbacks:

  1. onDraw(canvas: Canvas).
  2. onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int).

Then we will be creating the following functions:

  1. init().
  2. setCornerRadius(parameter) - Pass to this method a Int object as a parameter.
  3. getBitmapFromDrawable(parameter) - We pass a Drawable? object as a parameter.
  4. resizeBitmap(width: Int, height: Int): Bitmap.

(a). Our setCornerRadius() function

Write the setCornerRadius() function as follows:

    fun setCornerRadius(cornerRadius: Int) {
        this.cornerRadius = cornerRadius.toFloat()
        invalidate()
    }

(b). Our resizeBitmap() function

Write the resizeBitmap() function as follows:

    private fun resizeBitmap(width: Int, height: Int): Bitmap {
        val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
        val canvas = Canvas(bitmap)
        mBitmap?.let {
            mMatrix.setScale(width.toFloat() / it.width, height.toFloat() / it.height)
            canvas.drawBitmap(it, mMatrix, null)
        }

        return bitmap
    }

(c). Our getBitmapFromDrawable() function

Write the getBitmapFromDrawable() function as follows:

    private fun getBitmapFromDrawable(drawable: Drawable?): Bitmap? {
        if (drawable == null) {
            return null
        }
        return if (drawable is BitmapDrawable) {
            drawable.bitmap
        } else try {
            val bitmap: Bitmap = if (drawable is ColorDrawable) {
                Bitmap
                    .createBitmap(width, height, Bitmap.Config.ARGB_8888)
            } else {
                Bitmap.createBitmap(
                    drawable.intrinsicWidth, drawable.intrinsicHeight,
                    Bitmap.Config.ARGB_8888
                )
            }
            val canvas = Canvas(bitmap)
            drawable.setBounds(0, 0, canvas.width, canvas.height)
            drawable.draw(canvas)
            bitmap
        } catch (e: Exception) {
            e.printStackTrace()
            null
        }
    }

(d). Our init() function

Write the init() function as follows:

    private fun init() {
        mBitmap = getBitmapFromDrawable(drawable)
        val mBitmapShader =
            BitmapShader(mBitmap!!, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP)
        mBitmapPaint.shader = mBitmapShader
        mBitmapPaint.isAntiAlias = true
    }

Here is the full code:

package replace_with_your_package_name

import android.annotation.SuppressLint
import android.content.Context
import android.graphics.*
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.ColorDrawable
import android.graphics.drawable.Drawable
import android.util.AttributeSet
import androidx.appcompat.widget.AppCompatImageView

class AvatarView : AppCompatImageView {
    private var mBitmap: Bitmap? = null
    private var cornerRadius = 50f
    private val mBitmapPaint = Paint()
    private val mMatrix = Matrix()
    private val mRectF = RectF()

    constructor(context: Context?) : super(context) {
        init()
    }

    constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) {
        init()
    }

    constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(
        context,
        attrs,
        defStyleAttr
    ) {
        val a =
            context.obtainStyledAttributes(attrs, R.styleable.AvatarView, defStyleAttr, 0)
        cornerRadius = a
            .getDimensionPixelSize(R.styleable.AvatarView_av_radius, cornerRadius.toInt()).toFloat()
        a.recycle()
        init()
    }

    private fun init() {
        mBitmap = getBitmapFromDrawable(drawable)
        val mBitmapShader =
            BitmapShader(mBitmap!!, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP)
        mBitmapPaint.shader = mBitmapShader
        mBitmapPaint.isAntiAlias = true
    }

    @SuppressLint("DrawAllocation")
    override fun onDraw(canvas: Canvas) {
        if (mBitmap == null) {
            return
        }
        mBitmapPaint.shader = BitmapShader(
            resizeBitmap(width, height), Shader.TileMode.CLAMP, Shader.TileMode.CLAMP
        )
        mRectF.set(0F, 0F, width.toFloat(), height.toFloat())
        canvas.drawRoundRect(
            mRectF,
            cornerRadius,
            cornerRadius,
            mBitmapPaint
        )
    }

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        super.onMeasure(widthMeasureSpec, widthMeasureSpec)
    }

    fun setCornerRadius(cornerRadius: Int) {
        this.cornerRadius = cornerRadius.toFloat()
        invalidate()
    }

    private fun getBitmapFromDrawable(drawable: Drawable?): Bitmap? {
        if (drawable == null) {
            return null
        }
        return if (drawable is BitmapDrawable) {
            drawable.bitmap
        } else try {
            val bitmap: Bitmap = if (drawable is ColorDrawable) {
                Bitmap
                    .createBitmap(width, height, Bitmap.Config.ARGB_8888)
            } else {
                Bitmap.createBitmap(
                    drawable.intrinsicWidth, drawable.intrinsicHeight,
                    Bitmap.Config.ARGB_8888
                )
            }
            val canvas = Canvas(bitmap)
            drawable.setBounds(0, 0, canvas.width, canvas.height)
            drawable.draw(canvas)
            bitmap
        } catch (e: Exception) {
            e.printStackTrace()
            null
        }
    }

    private fun resizeBitmap(width: Int, height: Int): Bitmap {
        val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
        val canvas = Canvas(bitmap)
        mBitmap?.let {
            mMatrix.setScale(width.toFloat() / it.width, height.toFloat() / it.height)
            canvas.drawBitmap(it, mMatrix, null)
        }

        return bitmap
    }
}

(b). MainActivity.kt

Our MainActivity class.

Create a Kotlin file named MainActivity.kt and add the necessary imports. Here are some of the imports we will be using: 1. ViewGroup from the android.view package. 2. AppCompatActivity from the androidx.appcompat.app package. 3. LinearLayoutManager from the androidx.recyclerview.widget package. 4. RecyclerView from the androidx.recyclerview.widget package. 5. * from the kotlinx.android.synthetic.main.activity_main package.

Then extend the AppCompatActivity and add its contents as follows:

First override these callbacks:

  1. onCreate(savedInstanceState: Bundle?).
  2. onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder.
  3. onBindViewHolder(holder: ViewHolder, position: Int).

Here is the full code:

package replace_with_your_package_name

import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        recyclerview.layoutManager = LinearLayoutManager(baseContext)
        recyclerview.adapter = Adapter()

        newrelease.setOnClickListener {
            Intent(Intent.ACTION_VIEW).apply {
                data = Uri.parse("http://github.com/rezaiyan/awesomeprofile")
            }.also { startActivity(it) }
        }
    }


    class Adapter() : RecyclerView.Adapter<Adapter.ViewHolder>() {

        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
            val layout = when (viewType) {
                0 -> R.layout.item1
                1 -> R.layout.item2
                else -> R.layout.item3
            }

            return ViewHolder(
                LayoutInflater.from(parent.context).inflate(
                    layout,
                    parent,
                    false
                )
            )
        }

        override fun getItemViewType(position: Int) = position

        override fun getItemCount() = 3

        override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        }

        class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)
    }
}

Reference

Download the code below:

No. Link
1. Download Full Code
2. Read more here.
3. Follow code author here.

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.

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 = "Item\n${itemList[currentPosition]}"

        rightTextView.text = if (currentPosition == itemList.lastIndex) {
            "Item\n${itemList.first()}"
        } else {
            "Item\n${itemList[currentPosition + 1]}"
        }

        leftTextView.text = if (currentPosition == 0) {
            "Item\n${itemList.last()}"
        } else {
            "Item\n${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.