303

How can I animate the RecyclerView Items when there are appearing?

The default item animator only animates when a data is added or removed after the recycler data has been set.

How can this be achieved?

starball
  • 20,030
  • 7
  • 43
  • 238
PaulNunezM
  • 3,217
  • 3
  • 14
  • 10

11 Answers11

345

EDIT :

According to the ItemAnimator documentation :

This class defines the animations that take place on items as changes are made to the adapter.

So unless you add your items one by one to your RecyclerView and refresh the view at each iteration, I don't think ItemAnimator is the solution to your need.

Here is how you can animate the RecyclerView items when they appear using a CustomAdapter :

public class CustomAdapter extends RecyclerView.Adapter<CustomAdapter.ViewHolder>
{
    private Context context;

    // The items to display in your RecyclerView
    private ArrayList<String> items;
    // Allows to remember the last item shown on screen
    private int lastPosition = -1;

    public static class ViewHolder extends RecyclerView.ViewHolder
    {
        TextView text;
        // You need to retrieve the container (ie the root ViewGroup from your custom_item_layout)
        // It's the view that will be animated
        FrameLayout container;

        public ViewHolder(View itemView)
        {
            super(itemView);
            container = (FrameLayout) itemView.findViewById(R.id.item_layout_container);
            text = (TextView) itemView.findViewById(R.id.item_layout_text);
        }
    }

    public CustomAdapter(ArrayList<String> items, Context context)
    {
        this.items = items;
        this.context = context;
    }

    @Override
    public CustomAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType)
    {
        View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.custom_item_layout, parent, false);
        return new ViewHolder(v);
    }

    @Override
    public void onBindViewHolder(ViewHolder holder, int position)
    {
        holder.text.setText(items.get(position));

        // Here you apply the animation when the view is bound
        setAnimation(holder.itemView, position);
    }

    /**
     * Here is the key method to apply the animation
     */
    private void setAnimation(View viewToAnimate, int position)
    {
        // If the bound view wasn't previously displayed on screen, it's animated
        if (position > lastPosition)
        {
            Animation animation = AnimationUtils.loadAnimation(context, android.R.anim.slide_in_left);
            viewToAnimate.startAnimation(animation);
            lastPosition = position;
        }
    }
}

And your custom_item_layout would look like this :

<FrameLayout
    android:id="@+id/item_layout_container"
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <TextView
        android:id="@+id/item_layout_text"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textAppearance="?android:attr/textAppearanceListItemSmall"
        android:gravity="center_vertical"
        android:minHeight="?android:attr/listPreferredItemHeightSmall"/>

</FrameLayout>

For more information about CustomAdapters and RecyclerView, refer to this training on the official documentation.

Problems on fast scroll

Using this method could cause problems with fast scrolling. The view could be reused while the animation is been happening. In order to avoid that is recommendable to clear the animation when is detached.

    @Override
    public void onViewDetachedFromWindow(final RecyclerView.ViewHolder holder)
    {
        ((CustomViewHolder)holder).clearAnimation();
    }

On CustomViewHolder:

    public void clearAnimation()
    {
        mRootLayout.clearAnimation();
    }

Old answer :

Give a look at Gabriele Mariotti's repo, I'm pretty sure you'll find what you need. He provides simple ItemAnimators for the RecyclerView, such as SlideInItemAnimator or SlideScaleItemAnimator.

dgngulcan
  • 3,101
  • 1
  • 24
  • 26
MathieuMaree
  • 7,453
  • 6
  • 26
  • 31
  • 1
    I've seen that, but that's for adding and removing items after they've appeared. I need to start animation right before they appeard. Thanks anyway Mathieu. – PaulNunezM Nov 06 '14 at 03:47
  • 1
    As far as I know, you need to use a CustomAdapter then. – MathieuMaree Nov 06 '14 at 13:22
  • @MathieuMaree but can you imagine this sense: while i'm using this CustomAdapter, when I scroll from position 0 to 100, these items all play this animation, but when I scroll back, position 0's item will play again. Is there any way to avoid this happen? – Kevin Liu Jan 07 '15 at 08:05
  • @KevinLiu Not sure to understand what you ask. Are you saying the first item is animated when you scroll back to the top? – MathieuMaree Jan 07 '15 at 11:32
  • @MathieuMaree yes, I don't want the item which has been played its animation play again. I thought maybe I need a Map to store the state of every item's animation? like "done" or "not yet". – Kevin Liu Jan 08 '15 at 05:10
  • @KevinLiu I don't get it, they're not supposed to be animated again; that's why we keep `lastPosition` updated, to know the index of the last animated item. If you already use it, can you be more explicit about your problem? – MathieuMaree Jan 08 '15 at 11:27
  • 22
    I was wondering if you've experienced and maybe solved the "stuck" effect with these animations in RecyclerView? I used similiar code for the ListView and my items were animated without a problem no matter how fast I scrolled, but with RecyclerView if I scroll fast some items sometimes get stuck on the screen on top of the other items and they don't hide entirely - it's like the animation was stopped before it finished. I actually tried to comment out the part of the code which populates the fields (trying to speed up the execution of onBindViewHolder method) so I only left the code for animat – Tomislav Feb 16 '15 at 12:40
  • 4
    @MathieuMaree Thanks for this amaze animation.It looks good for slow scroll,but on fast scroll recyclerview items overlapped.Did you find this issse?Any suggestion to overcome this issue. – Giru Bhai Feb 26 '15 at 12:16
  • @GiruBhai, user2489759 : As a matter of fact, I did... even though I'm not even using this animation. I believe there's something wrong with this adapter but can't figure out what. Feel free to suggest anything, I'll look into it myself too. – MathieuMaree Feb 27 '15 at 13:00
  • @GiruBhai, user2489759 : After digging into it, I discovered that it's linked to the fact that I'm using the `StaggeredGridLayoutManager`. When using either `LinearLayoutManager` or `GridLayoutManager`, it appears I have no problem at all. Have you noticed anything similar? – MathieuMaree Feb 27 '15 at 14:07
  • @MathieuMaree in my case recycleview have `LinearLayoutManager` and having problem during fast scroll. – Giru Bhai Feb 27 '15 at 17:08
  • @MathieuMaree works perfect for me, i used LinearLayoutManager, Do you anyway to set animation for the items which go out of site. I mean this code animates items when then appear. So i need to know is there anyway we can animate the items that are about to disappear. – Ashok Varma Mar 09 '15 at 04:31
  • 42
    @GiruBhai override `onViewDetachedFromWindow` and call `clearAnimation` on the view. The problem is that there are animations running when RecyclerView is trying to reuse the view. – Xample Mar 24 '15 at 04:12
  • 1
    @AshokVarma the view that is about to disappear is handed to `onViewDetachedFromWindow` – Xample Mar 24 '15 at 04:13
  • 1
    Animate items in RecyclerView from outside (from adapter for example) is really bad practice because RecyclerView easily can rebind your holder while animating. This lead to undefined state for those holders and weird errors. I don't know hot to animate views when they appear, but I bet you, don't animate those views outside RecyclerView, this will save you a lot nerve. – s.maks Apr 15 '15 at 09:44
  • @Xample thx for this solution, I was wondering about this issue and didn't have any idea how to solve this problem :-) – Strassenrenner Jul 19 '15 at 17:01
  • how can i make the animation work one after the other, currently this animates all the items at once. i want to animate one item then the next and so on. – usr30911 Nov 16 '15 at 13:40
  • notifyDataSetChanged has a pattern of abruptly updating the recyclerview with new data/removing old data. Does this setAnimation() permit animation even when (or especially when) that method is called? – AlleyOOP Dec 15 '15 at 06:50
  • @KevinLiu i am having the same problem... how did u resolve that?? – Aman Verma Jan 08 '16 at 21:46
  • 1
    @AmanVerma just like Mathieu Maree said to me. see above about our discuss. – Kevin Liu Jan 11 '16 at 02:59
  • 1
    doesn't super.onViewDetachedFromWindow(holder); need to be called first? I'm not sure where mRootLayout is initialized so I used ((MyViewHolder)holder).itemView.clearAnimation(); – Futureproof Feb 12 '16 at 20:22
  • The code above tt's clear to me, however, when I set the adapter to my recycler, all the visible views animates together causing an ugly effect. How can I animate (at first show) all the items one by one? Should I add the items accordingly? – Lampione Sep 21 '16 at 14:55
  • I use grid layout manager with 2 span, but the animation occur only on the right side items. Anyone has the same problem and has the solution? – HendraWD Nov 21 '16 at 04:50
  • How to give animation load "from bottom to top"? – VVB Dec 13 '16 at 08:37
  • @VVB Simply replace `android.R.anim.slide_in_left` by `R.anim.slide_bottom_to_top` and create a new file with your bottom-to-top translation animation in your res/anim folder – MathieuMaree Dec 13 '16 at 13:23
  • where to put these functions ? `onViewDetachedFromWindow` and `clearAnimation` ?? in custom ViewHolder class or adapter ?? – mrid Sep 09 '17 at 16:03
  • @Gavriel: clearAnimation() is a method in your RecyclerView.ViewHolder class. The constructor accepts itemView as your root layout. You can use something like itemView.clearAnimation() – Vinit Shandilya Jun 12 '18 at 15:25
  • @MathieuMaree great job, I followed your code, but animation runs only on first load not on scroll down, if I remove `if (position > lastPosition)` then animation runs on scroll up/down for all items (on every scroll), any idea what is the problem? – Mehdi Dehghani Mar 09 '19 at 12:52
  • @MathieuMaree seems like the problem is the XML, the parent of my recyclerview have `layout_weight`, if I move the recyclerview one level up (with no `layout_weight` on parent), everything works fine, any idea? – Mehdi Dehghani Mar 09 '19 at 14:26
  • @Override public void onViewDetachedFromWindow(final RecyclerView.ViewHolder holder) { ((CustomViewHolder)holder).clearAnimation(); } doesnot resoled method in my project – Rahul Kushwaha Mar 19 '19 at 06:58
  • I understand it – Rahul Kushwaha Mar 19 '19 at 07:12
129

Made Simple with XML only

Visit Gist Link

res/anim/layout_animation.xml

<?xml version="1.0" encoding="utf-8"?>
    <layoutAnimation xmlns:android="http://schemas.android.com/apk/res/android"
        android:animation="@anim/item_animation_fall_down"
        android:animationOrder="normal"
        android:delay="15%" />

res/anim/item_animation_fall_down.xml

<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="500">

    <translate
        android:fromYDelta="-20%"
        android:toYDelta="0"
        android:interpolator="@android:anim/decelerate_interpolator"
        />

    <alpha
        android:fromAlpha="0"
        android:toAlpha="1"
        android:interpolator="@android:anim/decelerate_interpolator"
        />

    <scale
        android:fromXScale="105%"
        android:fromYScale="105%"
        android:toXScale="100%"
        android:toYScale="100%"
        android:pivotX="50%"
        android:pivotY="50%"
        android:interpolator="@android:anim/decelerate_interpolator"
        />

</set>

Use in layouts and recylcerview like:

<androidx.recyclerview.widget.RecyclerView
    android:id="@+id/recycler_view"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layoutAnimation="@anim/layout_animation"
    app:layout_behavior="@string/appbar_scrolling_view_behavior" />
Cyb3rKo
  • 413
  • 7
  • 22
iamnaran
  • 1,894
  • 2
  • 15
  • 24
  • 2
    @ArnoldBrown By changing the animation file. Refer: https://stackoverflow.com/questions/5151591/android-left-to-right-slide-animation – iamnaran Aug 03 '19 at 09:29
  • 1
    This is by far the most practical answer. – Oliver Metz Oct 27 '19 at 22:33
  • 2
    how to make this work every time the list is opened, because now it only do it for the first time. – hewa jalal Dec 07 '19 at 14:52
  • 43
    You have to call `recyclerView.scheduleLayoutAnimation()` after data set changed, if not, the animation would not work. – zeleven Jan 18 '20 at 07:22
  • 1
    Should this work for when items are recycled and come back into view? I attempted this solution and it works great for the initial animation when you can first see the layout. After scrolling, items do not have the animation when coming back to view. – Jason p Jan 22 '20 at 01:40
  • @Jasonp to do that you will need to do the animation in your onBindViewHolder, so for each time an item is binded, you will get the animation on scrolling – Gastón Saillén May 25 '20 at 20:50
  • @iamnaran now I'm trying to reproduce your solution but I got some outside elements that weren't animated: https://imgur.com/a/F1pGjbL What's the problem? – Egor Jul 15 '20 at 15:48
  • you can follow these guidelines regarding duration for better experience: https://material.io/design/motion/speed.html#easing – Ultimo_m Oct 01 '20 at 12:56
  • this answer doesnt work when you `notifyItemInserted` on the adapter, this just animates the parent view, not the items inside – Ultimo_m Oct 01 '20 at 13:47
  • How can I not apply this animation only for the first item? I want to make the list come out of the first item and drop down one from the next one. – c-an Nov 17 '20 at 07:14
74

I animated fading in of Recyclerview items when they first appear as shown in the code below. Perhaps this will be of use to someone.

private final static int FADE_DURATION = 1000; //FADE_DURATION in milliseconds

@Override
public void onBindViewHolder(ViewHolder holder, int position) {

    holder.getTextView().setText("some text");

    // Set the view to fade in
    setFadeAnimation(holder.itemView);            
}

private void setFadeAnimation(View view) {
    AlphaAnimation anim = new AlphaAnimation(0.0f, 1.0f);
    anim.setDuration(FADE_DURATION);
    view.startAnimation(anim);
}

You can also replace setFadeAnimation() with the following setScaleAnimation() to animate appearance of items by scaling them from a point:

private void setScaleAnimation(View view) {
    ScaleAnimation anim = new ScaleAnimation(0.0f, 1.0f, 0.0f, 1.0f, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
    anim.setDuration(FADE_DURATION);
    view.startAnimation(anim);
}

The code above has some warts in so far as when you scroll the RecyclerView items always fade or scale. If you wish you can add code to just allow the animation to happen when the fragment or activity containing the RecyclerView is first created (e.g. get the system time on creation and only allow animation for the first FADE_DURATION milliseconds).

Arpit J.
  • 1,108
  • 12
  • 20
pbm
  • 5,081
  • 4
  • 18
  • 31
  • 1
    I did small modification for your answer to make animation work on scroll down only, check [my answer](http://stackoverflow.com/a/36545709/4251431) – Basheer AL-MOMANI Apr 11 '16 at 10:14
  • 1
    this spoils the layout ( over-written list items, some items having wrong text color ) on fast scrolling up and down – mrid Sep 09 '17 at 12:36
  • This is not the proper or recommended way to animate recyclerview items. You must be using ItemAnimator class – Eco4ndly Aug 15 '19 at 10:04
33

I created animation from pbm's answer with little modification to make the aninmation run only once

in the other word the Animation appear with you scroll down only

private int lastPosition = -1;

private void setAnimation(View viewToAnimate, int position) {
    // If the bound view wasn't previously displayed on screen, it's animated
    if (position > lastPosition) {
        ScaleAnimation anim = new ScaleAnimation(0.0f, 1.0f, 0.0f, 1.0f, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
        anim.setDuration(new Random().nextInt(501));//to make duration random number between [0,501)
        viewToAnimate.startAnimation(anim);
        lastPosition = position;
    }
}

and in onBindViewHolder call the function

@Override
public void onBindViewHolder(ViewHolder holder, int position) {

holder.getTextView().setText("some text");

// call Animation function
setAnimation(holder.itemView, position);            
}
Community
  • 1
  • 1
Basheer AL-MOMANI
  • 14,473
  • 9
  • 96
  • 92
17

You can add a android:layoutAnimation="@anim/rv_item_animation" attribute to RecyclerView like this:

<android.support.v7.widget.RecyclerView
    android:layout_width="match_parent"
    android:layout_height="match_parent"                                        
    android:layoutAnimation="@anim/layout_animation_fall_down"
    />

thanks for the excellent article here: https://proandroiddev.com/enter-animation-using-recyclerview-and-layoutanimation-part-1-list-75a874a5d213

AskNilesh
  • 67,701
  • 16
  • 123
  • 163
Pavel Biryukov
  • 1,076
  • 12
  • 19
10

A good place to start is this: https://github.com/wasabeef/recyclerview-animators/blob/master/animators/src/main/java/jp/wasabeef/recyclerview/adapters/AnimationAdapter.java

You don't even need the full library, that class is enough. Then if you just implement your Adapter class giving an animator like this:

@Override
protected Animator[] getAnimators(View view) {
    return new Animator[]{
            ObjectAnimator.ofFloat(view, "translationY", view.getMeasuredHeight(), 0)
    };
}

@Override
public long getItemId(final int position) {
    return getWrappedAdapter().getItemId(position);
}

you'll see items appearing from the bottom as they scroll, also avoiding the problem with the fast scroll.

Patrick
  • 33,984
  • 10
  • 106
  • 126
6

Animating items in the recyclerview when they are binded in the adapter might not be the best idea as that can cause the items in the recyclerview to animate at different speeds. In my case, the item at the end of the recyclerview animate to their position quicker then the ones at the top as the ones at the top have further to travel so it made it look untidy.

The original code that I used to animate each item into the recyclerview can be found here:

http://frogermcs.github.io/Instagram-with-Material-Design-concept-is-getting-real/

But I will copy and paste the code in case the link breaks.

STEP 1: Set this inside your onCreate method so that you ensure the animation only run once:

if (savedInstanceState == null) {
    pendingIntroAnimation = true;
}

STEP 2: You will need to put this code into the method where you want to start the animation:

if (pendingIntroAnimation) {
    pendingIntroAnimation = false;
    startIntroAnimation();
}

In the link, the writer is animating the toolbar icons, so he put it inside this method:

@Override
public boolean onCreateOptionsMenu(Menu menu) {
    getMenuInflater().inflate(R.menu.menu_main, menu);
    inboxMenuItem = menu.findItem(R.id.action_inbox);
    inboxMenuItem.setActionView(R.layout.menu_item_view);
    if (pendingIntroAnimation) {
        pendingIntroAnimation = false;
        startIntroAnimation();
    }
    return true;
}

STEP 3: Now write the logic for the startIntroAnimation():

private static final int ANIM_DURATION_TOOLBAR = 300;

private void startIntroAnimation() {
    btnCreate.setTranslationY(2 * getResources().getDimensionPixelOffset(R.dimen.btn_fab_size));

    int actionbarSize = Utils.dpToPx(56);
    toolbar.setTranslationY(-actionbarSize);
    ivLogo.setTranslationY(-actionbarSize);
    inboxMenuItem.getActionView().setTranslationY(-actionbarSize);

    toolbar.animate()
            .translationY(0)
            .setDuration(ANIM_DURATION_TOOLBAR)
            .setStartDelay(300);
    ivLogo.animate()
            .translationY(0)
            .setDuration(ANIM_DURATION_TOOLBAR)
            .setStartDelay(400);
    inboxMenuItem.getActionView().animate()
            .translationY(0)
            .setDuration(ANIM_DURATION_TOOLBAR)
            .setStartDelay(500)
            .setListener(new AnimatorListenerAdapter() {
                @Override
                public void onAnimationEnd(Animator animation) {
                    startContentAnimation();
                }
            })
            .start();
}

My preferred alternative:

I would rather animate the whole recyclerview instead of the items inside the recyclerview.

STEP 1 and 2 remains the same.

In STEP 3, as soon as your API call returns with your data, I would start the animation.

private void startIntroAnimation() {
    recyclerview.setTranslationY(latestPostRecyclerview.getHeight());
    recyclerview.setAlpha(0f);
    recyclerview.animate()
            .translationY(0)
            .setDuration(400)
            .alpha(1f)
            .setInterpolator(new AccelerateDecelerateInterpolator())
            .start();
}

This would animate your entire recyclerview so that it flys in from the bottom of the screen.

Simon
  • 19,658
  • 27
  • 149
  • 217
  • you are talking about animating the whole recyclerview – Longerian Mar 24 '16 at 08:18
  • Yes - I'm. You should read the first paragraph on why I think animating the whole recyclerview is a better idea than each item. You can try animating each item but it will not look good. – Simon Mar 24 '16 at 09:23
  • 1
    What is `latestPostRecyclerview`? – Antonio Sep 16 '16 at 19:53
  • 1
    Did u read the first paragraph of my answer where I had stated the reason why animating item views are not such a great idea? Also I would suggest trying out the accepted solution and then you realize the problem, come back and try this solution. – Simon Oct 02 '16 at 18:12
6

Create this method into your recyclerview Adapter

private void setZoomInAnimation(View view) {
        Animation zoomIn = AnimationUtils.loadAnimation(context, R.anim.zoomin);// animation file 
        view.startAnimation(zoomIn);
    }

And finally add this line of code in onBindViewHolder

setZoomInAnimation(holder.itemView);

Md.Tarikul Islam
  • 1,241
  • 1
  • 14
  • 16
4

In 2019, I would suggest putting all the item animations into the ItemAnimator.

Let's start with declaring the animator in the recycler-view:

with(view.recycler_view) {
adapter = Adapter()
itemAnimator = CustomAnimator()
}

Declare the custom animator then,

class CustomAnimator() : DefaultItemAnimator() {

     override fun animateAppearance(
       holder: RecyclerView.ViewHolder,
       preInfo: ItemHolderInfo?,
       postInfo: ItemHolderInfo): Boolean{} // declare  what happens when a item appears on the recycler view

     override fun animatePersistence(
       holder: RecyclerView.ViewHolder,
       preInfo: ItemHolderInfo,
       postInfo: ItemHolderInfo): Boolean {} // declare animation for items that persist in a recycler view even when the items change

}

Similar to the ones above there is one for disappearance animateDisappearance, for add animateAdd, for change animateChange and move animateMove.

One important point would be to call the correct animation-dispatchers inside them.

Dinesh
  • 889
  • 14
  • 34
  • Could you provide an example of a custom appearance animation using this override function? I can't find an example and i'm not sure if i just have to specify the animation in the function. – m.i.n.a.r. May 06 '20 at 09:21
  • 1
    https://gist.github.com/tadfisher/120d03f8380bfa8a16bf I found this online on a quick search, this gives an idea of how the overloading works – Dinesh May 13 '20 at 20:58
2

I think, is better to use it like this: (in RecyclerView adapter override just a one method)

override fun onViewAttachedToWindow(holder: ViewHolder) {
    super.onViewAttachedToWindow(holder)

    setBindAnimation(holder)
}

If You want every attach animation there in RV.

Milan Jurkulak
  • 557
  • 3
  • 6
1

Just extends your Adapter like below

public class RankingAdapter extends AnimatedRecyclerView<RankingAdapter.ViewHolder> 

And add super method to onBindViewHolder

@Override
    public void onBindViewHolder(ViewHolder holder, final int position) {
        super.onBindViewHolder(holder, position);

It's automate way to create animated adapter like "Basheer AL-MOMANI"

import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.Animation;
import android.view.animation.ScaleAnimation;

import java.util.Random;

/**
 * Created by eliaszkubala on 24.02.2017.
 */
public class AnimatedRecyclerView<T extends RecyclerView.ViewHolder> extends RecyclerView.Adapter<T> {


    @Override
    public T onCreateViewHolder(ViewGroup parent, int viewType) {
        return null;
    }

    @Override
    public void onBindViewHolder(T holder, int position) {
        setAnimation(holder.itemView, position);
    }

    @Override
    public int getItemCount() {
        return 0;
    }

    protected int mLastPosition = -1;

    protected void setAnimation(View viewToAnimate, int position) {
        if (position > mLastPosition) {
            ScaleAnimation anim = new ScaleAnimation(0.0f, 1.0f, 0.0f, 1.0f, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
            anim.setDuration(new Random().nextInt(501));//to make duration random number between [0,501)
            viewToAnimate.startAnimation(anim);
            mLastPosition = position;
        }
    }

}
Eliasz Kubala
  • 3,836
  • 1
  • 23
  • 28