studio recyclerview que personalizado imágenes horizontal ejemplo dependencia con codigo cardview card and android android-animation android-recyclerview notifydatasetchanged

android - que - Cómo proporcionar una animación personalizada durante la clasificación(notifyDataSetChanged) en RecyclerView



recyclerview cardview horizontal (2)

Ante todo:

  • Esta solución supone que los elementos que aún están visibles, después de que se cambió el conjunto de datos, también se deslizan hacia la derecha y luego vuelven a deslizarse desde la parte inferior nuevamente (al menos eso es 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 forma que encontré fue engañar al adaptador y luchar contra el marco para hacer algo para lo que no estaba destinado. Esta es la razón por la que la primera parte (Cómo funciona normalmente) describe cómo lograr animaciones agradables con RecyclerView manera predeterminada . La segunda parte describe la solución sobre cómo imponer la salida / diapositiva en animación para todos los elementos después de que el conjunto de datos haya cambiado.
  • Más adelante, encontré una mejor solución que no requiere engañar al adaptador con identificadores aleatorios (saltar a la parte inferior de la versión actualizada).

Como funciona normalmente

Para habilitar animaciones, debe decirle al RecyclerView cómo cambió el conjunto de datos (para que sepa qué tipo de animaciones deben ejecutarse). Esto se puede hacer de dos formas:

1) Versión simple: Necesitamos configurar adapter.setHasStableIds(true); y proporcionar los identificadores de sus artículos a través de public long getItemId(int position) en su Adapter al RecyclerView . RecyclerView utiliza estos identificadores para averiguar 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 cambió 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 el ItemAnimator . RecyclerView ya está equipado con un buen valor predeterminado DefaultItemAnimator . Además, es posible definir un comportamiento de animación personalizado con un ItemAnimator personalizado.

Estrategia para implementar el deslizar hacia fuera (derecha), deslizar hacia adentro (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 desde la animación inferior debe reproducirse para los elementos que se agregaron al conjunto de datos. Como mencioné al principio, asumo que es deseable que todos los elementos se deslicen hacia la derecha y se deslicen 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 animaciones para los elementos que permanecen visibles. Sin embargo, debido a que queremos utilizar la animación de quitar / agregar para todos los elementos, necesitamos engañar al adaptador para que piense que solo hay elementos nuevos después del cambio y que se eliminaron todos los elementos previamente disponibles. 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 debemos 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 RecyclerView . También tenga en cuenta que esto es una prueba de concepto y debe ajustarse antes de utilizarlo 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:

Actualización: Mientras leía la publicación otra vez, descubrí una mejor solución.

Esta solución actualizada no requiere engañar al adaptador con identificadores aleatorios para que piense que todos los elementos se eliminaron y solo se agregaron elementos nuevos. Si aplicamos la 2) Versión avanzada : cómo notificar al adaptador acerca de los cambios en el conjunto de datos, podemos decirle al adapter que se eliminaron todos los elementos anteriores y se agregaron todos los nuevos elementos:

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 SlidingAnimator presentado anteriormente todavía es necesario para animar los cambios.

Actualmente, al utilizar el animador predeterminado android.support.v7.widget.DefaultItemAnimator , este es el resultado que tengo 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 una animación personalizada de la siguiente manera. El elemento antiguo se deslizará hacia el lado derecho y el nuevo elemento se deslizará hacia arriba.

Video de animación esperado: https://youtu.be/9aQTyM7K4B0

Cómo logro tal animación sin RecylerView

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

Así es como se configura 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 activa 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?


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

Use algún tipo de RecyclerView.ItemAnimator , pero en lugar de reescribir 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 trigerina. 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 puede usar android.support.v7.widget.DefaultItemAnimator , pero esta clase tiene animateChangeImpl() privado animateChangeImpl() por lo que tendrá que copiar y pegar el código y cambiar este método o usar la reflexión. O puede crear su propia clase de @Andreas Wenger como @Andreas Wenger hizo en su ejemplo de SlidingAnimator . El punto aquí es implementar animateChangeImpl Simmilar a tu código, hay 2 animaciones:

1) Deslice la vista antigua hacia 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 la 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 desplazamiento de trabajo y una animación similar https://i.gyazo.com/04f4b767ea61569c00d3b4a4a86795ce.gif https://i.gyazo.com/57a52b8477a361c383d44664392db0be.gif

Editar:

Para acelerar el adapter.notifyItemRangeChanged(0, mItems.size()); de RecyclerView, en lugar de adapter.notifyItemRangeChanged(0, mItems.size()); probablemente querrás usar 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);