2015-11-14 24 views
24

Il RecyclerView, a differenza di ListView, non ha un modo semplice per impostare una vista vuota ad esso, quindi si deve gestirlo manualmente, rendendo la vista vuota visibile in caso di conteggio degli elementi dell'adattatore è 0.Rileva l'animazione in Android RecyclerView

attuazione di questo, in un primo momento cercato di chiamare logica vista vuoto subito dopo la modifica struttura underlaying (ArrayList nel mio caso), ad esempio:

btnRemoveFirst.setOnClickListener(new View.OnClickListener() { 
    @Override 
    public void onClick(View view) { 
     devices.remove(0); // remove item from ArrayList 
     adapter.notifyItemRemoved(0); // notify RecyclerView's adapter 
     updateEmptyView(); 
    } 
}); 

Si fa la cosa, ma ha un inconveniente: quando viene rimosso l'ultimo elemento, viene visualizzata la vista vuota prima di La riparazione di rimozione è terminata, immediatamente dopo la rimozione. Così ho deciso di aspettare fino alla fine dell'animazione e quindi aggiornare l'interfaccia utente.

Con mia sorpresa, non sono riuscito a trovare un buon modo per ascoltare gli eventi di animazione in RecyclerView. La prima cosa a venire in mente è quello di utilizzare isRunning metodo come questo:

btnRemoveFirst.setOnClickListener(new View.OnClickListener() { 
    @Override 
    public void onClick(View view) { 
     devices.remove(0); // remove item from ArrayList 
     adapter.notifyItemRemoved(0); // notify RecyclerView's adapter 
     recyclerView.getItemAnimator().isRunning(new RecyclerView.ItemAnimator.ItemAnimatorFinishedListener() { 
      @Override 
      public void onAnimationsFinished() { 
       updateEmptyView(); 
      } 
     }); 
    } 
}); 

Purtroppo, richiamata in questo caso viene eseguito immediatamente, perché in quel momento interiore ItemAnimator ancora non è in stato di "esecuzione". Quindi, le domande sono: come utilizzare correttamente il metodo ItemAnimator.isRunning() e c'è un modo migliore per ottenere il risultato desiderato, ovvero mostra la vista vuota dopo che l'animazione di rimozione del singolo elemento è terminata?

risposta

14

Attualmente l'unico modo di lavoro che ho trovato per risolvere questo problema è quello di estendere ItemAnimator e passarlo al RecyclerView in questo modo:

recyclerView.setItemAnimator(new DefaultItemAnimator() { 
    @Override 
    public void onAnimationFinished(RecyclerView.ViewHolder viewHolder) { 
     updateEmptyView(); 
    } 
}); 

Ma questa tecnica non è universale, perché devo estendersi da cemento Implementazione ItemAnimator utilizzata da RecyclerView. Nel caso di CoolItemAnimator interno privato all'interno di CoolRecyclerView, il mio metodo non funzionerà affatto.


PS: Il mio collega ha suggerito di avvolgere ItemAnimator all'interno del decorator in maniera seguente:

recyclerView.setItemAnimator(new ListenableItemAnimator(recyclerView.getItemAnimator())); 

Sarebbe bello, nonostante sembra eccessivo per un tale compito banale, ma la creazione del decorator in questo caso non è possibile in ogni caso, perché ItemAnimator ha un metodo setListener() che è protetto da un pacchetto, quindi ovviamente non posso inserirlo, così come diversi metodi finali.

+0

Ehi romano. Ti è mai capitato di trovare una soluzione più centralizzata a questo problema? – Ryan

+0

@Ryan: Purtroppo no. Ma non ho fatto ricerche su questo problema dal momento della mia risposta. –

0

Per ampliare la risposta di Roman Petrenko, non ho una risposta veramente universale, ma ho trovato il modello Factory come un modo utile per pulire almeno un po 'del problema che è questo problema.

public class ItemAnimatorFactory { 

    public interface OnAnimationEndedCallback{ 
     void onAnimationEnded(); 
    } 
    public static RecyclerView.ItemAnimator getAnimationCallbackItemAnimator(OnAnimationEndedCallback callback){ 
     return new FadeInAnimator() { 
      @Override 
      public void onAnimationFinished(RecyclerView.ViewHolder viewHolder) { 
       callback.onAnimationEnded(); 
       super.onAnimationEnded(viewHolder); 
      } 
     }; 
    } 
} 

Nel mio caso, sto usando una libreria che fornisce un FadeInAnimator che stavo già utilizzando. Uso la soluzione di Roman nel metodo factory per collegarmi all'evento onAnimationEnded, quindi passa l'evento a fare il backup della catena.

Poi, quando sto configurando il mio recyclerview, a specificare il callback per essere il mio metodo di aggiornamento della vista in base all'elemento recyclerview contare:

mRecyclerView.setItemAnimator(ItemAnimatorFactory.getAnimationCallbackItemAnimator(this::checkSize)); 

Anche in questo caso, non è del tutto universale per tutte ogni e tutti gli ItemAnimators, ma almeno "consolida il cruft", quindi se hai più animatori di oggetti diversi, puoi semplicemente implementare un metodo factory qui seguendo lo stesso pattern, e quindi la tua configurazione di recyclerview sta solo specificando quale ItemAnimator vuoi.

1

Quello che ha funzionato per me è il seguente:

  • rilevare che un titolare di vista è stato rimosso
  • in questo caso, registrare un listener per essere avvisati quando dispatchAnimationsFinished() si chiama
  • quando tutte le animazioni sono finiti , chiamare un ascoltatore per eseguire l'operazione (updateEmptyView())

public class CompareItemAnimator extends DefaultItemAnimator implements RecyclerView.ItemAnimator.ItemAnimatorFinishedListener { 

private OnItemAnimatorListener mOnItemAnimatorListener; 

public interface OnItemAnimatorListener { 
    void onAnimationsFinishedOnItemRemoved(); 
} 

@Override 
public void onAnimationsFinished() { 
    if (mOnItemAnimatorListener != null) { 
     mOnItemAnimatorListener.onAnimationsFinishedOnItemRemoved(); 
    } 
} 

public void setOnItemAnimatorListener(OnItemAnimatorListener onItemAnimatorListener) { 
    mOnItemAnimatorListener = onItemAnimatorListener; 
} 

@Override 
public void onRemoveFinished(RecyclerView.ViewHolder viewHolder) { 
    isRunning(this); 
}} 
+0

Questo ha funzionato per me – BQuadra

4

Ho un caso un po 'più generico in cui voglio rilevare quando la visualizzazione del riciclatore ha terminato completamente l'animazione quando uno o più elementi sono stati rimossi o aggiunti allo stesso tempo.

Ho provato la risposta di Roman Petrenko, ma in questo caso non funziona. Il problema è che viene chiamato onAnimationFinished per ciascuna voce nella visualizzazione del recycler. La maggior parte delle voci non è cambiata, pertanto onAnimationFinished viene chiamato più o meno istantaneo. Ma per le aggiunte e le rimozioni l'animazione richiede un po 'di tempo, quindi viene chiamata in seguito.

Ciò comporta almeno due problemi. Supponiamo di avere un metodo chiamato doStuff() che si desidera eseguire quando l'animazione è terminata.

  1. Se è sufficiente chiamare doStuff() in onAnimationFinished si chiamerà una volta per ogni elemento nella vista riciclatore che potrebbe non essere quello che si vuole fare.

  2. Se si chiama doStuff() la prima volta che viene chiamato onAnimationFinished, è possibile che si stia chiamando molto prima che l'ultima animazione sia stata completata.

Se si potesse sapere quanti elementi ci sono per essere animato si potrebbe fare in modo di chiamare doStuff() quando termina l'ultima animazione. Ma non ho trovato alcun modo per sapere quante animazioni rimanenti ci sono in coda.

La mia soluzione a questo problema è lasciare che la visualizzazione del riciclatore inizi ad animare utilizzando new Handler().post(), quindi impostare un listener con isRunning() che viene chiamato quando l'animazione è pronta. Dopodiché ripete il processo fino a quando tutte le visualizzazioni sono state animate.

void changeAdapterData() { 
    // ... 
    // Changes are made to the data held by the adapter 
    recyclerView.getAdapter().notifyDataSetChanged(); 

    // The recycler view have not started animating yet, so post a message to the 
    // message queue that will be run after the recycler view have started animating. 
    new Handler().post(waitForAnimationsToFinishRunnable); 
} 

private Runnable waitForAnimationsToFinishRunnable = new Runnable() { 
    @Override 
    public void run() { 
     waitForAnimationsToFinish(); 
    } 
}; 

// When the data in the recycler view is changed all views are animated. If the 
// recycler view is animating, this method sets up a listener that is called when the 
// current animation finishes. The listener will call this method again once the 
// animation is done. 
private void waitForAnimationsToFinish() { 
    if (recyclerView.isAnimating()) { 
     // The recycler view is still animating, try again when the animation has finished. 
     recyclerView.getItemAnimator().isRunning(animationFinishedListener); 
     return; 
    } 

    // The recycler view have animated all it's views 
    onRecyclerViewAnimationsFinished(); 
} 

// Listener that is called whenever the recycler view have finished animating one view. 
private RecyclerView.ItemAnimator.ItemAnimatorFinishedListener animationFinishedListener = 
     new RecyclerView.ItemAnimator.ItemAnimatorFinishedListener() { 
    @Override 
    public void onAnimationsFinished() { 
     // The current animation have finished and there is currently no animation running, 
     // but there might still be more items that will be animated after this method returns. 
     // Post a message to the message queue for checking if there are any more 
     // animations running. 
     new Handler().post(waitForAnimationsToFinishRunnable); 
    } 
}; 

// The recycler view is done animating, it's now time to doStuff(). 
private void onRecyclerViewAnimationsFinished() { 
    doStuff(); 
} 
+0

Funziona perfettamente, inoltre ho provato quando il metodo '' 'notifyItemRemoved (position)' '' e funziona anche per quanto riguarda. –