Comment animer des éléments RecyclerView lorsqu'ils apparaissent


237

Comment puis-je animer les éléments RecyclerView lorsqu'ils apparaissent?

L'animateur d'élément par défaut s'anime uniquement lorsqu'une donnée est ajoutée ou supprimée après la définition des données du recycleur. Je suis un nouveau développeur d'applications et je n'ai aucune idée par où commencer.

Des idées pour y parvenir?

Réponses:


42

Simplifié avec XML uniquement

Visitez 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>

Utilisez dans les mises en page et les récapitulatifs comme:

<android.support.v7.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" />

1
@ArnoldBrown En changeant le fichier d'animation. Référez: stackoverflow.com/questions/5151591/…
iamnaran

C'est de loin la réponse la plus pratique.
Oliver Metz

comment faire en sorte que cela fonctionne à chaque fois que la liste est ouverte, car maintenant elle ne le fait que pour la première fois.
Hiwa Jalal

7
Vous devez appeler recyclerView.scheduleLayoutAnimation()après la modification de l'ensemble de données, sinon, l'animation ne fonctionnerait pas.
zeleven

Cela devrait-il fonctionner lorsque les articles sont recyclés et reviennent en vue? J'ai essayé cette solution et cela fonctionne très bien pour l'animation initiale lorsque vous pouvez voir la mise en page pour la première fois. Après le défilement, les éléments n'ont pas d'animation lorsqu'ils reviennent à la vue.
Jason p

315

ÉDITER :

Selon la documentation d'ItemAnimator :

Cette classe définit les animations qui ont lieu sur les éléments lorsque des modifications sont apportées à l'adaptateur.

Donc, à moins que vous n'ajoutiez vos articles un à un RecyclerViewet que vous actualisiez la vue à chaque itération, je ne pense pas que ce ItemAnimatorsoit la solution à votre besoin.

Voici comment animer les RecyclerViewéléments lorsqu'ils apparaissent à l'aide d'un 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;
        }
    }
}

Et votre custom_item_layout ressemblerait à ceci:

<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>

Pour plus d'informations sur CustomAdapters et RecyclerView, reportez-vous à cette formation sur la documentation officielle .

Problèmes de défilement rapide

L'utilisation de cette méthode peut entraîner des problèmes de défilement rapide. La vue pourrait être réutilisée pendant que l'animation est en cours. Afin d'éviter cela, il est recommandé d'effacer l'animation lorsqu'elle est détachée.

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

Sur CustomViewHolder:

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

Ancienne réponse:

Jetez un œil au repo de Gabriele Mariotti , je suis sûr que vous trouverez ce dont vous avez besoin. Il fournit des ItemAnimators simples pour RecyclerView, tels que SlideInItemAnimator ou SlideScaleItemAnimator.


1
Je l'ai vu, mais c'est pour ajouter et supprimer des éléments après leur apparition. J'ai besoin de démarrer l'animation juste avant leur apparition. Merci quand même Mathieu.
PaulNunezM

1
Autant que je sache, vous devez alors utiliser un CustomAdapter.
MathieuMaree

20
Je me demandais si vous aviez expérimenté et peut-être résolu l'effet "coincé" avec ces animations dans RecyclerView? J'ai utilisé un code similaire pour ListView et mes éléments ont été animés sans problème, quelle que soit la vitesse de défilement, mais avec RecyclerView si je défile rapidement, certains éléments se bloquent parfois sur l'écran au-dessus des autres éléments et ils ne se cachent pas entièrement - c'est comme si l'animation était arrêtée avant la fin. J'ai en fait essayé de commenter la partie du code qui remplit les champs (en essayant d'accélérer l'exécution de la méthode onBindViewHolder), donc je n'ai laissé que le code pour animat
Tomislav

4
@MathieuMaree Merci pour cette animation stupéfiante.Elle semble bonne pour un défilement lent, mais sur des éléments de vue de recyclage à défilement rapide se chevauchent.Avez-vous trouvé ce problème? Toute suggestion pour surmonter ce problème.
Giru Bhai

40
@GiruBhai outrepasse onViewDetachedFromWindowet appelle clearAnimationla vue. Le problème est qu'il y a des animations en cours d'exécution lorsque RecyclerView essaie de réutiliser la vue.
Xample

62

J'ai animé le fondu des Recyclerviewéléments lorsqu'ils apparaissent pour la première fois, comme indiqué dans le code ci-dessous. Peut-être que cela sera utile à quelqu'un.

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);
}

Vous pouvez également remplacer setFadeAnimation()par ce qui suit setScaleAnimation()pour animer l'apparence des éléments en les mettant à l'échelle à partir d'un 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);
}

Le code ci-dessus a quelques verrues dans la mesure où lorsque vous faites défiler les RecyclerViewéléments, ils s'effacent toujours ou se dégradent. Si vous le souhaitez, vous pouvez ajouter du code pour permettre à l'animation de se produire uniquement lors de la création du fragment ou de l'activité contenant le RecyclerView(par exemple, obtenir l'heure système lors de la création et autoriser l'animation uniquement pour les premières millisecondes FADE_DURATION).


1
J'ai fait une petite modification pour que votre réponse fasse fonctionner l'animation sur le défilement vers le bas uniquement, vérifiez ma réponse
Basheer AL-MOMANI

1
cela gâche la mise en page (éléments de liste écrasés, certains éléments ayant une mauvaise couleur de texte) lors du défilement rapide de haut en bas
mrid

Ce n'est pas la manière appropriée ou recommandée d'animer des éléments de vue de recyclage. Vous devez utiliser la classe
ItemAnimator

25

J'ai créé une animation à partir de la réponse de pbm avec peu modificationde choses pour faire fonctionner l'aninmation une seule fois

en d'autres termes, 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;
    }
}

et onBindViewHolderappeler la fonction

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

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

// call Animation function
setAnimation(holder.itemView, position);            
}

1
Qu'est-ce que la dernière position ici? Est-ce arrayList.size () - 1
Sumit Shukla

1
lastPositionreprésente le nombre de vues rendues, donc c'est le début de sa valeur -1, chaque fois qu'une nouvelle vue est rendue, nous commençons une animation et
augmentons

15

Vous pouvez ajouter un android:layoutAnimation="@anim/rv_item_animation"attribut pour RecyclerViewaimer ceci:

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

merci pour l'excellent article ici: https://proandroiddev.com/enter-animation-using-recyclerview-and-layoutanimation-part-1-list-75a874a5d213


Cela a fonctionné pour moi lors du premier chargement d'une vue d'ensemble du recyclage, mais pas lors de l'ajout de nouveaux éléments. (Bien que je suppose que c'était la question si bon travail)
C. Skjerdal

8

Un bon point de départ est le suivant: https://github.com/wasabeef/recyclerview-animators/blob/master/animators/src/main/java/jp/wasabeef/recyclerview/adapters/AnimationAdapter.java

Vous n'avez même pas besoin de la bibliothèque complète, cette classe suffit. Ensuite, si vous implémentez simplement votre classe Adapter en donnant un animateur comme celui-ci:

@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);
}

vous verrez des éléments apparaître du bas pendant leur défilement, évitant également le problème avec le défilement rapide.


3

L'animation d'éléments dans la vue de recyclage lorsqu'ils sont liés dans l'adaptateur n'est peut-être pas la meilleure idée car cela peut entraîner l'animation des éléments de la vue de recyclage à différentes vitesses. Dans mon cas, l'élément à la fin de la vue de recyclage s'anime plus rapidement que ceux du haut car ceux du haut doivent continuer à voyager, ce qui le rendait désordonné.

Le code d'origine que j'ai utilisé pour animer chaque élément dans la vue de recyclage peut être trouvé ici:

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

Mais je vais copier et coller le code en cas de rupture du lien.

ÉTAPE 1: définissez cela dans votre méthode onCreate afin de vous assurer que l'animation ne s'exécute qu'une seule fois:

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

ÉTAPE 2: Vous devrez mettre ce code dans la méthode où vous souhaitez démarrer l'animation:

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

Dans le lien, l'auteur anime les icônes de la barre d'outils, il l'a donc mis dans cette méthode:

@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;
}

ÉTAPE 3: Maintenant, écrivez la logique de 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();
}

Mon alternative préférée:

Je préfère animer l'ensemble du recyclage au lieu des éléments à l'intérieur du recyclage.

Les ÉTAPES 1 et 2 restent les mêmes.

À l'ÉTAPE 3, dès que votre appel API revient avec vos données, je lance l'animation.

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

Cela animerait l'intégralité de votre recyclage afin qu'il vole depuis le bas de l'écran.


vous parlez d'animer l'ensemble du
recyclage

Oui je suis. Vous devriez lire le premier paragraphe sur la raison pour laquelle je pense que l'animation de l'ensemble du recyclage est une meilleure idée que chaque élément. Vous pouvez essayer d'animer chaque élément, mais il ne sera pas beau.
Simon

Qu'est-ce que c'est latestPostRecyclerview?
Antonio

1
Avez-vous lu le premier paragraphe de ma réponse où j'avais indiqué la raison pour laquelle l'animation des vues d'élément n'est pas une si bonne idée? Je suggère également d'essayer la solution acceptée et ensuite vous vous rendez compte du problème, revenez et essayez cette solution.
Simon

3

Créez cette méthode dans votre adaptateur Recyclerview

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

Et enfin, ajoutez cette ligne de code dans onBindViewHolder

setZoomInAnimation(holder.itemView);


2

En 2019, je suggérerais de mettre toutes les animations d'objets dans ItemAnimator.

Commençons par déclarer l'animateur dans la vue recycleur:

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

Déclarez ensuite l'animateur personnalisé,

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

}

Semblable à ceux ci-dessus, il y en a un pour la disparition animateDisappearance, pour l'ajout animateAdd, pour le changement animateChangeet le déplacement animateMove.

Un point important serait d'appeler les bons répartiteurs d'animation en leur sein.


Pouvez-vous fournir un exemple d'animation d'apparence personnalisée à l'aide de cette fonction de substitution? Je ne trouve pas d'exemple et je ne sais pas si je dois juste spécifier l'animation dans la fonction.
minar

1
gist.github.com/tadfisher/120d03f8380bfa8a16bf J'ai trouvé cela en ligne sur une recherche rapide, cela donne une idée du fonctionnement de la surcharge
Dinesh

0

Prolonge simplement votre adaptateur comme ci-dessous

public class RankingAdapter extends AnimatedRecyclerView<RankingAdapter.ViewHolder> 

Et ajoutez une super méthode à onBindViewHolder

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

C'est un moyen automatisé de créer un adaptateur animé comme "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;
        }
    }

}

0

Je pense qu'il vaut mieux l'utiliser comme ceci: (dans l' adaptateur RecyclerView, remplacez juste une méthode)

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

    setBindAnimation(holder)
}

Si vous voulez que chaque animation soit attachée dans RV.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.