Cómo proporcionar animación personalizada durante la clasificación (notifyDataSetChanged) en RecyclerView

Actualmente, usando el animador predeterminado android.support.v7.widget.DefaultItemAnimator , he aquí el resultado que estoy teniendo durante la clasificación

Video de animación DefaultItemAnimator: https://youtu.be/EccI7RUcdbg

 public void sortAndNotifyDataSetChanged() { int i0 = 0; int i1 = models.size() - 1; while (i0 < i1) { DemoModel o0 = models.get(i0); DemoModel o1 = models.get(i1); models.set(i0, o1); models.set(i1, o0); i0++; i1--; //break; } // adapter is created via adapter = new RecyclerViewDemoAdapter(models, mRecyclerView, this); adapter.notifyDataSetChanged(); } 

Sin embargo, en lugar de la animación predeterminada durante la clasificación (notifyDataSetChanged), prefiero proporcionar animación personalizada como sigue. El artículo antiguo se deslizará hacia fuera a través del lado derecho, y el nuevo artículo se deslizará hacia arriba.

Vídeo de animación esperado: https://youtu.be/9aQTyM7K4B0

Cómo lograr esa animación sin RecylerView

Hace pocos años, logro este efecto usando LinearLayout + View , ya que en ese momento, todavía no tenemos RecyclerView .

Así es como se está configurando la animación

 PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 1.0f, 0f); PropertyValuesHolder translationX = PropertyValuesHolder.ofFloat("translationX", 0f, (float) width); ObjectAnimator animOut = ObjectAnimator.ofPropertyValuesHolder(this, alpha, translationX); animOut.setDuration(duration); animOut.setInterpolator(accelerateInterpolator); animOut.addListener(new AnimatorListenerAdapter() { public void onAnimationEnd(Animator anim) { final View view = (View) ((ObjectAnimator) anim).getTarget(); Message message = (Message)view.getTag(R.id.TAG_MESSAGE_ID); if (message == null) { return; } view.setAlpha(0f); view.setTranslationX(0); NewsListFragment.this.refreshUI(view, message); final Animation animation = AnimationUtils.loadAnimation(NewsListFragment.this.getActivity(), R.anim.slide_up); animation.setAnimationListener(new Animation.AnimationListener() { @Override public void onAnimationStart(Animation animation) { } @Override public void onAnimationEnd(Animation animation) { view.setVisibility(View.VISIBLE); view.setTag(R.id.TAG_MESSAGE_ID, null); } @Override public void onAnimationRepeat(Animation animation) { } }); view.startAnimation(animation); } }); layoutTransition.setAnimator(LayoutTransition.DISAPPEARING, animOut); this.nowLinearLayout.setLayoutTransition(layoutTransition); 

Y, así es como se anima la animación.

 // messageView is view being added earlier in nowLinearLayout for (int i = 0, ei = messageViews.size(); i < ei; i++) { View messageView = messageViews.get(i); messageView.setTag(R.id.TAG_MESSAGE_ID, messages.get(i)); messageView.setVisibility(View.INVISIBLE); } 

Me preguntaba, ¿cómo puedo lograr el mismo efecto en RecylerView?

2 Solutions collect form web for “Cómo proporcionar animación personalizada durante la clasificación (notifyDataSetChanged) en RecyclerView”

Aquí hay una dirección más a la que puede mirar, si no desea que su desplazamiento se reinicie en cada tipo ( proyecto de demostración de GITHUB ):

Utilice algún tipo de RecyclerView.ItemAnimator , pero en lugar de volver a escribir las animateAdd() y animateRemove() , puede implementar animateChange() y animateChangeImpl() . Después de ordenar puede llamar a adapter.notifyItemRangeChanged(0, mItems.size()); A la animación triger. Así que el código para activar la animación se verá bastante simple:

 for (int i = 0, j = mItems.size() - 1; i < j; i++, j--) Collections.swap(mItems, i, j); adapter.notifyItemRangeChanged(0, mItems.size()); 

Para el código de animación se puede utilizar android.support.v7.widget.DefaultItemAnimator , pero esta clase tiene animateChangeImpl() privado, por lo que tendrá que copiar el código pegado y cambió este método o utilizar la reflexión. O puede crear su propia clase @Andreas Wenger como @Andreas Wenger hizo en su ejemplo de SlidingAnimator . El punto aquí es implementar animateChangeImpl Simmilar a su código hay 2 animaciones:

1) Deslice la vista antigua a la derecha

 private void animateChangeImpl(final ChangeInfo changeInfo) { final RecyclerView.ViewHolder oldHolder = changeInfo.oldHolder; final View view = oldHolder == null ? null : oldHolder.itemView; final RecyclerView.ViewHolder newHolder = changeInfo.newHolder; final View newView = newHolder != null ? newHolder.itemView : null; if (view == null) return; mChangeAnimations.add(oldHolder); final ViewPropertyAnimatorCompat animOut = ViewCompat.animate(view) .setDuration(getChangeDuration()) .setInterpolator(interpolator) .translationX(view.getRootView().getWidth()) .alpha(0); animOut.setListener(new VpaListenerAdapter() { @Override public void onAnimationStart(View view) { dispatchChangeStarting(oldHolder, true); } @Override public void onAnimationEnd(View view) { animOut.setListener(null); ViewCompat.setAlpha(view, 1); ViewCompat.setTranslationX(view, 0); dispatchChangeFinished(oldHolder, true); mChangeAnimations.remove(oldHolder); dispatchFinishedWhenDone(); // starting 2-nd (Slide Up) animation if (newView != null) animateChangeInImpl(newHolder, newView); } }).start(); } 

2) Deslice hacia arriba una nueva vista

 private void animateChangeInImpl(final RecyclerView.ViewHolder newHolder, final View newView) { // setting starting pre-animation params for view ViewCompat.setTranslationY(newView, newView.getHeight()); ViewCompat.setAlpha(newView, 0); mChangeAnimations.add(newHolder); final ViewPropertyAnimatorCompat animIn = ViewCompat.animate(newView) .setDuration(getChangeDuration()) .translationY(0) .alpha(1); animIn.setListener(new VpaListenerAdapter() { @Override public void onAnimationStart(View view) { dispatchChangeStarting(newHolder, false); } @Override public void onAnimationEnd(View view) { animIn.setListener(null); ViewCompat.setAlpha(newView, 1); ViewCompat.setTranslationY(newView, 0); dispatchChangeFinished(newHolder, false); mChangeAnimations.remove(newHolder); dispatchFinishedWhenDone(); } }).start(); } 

Aquí está la imagen de demostración con el trabajo de desplazamiento y un poco de animación similar https://i.gyazo.com/04f4b767ea61569c00d3b4a4a86795ce.gif https://i.gyazo.com/57a52b8477a361c383d44664392db0be.gif

Editar:

Para acelerar el preformance RecyclerView, en lugar de adapter.notifyItemRangeChanged(0, mItems.size()); Es probable que desee utilizar algo como:

 LinearLayoutManager layoutManager = (LinearLayoutManager) recyclerView.getLayoutManager(); int firstVisible = layoutManager.findFirstVisibleItemPosition(); int lastVisible = layoutManager.findLastVisibleItemPosition(); int itemsChanged = lastVisible - firstVisible + 1; // + 1 because we start count items from 0 adapter.notifyItemRangeChanged(firstVisible, itemsChanged); 

Ante todo:

  • Esta solución supone que los elementos que todavía están visibles, después de que el conjunto de datos cambiado, también se deslice hacia fuera a la derecha y luego se desliza desde abajo (esto es al menos lo que entendí que está pidiendo)
  • Debido a este requisito no pude encontrar una solución fácil y agradable para este problema (al menos durante la primera iteración). La única manera que encontré fue engañar al adaptador – y luchar contra el marco para hacer algo que no estaba destinado. Esta es la razón por la cual la primera parte (Cómo funciona normalmente) describe cómo lograr buenas animaciones con el RecyclerView forma predeterminada . La segunda parte describe la solución cómo aplicar la diapositiva / diapositiva en la animación para todos los elementos después de cambiar el conjunto de datos.
  • Más tarde encontré una mejor solución que no requiere engañar al adaptador con ids aleatorios (saltar a la parte inferior para la versión actualizada).

Cómo funciona normalmente

Para activar las animaciones, debe informar al RecyclerView cómo ha cambiado el conjunto de datos (para que sepa qué tipo de animaciones se deben ejecutar). Esto se puede hacer de dos formas:

1) Versión simple: Necesitamos establecer adapter.setHasStableIds(true); Y proporcionar los ids de sus artículos a través de public long getItemId(int position) en su Adapter a la RecyclerView . El RecyclerView utiliza estos identificadores para determinar qué elementos se eliminaron / agregaron / movieron durante la llamada a adapter.notifyDataSetChanged();

2) Versión avanzada: En lugar de llamar a adapter.notifyDataSetChanged(); También puede indicar explícitamente cómo ha cambiado el conjunto de datos. El Adapter proporciona varios métodos, como adapter.notifyItemChanged(int position) , adapter.notifyItemInserted(int position) , … para describir los cambios en el conjunto de datos

Las animaciones que se activan para reflejar los cambios en el conjunto de datos son administradas por ItemAnimator . El RecyclerView ya está equipado con un defecto DefaultItemAnimator defecto. Además, es posible definir comportamiento de animación personalizado con un ItemAnimator personalizado.

Estrategia para implementar el deslizamiento hacia afuera (derecha), deslizar en (abajo)

La diapositiva a la derecha es la animación que se debe reproducir si los elementos se eliminan del conjunto de datos. La diapositiva de la animación inferior se debe reproducir para los elementos que se agregaron al conjunto de datos. Como se mencionó al principio supongo que se desea que todos los elementos se deslice hacia la derecha y se deslice desde la parte inferior. Incluso si son visibles antes y después del cambio de conjunto de datos. Normalmente, RecyclerView jugaría para cambiar / mover la animación para aquellos elementos que permanezcan visibles. Sin embargo, debido a que queremos utilizar la animación remove / add para todos los elementos que necesitamos para engañar al adaptador en el pensamiento de que sólo hay elementos nuevos después del cambio y todos los elementos previamente disponibles se eliminaron. Esto se puede lograr proporcionando una identificación aleatoria para cada elemento en el adaptador:

 @Override public long getItemId(int position) { return Math.round(Math.random() * Long.MAX_VALUE); } 

Ahora necesitamos proporcionar un ItemAnimator personalizado que administre las animaciones para los elementos agregados / eliminados. La estructura del SlidingAnimator presentado es muy similar al android.support.v7.widget.DefaultItemAnimator que se proporciona con el RecyclerView . También tenga en cuenta que esto es una prueba de concepto y debe ajustarse antes de usarlo en cualquier aplicación:

 public class SlidingAnimator extends SimpleItemAnimator { List<RecyclerView.ViewHolder> pendingAdditions = new ArrayList<>(); List<RecyclerView.ViewHolder> pendingRemovals = new ArrayList<>(); @Override public void runPendingAnimations() { final List<RecyclerView.ViewHolder> additionsTmp = pendingAdditions; List<RecyclerView.ViewHolder> removalsTmp = pendingRemovals; pendingAdditions = new ArrayList<>(); pendingRemovals = new ArrayList<>(); for (RecyclerView.ViewHolder removal : removalsTmp) { // run the pending remove animation animateRemoveImpl(removal); } removalsTmp.clear(); if (!additionsTmp.isEmpty()) { Runnable adder = new Runnable() { public void run() { for (RecyclerView.ViewHolder addition : additionsTmp) { // run the pending add animation animateAddImpl(addition); } additionsTmp.clear(); } }; // play the add animation after the remove animation finished ViewCompat.postOnAnimationDelayed(additionsTmp.get(0).itemView, adder, getRemoveDuration()); } } @Override public boolean animateAdd(RecyclerView.ViewHolder holder) { pendingAdditions.add(holder); // translate the new items vertically so that they later slide in from the bottom holder.itemView.setTranslationY(300); // also make them invisible holder.itemView.setAlpha(0); // this requests the execution of runPendingAnimations() return true; } @Override public boolean animateRemove(final RecyclerView.ViewHolder holder) { pendingRemovals.add(holder); // this requests the execution of runPendingAnimations() return true; } private void animateAddImpl(final RecyclerView.ViewHolder holder) { View view = holder.itemView; final ViewPropertyAnimatorCompat anim = ViewCompat.animate(view); anim // undo the translation we applied in animateAdd .translationY(0) // undo the alpha we applied in animateAdd .alpha(1) .setDuration(getAddDuration()) .setInterpolator(new DecelerateInterpolator()) .setListener(new ViewPropertyAnimatorListener() { @Override public void onAnimationStart(View view) { dispatchAddStarting(holder); } @Override public void onAnimationEnd(View view) { anim.setListener(null); dispatchAddFinished(holder); // cleanup view.setTranslationY(0); view.setAlpha(1); } @Override public void onAnimationCancel(View view) { } }).start(); } private void animateRemoveImpl(final RecyclerView.ViewHolder holder) { View view = holder.itemView; final ViewPropertyAnimatorCompat anim = ViewCompat.animate(view); anim // translate horizontally to provide slide out to right .translationX(view.getWidth()) // fade out .alpha(0) .setDuration(getRemoveDuration()) .setInterpolator(new AccelerateInterpolator()) .setListener(new ViewPropertyAnimatorListener() { @Override public void onAnimationStart(View view) { dispatchRemoveStarting(holder); } @Override public void onAnimationEnd(View view) { anim.setListener(null); dispatchRemoveFinished(holder); // cleanup view.setTranslationX(0); view.setAlpha(1); } @Override public void onAnimationCancel(View view) { } }).start(); } @Override public boolean animateMove(RecyclerView.ViewHolder holder, int fromX, int fromY, int toX, int toY) { // don't handle animateMove because there should only be add/remove animations dispatchMoveFinished(holder); return false; } @Override public boolean animateChange(RecyclerView.ViewHolder oldHolder, RecyclerView.ViewHolder newHolder, int fromLeft, int fromTop, int toLeft, int toTop) { // don't handle animateChange because there should only be add/remove animations if (newHolder != null) { dispatchChangeFinished(newHolder, false); } dispatchChangeFinished(oldHolder, true); return false; } @Override public void endAnimation(RecyclerView.ViewHolder item) { } @Override public void endAnimations() { } @Override public boolean isRunning() { return false; } } 

Este es el resultado final:

Introduzca aquí la descripción de la imagen

Actualización: Mientras leía el mensaje de nuevo me imaginé una mejor solución

Esta solución actualizada no requiere engañar al adaptador con identificadores aleatorios en el pensamiento de todos los elementos se eliminaron y sólo se agregaron nuevos elementos. Si aplicamos el 2) versión avanzada – cómo notificar al adaptador sobre los cambios del conjunto de datos, podemos apenas decirle al adapter que todos los artículos anteriores fueron quitados y todos los nuevos artículos fueron agregados:

 int oldSize = oldItems.size(); oldItems.clear(); // Notify the adapter all previous items were removed notifyItemRangeRemoved(0, oldSize); oldItems.addAll(items); // Notify the adapter all the new items were added notifyItemRangeInserted(0, items.size()); // don't call notifyDataSetChanged //notifyDataSetChanged(); 

El presentado previamente SlidingAnimator sigue siendo necesario para animar los cambios.

  • Hace notificydatasetchanged llamada onCreateViewHolder al usar RecyclerView
  • Emitir refrescando una vista de una fila en recyclerview
  • Adaptador de Android "java.lang.IndexOutOfBoundsException: Índice no válido 4, tamaño es 4"
  • RecyclerViewAdapter.notifyDataSetChanged () en una referencia de objeto nulo
  • Android: ListFragment refresh utilizando notifyDataSetChanged () con el adaptador personalizado no funciona
  • Android getChildView no se llama después de notifyDataSetChanged
  • ¿Quieres implementar notifyDataSetChanged o autorefresh a mi RecyclerView pero no funciona
  • NotifyDataSetChanged no puede actualizar ListView
  • Android: detecta si un ListView tiene la barra de desplazamiento (después de configurar nuevos datos)
  • Mantener el foco en TextView después de notifyDatasetChanged () fue llamado en listview personalizado?
  • Cómo evitar la renovación de las celdas al llamar a notifyDataSetChanged () para PinterestLikeAdapterView?
  • FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.