Cómo animar elementos RecyclerView cuando aparecen

237

¿Cómo puedo animar los elementos de RecyclerView cuando aparecen?

El animador de elementos predeterminado solo anima cuando se agregan o eliminan datos después de que se hayan establecido los datos del reciclador. Estoy desarrollando nuevas aplicaciones y no tengo ni idea de por dónde empezar.

¿Alguna idea de cómo lograr esto?

PaulNunezM
fuente

Respuestas:

42

Hecho simple con XML solamente

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

Úselo en diseños y vistas de la lista como:

<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" />
iamnaran
fuente
1
@ArnoldBrown Al cambiar el archivo de animación. Consulte: stackoverflow.com/questions/5151591/…
iamnaran
Esta es, con mucho, la respuesta más práctica.
Oliver Metz
cómo hacer que esto funcione cada vez que se abre la lista, porque ahora solo lo hace por primera vez.
Hiwa Jalal
77
Debe llamar recyclerView.scheduleLayoutAnimation()después de cambiar el conjunto de datos; de lo contrario, la animación no funcionaría.
zeleven
¿Debería funcionar esto cuando los artículos se reciclan y vuelven a la vista? Intenté esta solución y funciona muy bien para la animación inicial cuando puedes ver el diseño por primera vez. Después de desplazarse, los elementos no tienen la animación cuando vuelven a verse.
Jason p
315

EDITAR:

De acuerdo con la documentación del ItemAnimator :

Esta clase define las animaciones que tienen lugar en los elementos a medida que se realizan cambios en el adaptador.

Entonces, a menos que agregue sus elementos uno por uno RecyclerViewy actualice la vista en cada iteración, no creo que ItemAnimatorsea ​​la solución a su necesidad.

Así es como puede animar los RecyclerViewelementos cuando aparecen con un Adaptador personalizado:

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

Y su custom_item_layout se vería así:

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

Para obtener más información acerca de CustomAdapters y RecyclerView, consulte esta capacitación en la documentación oficial .

Problemas en el desplazamiento rápido

El uso de este método podría causar problemas con el desplazamiento rápido. La vista se puede reutilizar mientras se realiza la animación. Para evitar eso, es recomendable borrar la animación cuando se separa.

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

En CustomViewHolder:

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

Vieja respuesta:

Echa un vistazo al repositorio de Gabriele Mariotti , estoy bastante seguro de que encontrará lo que necesita. Proporciona ItemAnimators simples para RecyclerView, como SlideInItemAnimator o SlideScaleItemAnimator.

MathieuMaree
fuente
1
Lo he visto, pero eso es para agregar y eliminar elementos después de que aparecieron. Necesito comenzar la animación justo antes de que aparezcan. Gracias de todos modos Mathieu.
PaulNunezM
1
Hasta donde yo sé, entonces necesitas usar un CustomAdapter.
MathieuMaree
20
Me preguntaba si has experimentado y quizás resuelto el efecto "atascado" con estas animaciones en RecyclerView. Utilicé un código similar para ListView y mis elementos se animaron sin problemas, no importa cuán rápido me desplazara, pero con RecyclerView si me desplazo rápido, algunos elementos a veces se atascan en la pantalla sobre los otros elementos y no se ocultan por completo. - Es como si la animación se detuviera antes de que terminara. De hecho, intenté comentar la parte del código que llena los campos (tratando de acelerar la ejecución del método onBindViewHolder), así que solo dejé el código para animat
Tomislav
44
@MathieuMaree Gracias por esta sorprendente animación. Se ve bien para el desplazamiento lento, pero en el desplazamiento rápido, los elementos de la vista de reciclaje se superponen. ¿Encontró este problema? ¿Alguna sugerencia para solucionar este problema?
Giru Bhai
40
@GiruBhai anula onViewDetachedFromWindowy llama clearAnimationa la vista. El problema es que hay animaciones ejecutándose cuando RecyclerView intenta reutilizar la vista.
Xample
62

Animé el desvanecimiento de los Recyclerviewelementos cuando aparecen por primera vez como se muestra en el código a continuación. Quizás esto sea de utilidad para alguien.

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

También puede reemplazarlo setFadeAnimation()con lo siguiente setScaleAnimation()para animar la apariencia de los elementos al escalarlos desde un punto:

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

El código anterior tiene algunas verrugas en cuanto a que al desplazar los RecyclerViewelementos siempre se desvanecen o escalan. Si lo desea, puede agregar código para permitir que la animación suceda cuando RecyclerViewse crea el fragmento o la actividad que contiene la primera (por ejemplo, obtenga la hora del sistema en la creación y solo permita la animación durante los primeros milisegundos de FADE_DURATION).

pbm
fuente
1
Hice una pequeña modificación para su respuesta para que la animación funcione solo en el desplazamiento hacia abajo, verifique mi respuesta
Basheer AL-MOMANI
1
este botín la disposición (artículo en su lista escrita sobre-, algunos elementos que tienen mal color del texto) en un desplazamiento rápido hacia arriba y hacia abajo
MRID
Esta no es la forma adecuada o recomendada de animar los artículos de la vista del reciclador. Debe estar utilizando la clase
ItemAnimator
25

Creé animación a partir de la respuesta de pbm con poco modificationpara hacer que la aninmation se ejecute solo una vez

en otras palabras el 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;
    }
}

y en onBindViewHolderllamar a la función

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

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

// call Animation function
setAnimation(holder.itemView, position);            
}
Basheer AL-MOMANI
fuente
1
¿Qué es lastPosition aquí? ¿Es arrayList.size () - 1
Sumit Shukla
1
lastPositionrepresenta el número de vistas renderizadas, por lo que es el comienzo de su valor -1, cada vez que se muestra una nueva vista comenzamos una animación y aumentamos la posición
Basheer AL-MOMANI
15

Puede agregar un android:layoutAnimation="@anim/rv_item_animation"atributo RecyclerViewcomo este:

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

gracias por el excelente artículo aquí: https://proandroiddev.com/enter-animation-using-recyclerview-and-layoutanimation-part-1-list-75a874a5d213

Pavel Biryukov
fuente
Esto funcionó para mí cuando se carga por primera vez una vista de reciclador, pero no cuando se agregan nuevos elementos. (Aunque supongo que esa fue la pregunta tan buen trabajo)
C. Skjerdal
8

Un buen lugar para comenzar es este: https://github.com/wasabeef/recyclerview-animators/blob/master/animators/src/main/java/jp/wasabeef/recyclerview/adapters/AnimationAdapter.java

Ni siquiera necesita la biblioteca completa, esa clase es suficiente. Entonces, si solo implementa su clase de Adaptador dando un animador como este:

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

verá elementos que aparecen desde la parte inferior a medida que se desplazan, evitando también el problema con el desplazamiento rápido.

Alessandro Crugnola
fuente
3

Animar elementos en la vista de reciclaje cuando están unidos en el adaptador podría no ser la mejor idea, ya que puede hacer que los elementos en la vista de reciclaje se animen a diferentes velocidades. En mi caso, el artículo al final de la vista del reciclador se anima a su posición más rápido que los que están en la parte superior, ya que los que están en la parte superior tienen que viajar más, por lo que se ve desordenado.

El código original que utilicé para animar cada elemento en la vista del reciclador se puede encontrar aquí:

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

Pero copiaré y pegaré el código en caso de que se rompa el enlace.

PASO 1: configure esto dentro de su método onCreate para asegurarse de que la animación solo se ejecute una vez:

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

PASO 2: Deberá poner este código en el método donde desea iniciar la animación:

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

En el enlace, el escritor está animando los iconos de la barra de herramientas, por lo que lo puso dentro de este método:

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

PASO 3: Ahora escriba la lógica para 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();
}

Mi alternativa preferida:

Prefiero animar toda la vista del reciclador en lugar de los elementos dentro de la vista del reciclador.

Los PASOS 1 y 2 siguen siendo los mismos.

En el PASO 3, tan pronto como su llamada API regrese con sus datos, comenzaría la animación.

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

Esto animaría toda su vista de reciclador para que vuele desde la parte inferior de la pantalla.

Simón
fuente
estás hablando de animar toda la vista del reciclador
Longerian
Sí, yo soy. Debería leer el primer párrafo sobre por qué creo que animar toda la vista del reciclador es una mejor idea que cada elemento. Puedes intentar animar cada elemento pero no se verá bien.
Simon
¿Qué es latestPostRecyclerview?
Antonio
1
¿Leíste el primer párrafo de mi respuesta donde dije la razón por la cual animar vistas de elementos no es una gran idea? También sugeriría probar la solución aceptada y luego se dará cuenta del problema, vuelva y pruebe esta solución.
Simon
3

Cree este método en su adaptador de vista de reciclaje

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

Y finalmente agregue esta línea de código en onBindViewHolder

setZoomInAnimation(holder.itemView);

Md.Tarikul Islam
fuente
2

En 2019, sugeriría poner todas las animaciones de elementos en el animador de elementos.

Comencemos declarando el animador en la vista de reciclador:

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

Declara el animador personalizado entonces,

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 a las anteriores, hay una para desaparición animateDisappearance, para agregar animateAdd, para cambiar animateChangey moveranimateMove .

Un punto importante sería llamar a los despachadores de animación correctos dentro de ellos.

Dinesh
fuente
¿Podría proporcionar un ejemplo de una animación de apariencia personalizada con esta función de anulación? No puedo encontrar un ejemplo y no estoy seguro si solo tengo que especificar la animación en la función.
minar
1
gist.github.com/tadfisher/120d03f8380bfa8a16bf Encontré esto en línea en una búsqueda rápida, esto da una idea de cómo funciona la sobrecarga
Dinesh
0

Solo extiende su adaptador como a continuación

public class RankingAdapter extends AnimatedRecyclerView<RankingAdapter.ViewHolder> 

Y agregue un súper método a onBindViewHolder

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

Es una forma automatizada de crear un adaptador animado como "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
fuente
0

Creo que es mejor usarlo así: (en el adaptador RecyclerView se anula solo un método)

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

    setBindAnimation(holder)
}

Si desea cada animación adjunta allí en RV.

Milan Jurkulak
fuente