Jak animować elementy RecyclerView, gdy się pojawią

237

Jak mogę animować elementy RecyclerView, kiedy się pojawią?

Domyślny animator elementów animuje tylko wtedy, gdy dane są dodawane lub usuwane po ustawieniu danych recyklera. Jestem nowymi programistami i nie mam pojęcia, od czego zacząć.

Wszelkie pomysły, jak to osiągnąć?

PaulNunezM
źródło

Odpowiedzi:

42

Uprościło tylko za pomocą XML

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

Użyj w układach i widoku recylcer, takich jak:

<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
źródło
1
@ArnoldBrown Zmieniając plik animacji. Patrz: stackoverflow.com/questions/5151591/…
iamnaran
To zdecydowanie najbardziej praktyczna odpowiedź.
Oliver Metz
jak sprawić, by działało to przy każdym otwarciu listy, ponieważ teraz robi to tylko po raz pierwszy.
Hiwa Jalal,
7
Musisz zadzwonić recyclerView.scheduleLayoutAnimation()po zmianie zestawu danych, jeśli nie, animacja nie zadziała.
zeleven
Czy powinno to działać, gdy przedmioty są poddawane recyklingowi i wracają do widoku? Wypróbowałem to rozwiązanie i działa on świetnie w przypadku początkowej animacji, gdy pierwszy raz widzisz układ. Po przewinięciu elementy nie mają animacji podczas powrotu do widoku.
Jason p
315

EDYTOWAĆ :

Zgodnie z dokumentacją ItemAnimator :

Ta klasa definiuje animacje zachodzące na przedmiotach w miarę wprowadzania zmian w adapterze.

Więc jeśli nie dodasz swoich elementów jeden po drugim RecyclerViewi nie odświeżysz widoku przy każdej iteracji, nie sądzę, że ItemAnimatorjest to rozwiązanie dla twojej potrzeby.

Oto w jaki sposób można animować RecyclerViewelementy, które pojawiają się za pomocą 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;
        }
    }
}

A Twój custom_item_layout wyglądałby tak:

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

Aby uzyskać więcej informacji na temat adapterów niestandardowych i RecyclerViewzapoznaj się z tym szkoleniem w oficjalnej dokumentacji .

Problemy z szybkim przewijaniem

Korzystanie z tej metody może powodować problemy z szybkim przewijaniem. Widok może być ponownie wykorzystany podczas trwania animacji. Aby tego uniknąć, zaleca się wyczyszczenie animacji po odłączeniu.

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

W CustomViewHolder:

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

Stara odpowiedź:

Rzuć okiem na repozytorium Gabriele Mariotti , jestem prawie pewien, że znajdziesz to, czego potrzebujesz. Zapewnia proste ItemAnimators dla RecyclerView, takie jak SlideInItemAnimator lub SlideScaleItemAnimator.

MathieuMaree
źródło
1
Widziałem to, ale służy to dodawaniu i usuwaniu przedmiotów po ich pojawieniu się. Muszę rozpocząć animację, zanim się pojawią. W każdym razie dzięki Mathieu.
PaulNunezM,
1
O ile mi wiadomo, musisz wtedy użyć CustomAdapter.
MathieuMaree,
20
Zastanawiałem się, czy doświadczyłeś i może rozwiązałeś efekt „zablokowania” dzięki tym animacjom w RecyclerView? Użyłem podobnego kodu dla ListView, a moje elementy były animowane bez problemu, bez względu na to, jak szybko przewijałem, ale z RecyclerView, jeśli przewijam szybko, niektóre elementy czasami blokują się na ekranie na innych elementach i nie chowają się całkowicie - to tak, jakby animacja została zatrzymana przed zakończeniem. W rzeczywistości próbowałem skomentować część kodu, która wypełnia pola (próbę przyspieszenia wykonywania metody onBindViewHolder), więc zostawiłem tylko kod animat
Tomislav
4
@MathieuMaree Dzięki za tę zadziwiającą animację. Wygląda dobrze na powolne przewijanie, ale na szybkim przewijaniu recyklingview elementy się nakładają. Czy znalazłeś tę zagadkę? Wszelkie sugestie, aby rozwiązać ten problem.
Giru Bhai
40
@GiruBhai przesłonić onViewDetachedFromWindowi wywołać clearAnimationwidok. Problem polega na tym, że są uruchomione animacje, gdy RecyclerView próbuje ponownie użyć widoku.
Xample
62

Animowałem zanikanie Recyclerviewprzedmiotów, gdy pojawiają się po raz pierwszy, jak pokazano w poniższym kodzie. Być może przyda się to komuś.

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

Możesz także zastąpić setFadeAnimation()następujące elementy, setScaleAnimation()aby animować wygląd przedmiotów, skalując je od punktu:

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

Powyższy kod zawiera brodawki, o ile podczas przewijania RecyclerViewelementy zawsze blakną lub skalują się. Jeśli chcesz, możesz dodać kod, aby zezwolić na animację, gdy fragment lub działanie zawierające je RecyclerViewzostanie utworzone po raz pierwszy (np. Uzyskaj czas systemowy na utworzenie i zezwól na animację tylko dla pierwszych milisekund FADE_DURATION).

pbm
źródło
1
Zrobiłem małą modyfikację dla twojej odpowiedzi, aby animacja działała tylko na przewijaniu w dół, sprawdź moją odpowiedź
Basheer AL-MOMANI
1
psuje to układ (nadpisane elementy listy, niektóre elementy mają niewłaściwy kolor tekstu) przy szybkim przewijaniu w górę i w dół
mrid
To nie jest właściwy ani zalecany sposób animowania przedmiotów z recyklingu. Musisz używać klasy
ItemAnimator
25

Stworzyłem animację na podstawie odpowiedzi PBM niewiele, modificationaby aninmacja przebiegła tylko raz

innymi słowy 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;
    }
}

i w onBindViewHolderwywołaniu funkcji

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

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

// call Animation function
setAnimation(holder.itemView, position);            
}
Basheer AL-MOMANI
źródło
1
Co to jest lastPosition tutaj? Czy to arrayList.size () - 1
Sumit Shukla
1
lastPositionreprezentuje liczbę renderowanych widoków, więc jest to jego początkowa wartość -1, za każdym razem, gdy renderowany jest nowy widok, rozpoczynamy animację i zwiększamy pozycję
Basheer AL-MOMANI
15

Możesz dodać android:layoutAnimation="@anim/rv_item_animation"atrybut RecyclerViewpodobny do tego:

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

dzięki za doskonały artykuł tutaj: https://proandroiddev.com/enter-animation-using-recyclerview-and-layoutanimation-part-1-list-75a874a5d213

Pavel Biryukov
źródło
To działało dla mnie, gdy przeglądarka ładuje się po raz pierwszy, ale nie przy dodawaniu nowych elementów. (Myślę, że to było pytanie, więc dobra robota)
C. Skjerdal,
8

Dobrym miejscem do rozpoczęcia jest: https://github.com/wasabeef/recyclerview-animators/blob/master/animators/src/main/java/jp/wasabeef/recyclerview/adapters/AnimationAdapter.java

Nie potrzebujesz nawet pełnej biblioteki, ta klasa wystarczy. Następnie, jeśli po prostu zaimplementujesz klasę Adaptera, dając animatorowi tak:

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

podczas przewijania zobaczysz elementy pojawiające się od dołu, unikając także problemu z szybkim przewijaniem.

Alessandro Crugnola
źródło
3

Animowanie elementów w widoku recyklera, gdy są one powiązane w adapterze, może nie być najlepszym pomysłem, ponieważ może powodować animację elementów w widoku recyklera przy różnych prędkościach. W moim przypadku element na końcu recyrkulatora ożywi się do swojej pozycji szybciej niż te na górze, ponieważ te na górze muszą się dalej przemieszczać, przez co wyglądają niechlujnie.

Oryginalny kod, którego użyłem do animacji każdego elementu w widoku recyklingu, można znaleźć tutaj:

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

Ale skopiuję i wkleję kod na wypadek, gdyby link się zepsuł.

KROK 1: Ustaw to w swojej metodzie onCreate, tak aby animacja była uruchamiana tylko raz:

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

KROK 2: Musisz wprowadzić ten kod do metody, w której chcesz rozpocząć animację:

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

W łączu pisarz animuje ikony paska narzędzi, więc umieścił go w tej metodzie:

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

KROK 3: Teraz napisz logikę dla 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();
}

Moja preferowana alternatywa:

Wolałbym animować cały widok recyclingler zamiast elementów w widoku recyclingler.

KROK 1 i 2 pozostają takie same.

W KROKU 3, gdy tylko wywołanie interfejsu API powróci z danymi, rozpocznę animację.

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

Spowoduje to animację całego widoku recyklera, tak aby leciał od dołu ekranu.

Szymon
źródło
mówisz o animowaniu całego widoku recyklera
Longerian
Tak jestem. Powinieneś przeczytać pierwszy akapit, dlaczego myślę, że animowanie całego widoku recyklingu jest lepszym pomysłem niż każdy element. Możesz spróbować animować każdy element, ale nie będzie on dobrze wyglądał.
Simon
Co to jest latestPostRecyclerview?
Antonio
1
Czy przeczytałeś pierwszy akapit mojej odpowiedzi, w którym podałem powód, dla którego animowanie widoków przedmiotów nie jest tak świetnym pomysłem? Proponuję również wypróbować zaakceptowane rozwiązanie, a następnie zdasz sobie sprawę z problemu, wróć i wypróbuj to rozwiązanie.
Simon
3

Utwórz tę metodę w adapterze recyclinglerview

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

I wreszcie dodaj ten wiersz kodu w onBindViewHolder

setZoomInAnimation(holder.itemView);

Md.Tarikul Islam
źródło
2

W 2019 r. Sugerowałbym umieszczenie wszystkich animacji przedmiotów w ItemAnimator.

Zacznijmy od zadeklarowania animatora w widoku recyklingu:

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

Zadeklaruj niestandardowego animatora,

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

}

Podobnie jak powyższe, jeden dotyczy zniknięcia animateDisappearance, dodania animateAdd, zmiany animateChangei przeniesienia animateMove.

Jednym z ważnych punktów byłoby nazywanie w nich właściwych dyspozytorów animacji.

Dinesh
źródło
Czy możesz podać przykład animacji o niestandardowym wyglądzie, używając tej funkcji zastępowania? Nie mogę znaleźć przykładu i nie jestem pewien, czy muszę tylko określić animację w funkcji.
Minar
1
gist.github.com/tadfisher/120d03f8380bfa8a16bf Znalazłem to w Internecie podczas szybkiego wyszukiwania, to daje wyobrażenie o tym, jak działa przeciążanie
Dinesh
0

Po prostu rozszerza swój adapter, jak poniżej

public class RankingAdapter extends AnimatedRecyclerView<RankingAdapter.ViewHolder> 

I dodaj super metodę do onBindViewHolder

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

To zautomatyzowany sposób tworzenia animowanych adapterów, takich jak „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;
        }
    }

}
EliaszKubala
źródło
0

Myślę, że lepiej użyć tego w ten sposób: (w adapterze RecyclerView zastąp tylko jedną metodę)

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

    setBindAnimation(holder)
}

Jeśli chcesz, aby każda załączona animacja była w RV.

Milan Jurkulak
źródło