In this tutorial, we’ll be discussing and implementing RecyclerView Layout Animations in our Android Application.
Android RecyclerView Layout Animations
There are many ways to Animate rows in a RecyclerView.
Two commonly tried and tested ways are :
- Using ItemAnimators. – Read this tutorial.
- Setting animation on each row in the
onBindViewHolder
in the Adapter class
There’s another lesser-known but more efficient way of animating a RecyclerView using Layout Animations.
We can directly pass the animation resource asset in the XML on the attribute android:layoutAnimation
.
The anim resource passed must contain <layoutAnimation> as the root tag.
layoutAnimation
is valid on all other layouts as well besides RecyclerView.
Let’s start by defining some basic Animations in the res | anim
folder in our Android Studio Project.
down_to_up.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
<?xml version="1.0" encoding="utf-8"?> <set xmlns:android="https://schemas.android.com/apk/res/android" android:duration="500"> <translate android:fromYDelta="50%p" android:interpolator="@android:anim/accelerate_decelerate_interpolator" android:toYDelta="0" /> <alpha android:fromAlpha="0" android:interpolator="@android:anim/accelerate_decelerate_interpolator" android:toAlpha="1" /> </set> |
up_to_down.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
<?xml version="1.0" encoding="utf-8"?> <set xmlns:android="https://schemas.android.com/apk/res/android" android:duration="500"> <translate android:fromYDelta="-25%" android:interpolator="@android:anim/decelerate_interpolator" android:toYDelta="0" /> <alpha android:fromAlpha="0" android:interpolator="@android:anim/decelerate_interpolator" android:toAlpha="1" /> <scale android:fromXScale="125%" android:fromYScale="125%" android:interpolator="@android:anim/decelerate_interpolator" android:pivotX="50%" android:pivotY="50%" android:toXScale="100%" android:toYScale="100%" /> </set> |
left_to_right.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
<?xml version="1.0" encoding="utf-8"?> <set xmlns:android="https://schemas.android.com/apk/res/android" android:duration="500"> <translate android:fromXDelta="-100%p" android:interpolator="@android:anim/decelerate_interpolator" android:toXDelta="0" /> <alpha android:fromAlpha="0.5" android:interpolator="@android:anim/accelerate_decelerate_interpolator" android:toAlpha="1" /> </set> |
right_to_left.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
<?xml version="1.0" encoding="utf-8"?> <set xmlns:android="https://schemas.android.com/apk/res/android" android:duration="500"> <translate android:fromXDelta="100%p" android:interpolator="@android:anim/decelerate_interpolator" android:toXDelta="0" /> <alpha android:fromAlpha="0.5" android:interpolator="@android:anim/accelerate_decelerate_interpolator" android:toAlpha="1" /> </set> |
Now let’s create the layoutAnimation
for each of these animation sets.
layout_animation_down_to_up.xml
1 2 3 4 5 6 7 |
<?xml version="1.0" encoding="utf-8"?> <layoutAnimation xmlns:android="https://schemas.android.com/apk/res/android" android:animation="@anim/down_to_up" android:animationOrder="normal" android:delay="15%" /> |
layout_animation_up_to_down.xml
1 2 3 4 5 6 7 |
<?xml version="1.0" encoding="utf-8"?> <layoutAnimation xmlns:android="https://schemas.android.com/apk/res/android" android:animation="@anim/up_to_down" android:animationOrder="normal" android:delay="15%" /> |
layout_animation_left_to_right.xml
1 2 3 4 5 6 7 |
<?xml version="1.0" encoding="utf-8"?> <layoutAnimation xmlns:android="https://schemas.android.com/apk/res/android" android:animation="@anim/left_to_right" android:animationOrder="normal" android:delay="15%" /> |
layout_animation_right_to_left.xml
1 2 3 4 5 6 7 |
<?xml version="1.0" encoding="utf-8"?> <layoutAnimation xmlns:android="https://schemas.android.com/apk/res/android" android:animation="@anim/right_to_left" android:animationOrder="normal" android:delay="15%" /> |
Setting Layout Animation in XML and Programmatically
We can set the Layout Animation on RecyclerView in XML in the following way:
1 2 3 4 5 6 7 |
<android.support.v7.widget.RecyclerView android:layout_width="match_parent" android:layout_height="match_parent" android:layoutAnimation="@anim/layout_animation_right_to_left" /> |
Programmatically:
1 2 3 4 5 |
int resId = R.anim.layout_animation_right_to_left; LayoutAnimationController animation = AnimationUtils.loadLayoutAnimation(context, resId); recyclerView.setLayoutAnimation(animation); |
In order to re-run the animation or in case the data set of the RecyclerView has changed the following code is used:
1 2 3 4 5 6 7 |
final LayoutAnimationController controller = AnimationUtils.loadLayoutAnimation(context, R.anim.layout_animation_right_to_left); recyclerView.setLayoutAnimation(controller); recyclerView.getAdapter().notifyDataSetChanged(); recyclerView.scheduleLayoutAnimation(); |
Project Structure
Code
The code for the activity_main.xml layout is given below:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
<?xml version="1.0" encoding="utf-8"?> <android.support.constraint.ConstraintLayout xmlns:android="https://schemas.android.com/apk/res/android" xmlns:app="https://schemas.android.com/apk/res-auto" xmlns:tools="https://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <android.support.v7.widget.RecyclerView android:id="@+id/recyclerView" android:layout_width="match_parent" android:layout_height="wrap_content" android:layoutAnimation="@anim/layout_animation_up_to_down" app:layoutManager="android.support.v7.widget.LinearLayoutManager" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" /> <android.support.design.widget.FloatingActionButton android:id="@+id/fab" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_margin="8dp" android:src="@android:drawable/ic_media_next" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintRight_toRightOf="parent" /> </android.support.constraint.ConstraintLayout> |
FloatingActionButton is used to toggle through the different layout animations.
The layout for the rows of RecyclerView is defined in item_row.xml as shown below:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
<?xml version="1.0" encoding="utf-8"?> <android.support.v7.widget.CardView xmlns:android="https://schemas.android.com/apk/res/android" xmlns:app="https://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="wrap_content" app:cardElevation="8dp" app:cardUseCompatPadding="true"> <TextView android:id="@+id/tvItem" android:layout_width="match_parent" android:layout_height="wrap_content" android:padding="16dp" android:text="Item X" /> </android.support.v7.widget.CardView> |
The code for the RecyclerViewAdapter.java
class is given below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
package com.journaldev.androidrecyclerviewlayoutanimation; import android.support.annotation.NonNull; import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; import java.util.List; public class RecyclerViewAdapter extends RecyclerView.Adapter<RecyclerViewAdapter.ItemViewHolder> { List<String> itemList; public RecyclerViewAdapter(List<String> itemList) { this.itemList = itemList; } @NonNull @Override public ItemViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) { View view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.item_row, viewGroup, false); return new ItemViewHolder(view); } @Override public void onBindViewHolder(@NonNull ItemViewHolder myViewHolder, int position) { myViewHolder.tvItem.setText(itemList.get(position)); } @Override public int getItemCount() { return itemList == null ? 0 : itemList.size(); } public class ItemViewHolder extends RecyclerView.ViewHolder { TextView tvItem; public ItemViewHolder(@NonNull View itemView) { super(itemView); tvItem = itemView.findViewById(R.id.tvItem); } } } |
The code for the MainActivity.java
class is given below:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 |
package com.journaldev.androidrecyclerviewlayoutanimation; import android.support.design.widget.FloatingActionButton; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.support.v7.widget.GridLayoutManager; import android.support.v7.widget.RecyclerView; import android.util.Log; import android.view.View; import android.view.animation.AnimationUtils; import android.view.animation.GridLayoutAnimationController; import android.view.animation.LayoutAnimationController; import android.widget.Spinner; import java.util.ArrayList; public class MainActivity extends AppCompatActivity { FloatingActionButton fab; RecyclerView recyclerView; RecyclerViewAdapter recyclerViewAdapter; ArrayList<String> arrayList = new ArrayList<>(); int[] animationList = {R.anim.layout_animation_up_to_down, R.anim.layout_animation_right_to_left, R.anim.layout_animation_down_to_up, R.anim.layout_animation_left_to_right}; int i = 0; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); fab = findViewById(R.id.fab); recyclerView = findViewById(R.id.recyclerView); populateData(); initAdapter(); fab.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (i < animationList.length - 1) { i++; } else { i = 0; } runAnimationAgain(); } }); } private void populateData() { for (int i = 0; i < 12; i++) { arrayList.add("Item " + i); } } private void initAdapter() { recyclerViewAdapter = new RecyclerViewAdapter(arrayList); recyclerView.setAdapter(recyclerViewAdapter); } private void runAnimationAgain() { final LayoutAnimationController controller = AnimationUtils.loadLayoutAnimation(this, animationList[i]); recyclerView.setLayoutAnimation(controller); recyclerViewAdapter.notifyDataSetChanged(); recyclerView.scheduleLayoutAnimation(); } } |
runAnimationAgain
method is used to loop through each animation and run them on the RecyclerView again.
The output of the above application in action is given below:
Now if we apply a GridLayoutManager as the layout manager we’ll get the following output:
1 2 3 |
recyclerView.setLayoutManager(new GridLayoutManager(this,2,GridLayoutManager.VERTICAL,false)); |
The above layout animation is not grid layout animation. It’s animating each row at a time.
This is incorrect. The RecyclerView LayoutAnimation defaults for Lists only.
In order to do Grid Layout Animation, we need to customize the recycler view.
We’ll do that in the next tutorial.
That brings an end to this tutorial. You can download the project from the link below: