In this article, I will explain and show you how to create beautiful animated lists based on RecyclerView and MotionLayout. I used a similar method in one of my projects.
From the translator: The repository of the author of the article is https://github.com/mjmanaog/foodbuddy .
I forked it to translate. Perhaps the "Russian version" will suit someone more.
What is MotionLayout?
In short, MotionLayout is a subclass of ConstraintLayout that allows you to describe the movement and animation of elements located on it using XML. More details - in the documentation and here with examples.
So, let's begin.
Step 1: create a new project
Let's call it whatever you like. Select Empty Activity as the activity.
Step 2: add the required dependencies
Add to the gradle file of the application:
implementation 'androidx.constraintlayout:constraintlayout:2.0.0-beta1'
And let's start synchronization (Sync Now in the upper right corner).
Step 3: create a layout
Our future list item will look like this:
res/layout item_food.
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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/clMain"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layoutDescription="@xml/item_food_scene">
<ImageView
android:id="@+id/ivFood"
android:layout_width="150dp"
android:layout_height="150dp"
android:layout_marginTop="8dp"
android:elevation="10dp"
android:scaleType="fitXY"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/img_salmon_salad" />
<androidx.cardview.widget.CardView
android:id="@+id/cardView"
android:layout_width="match_parent"
android:layout_height="150dp"
android:layout_marginStart="100dp"
android:layout_marginLeft="100dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:layout_marginRight="16dp"
android:layout_marginBottom="16dp"
app:cardCornerRadius="20dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/tvTitle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="60dp"
android:layout_marginLeft="60dp"
android:layout_marginTop="24dp"
android:layout_marginEnd="8dp"
android:layout_marginRight="8dp"
android:textSize="18sp"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text=" " />
<TextView
android:id="@+id/tvDescription"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="60dp"
android:layout_marginLeft="60dp"
android:layout_marginEnd="16dp"
android:layout_marginRight="8dp"
android:ellipsize="end"
android:maxLines="3"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tvTitle"
tools:text=" โ . , : , ." />
<TextView
android:id="@+id/tvCalories"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:layout_marginBottom="16dp"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@+id/imageView6"
tools:text="80 " />
<ImageView
android:id="@+id/imageView6"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginStart="60dp"
android:layout_marginLeft="60dp"
android:layout_marginBottom="16dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:srcCompat="@drawable/ic_calories" />
<ImageView
android:id="@+id/imageView7"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginStart="24dp"
android:layout_marginLeft="24dp"
android:layout_marginBottom="16dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@+id/tvCalories"
app:srcCompat="@drawable/ic_star" />
<TextView
android:id="@+id/tvRate"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:layout_marginBottom="16dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@+id/imageView7"
tools:text="4.5" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.cardview.widget.CardView>
</androidx.constraintlayout.widget.ConstraintLayout>
4: ConstraintLayout MotionLayout
ConstraintLayout MotionLayout:
Split Design;
(Component Tree) ( โ clMain);
Convert to MotionLayout.
MotionLayout.
item_food
<?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/clMain"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layoutDescription="@xml/item_food_scene">
<ImageView
android:id="@+id/ivFood"
android:layout_width="150dp"
android:layout_height="150dp"
android:layout_marginTop="8dp"
android:elevation="10dp"
android:scaleType="fitXY"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/img_salmon_salad" />
<androidx.cardview.widget.CardView
android:id="@+id/cardView"
android:layout_width="match_parent"
android:layout_height="150dp"
android:layout_marginStart="100dp"
android:layout_marginLeft="100dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:layout_marginRight="16dp"
android:layout_marginBottom="16dp"
app:cardCornerRadius="20dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/tvTitle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="60dp"
android:layout_marginLeft="60dp"
android:layout_marginTop="24dp"
android:layout_marginEnd="8dp"
android:layout_marginRight="8dp"
android:textSize="18sp"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text=" " />
<TextView
android:id="@+id/tvDescription"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="60dp"
android:layout_marginLeft="60dp"
android:layout_marginEnd="16dp"
android:layout_marginRight="8dp"
android:ellipsize="end"
android:maxLines="3"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tvTitle"
tools:text=" โ . , : , ." />
<TextView
android:id="@+id/tvCalories"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:layout_marginBottom="16dp"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@+id/imageView6"
tools:text="80 " />
<ImageView
android:id="@+id/imageView6"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginStart="60dp"
android:layout_marginLeft="60dp"
android:layout_marginBottom="16dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:srcCompat="@drawable/ic_calories" />
<ImageView
android:id="@+id/imageView7"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginStart="24dp"
android:layout_marginLeft="24dp"
android:layout_marginBottom="16dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@+id/tvCalories"
app:srcCompat="@drawable/ic_star" />
<TextView
android:id="@+id/tvRate"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:layout_marginBottom="16dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@+id/imageView7"
tools:text="4.5" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.cardview.widget.CardView>
</androidx.constraintlayout.motion.widget.MotionLayout>
res xml item_food_scene.xml:
(Warnings ), ImageView contentDescription. , XML- ( , ).
5: ImageView
ivFood (ImageView );
MotionLayout end;
ivFood (End) (End) ;
;
layout_height layout_width 300dp.
: ImageView ( , ) , : ( 150dp 300dp).
6: ,
, :
MotionLayout , start end;
Transition;
Play, .
7: CardView
:
cardView (constraintView , , );
MotionLayout end;
cardView ConstraintSet;
Transforms;
alpha 0.
: (end) (start) alpha. ( ).
8:
, :
OnClick ( ยซ+ยป);
targetId ivFood;
;
ClickAction toggle.
:
9: RecyclerView activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvMain"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
10:
package com.mjmanaog.foodbuddy.data.model
import com.mjmanaog.foodbuddy.R
data class FoodModel(
val title: String,
val description: String,
val calories: String,
val rate: String,
val imgId: Int
)
val foodDummyData: ArrayList<FoodModel> = arrayListOf(
FoodModel(
" ",
" โ . , : , .",
"80 ",
"4.5",
R.drawable.img_salmon_salad
),
FoodModel(
" -",
" , , .",
"80 ",
"4.5",
R.drawable.img_chicken
),
FoodModel(
" ",
" โ . , . .",
"80 ",
"4.5",
R.drawable.img_chicken_rice
),
FoodModel(
" ",
" , ( ), , , , , , , .",
"80 ",
"4.5",
R.drawable.img_salad
),
FoodModel(
" ",
" โ . , : , .",
"80 ",
"4.5",
R.drawable.img_healthy
)
)
11: ViewHolder
. FoodModel
12: RecyclerView
class MainActivity : AppCompatActivity() {
private var foodAdapter: FoodAdapter = FoodAdapter()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
rvMain.layoutManager = LinearLayoutManager(this, RecyclerView.VERTICAL, false)
rvMain.adapter = foodAdapter
foodAdapter.addAll(foodDummyData)
}
}
As a result of these simple actions, we got the following animation:
I didn't add the GIF from the article, because
She carry 11 MB.
Something else
The item_food_scene.xml file contains a description of the animation that we configured. Nobody bothers you to create and edit animations in scene files manually.
I hope the material from this article will be useful to someone. It will be cool if you learn something new from it.
Thank you for attention.