Java tutorial
/* * Copyright (C) 2016 Davide Steduto (Special customization for FlexibleAdapter) * Copyright (C) 2015 Wasabeef * Copyright (C) 2014 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package com.flexible.flexibleadapter.common; import android.support.annotation.NonNull; import android.support.v4.animation.AnimatorCompatHelper; import android.support.v4.view.ViewCompat; import android.support.v4.view.ViewPropertyAnimatorCompat; import android.support.v4.view.ViewPropertyAnimatorListener; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.RecyclerView.ViewHolder; import android.support.v7.widget.SimpleItemAnimator; import android.util.Log; import android.view.View; import android.view.animation.Interpolator; import android.view.animation.LinearInterpolator; import com.flexible.flexibleadapter.FlexibleAdapter; import com.flexible.viewholders.AnimatedViewHolder; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; /** * This implementation of {@link RecyclerView.ItemAnimator} provides basic * animations on remove, add, and move events that happen to the items in * a RecyclerView. RecyclerView uses a DefaultItemAnimator by default. * * @see RecyclerView#setItemAnimator(RecyclerView.ItemAnimator) */ public class FlexibleItemAnimator extends SimpleItemAnimator { private final String TAG = this.getClass().getSimpleName(); private ArrayList<ViewHolder> mPendingRemovals = new ArrayList<>(); private ArrayList<ViewHolder> mPendingAdditions = new ArrayList<>(); private ArrayList<MoveInfo> mPendingMoves = new ArrayList<>(); private ArrayList<ChangeInfo> mPendingChanges = new ArrayList<>(); private ArrayList<ArrayList<ViewHolder>> mAdditionsList = new ArrayList<>(); private ArrayList<ArrayList<MoveInfo>> mMovesList = new ArrayList<>(); private ArrayList<ArrayList<ChangeInfo>> mChangesList = new ArrayList<>(); private ArrayList<ViewHolder> mMoveAnimations = new ArrayList<>(); private ArrayList<ViewHolder> mChangeAnimations = new ArrayList<>(); protected ArrayList<ViewHolder> mRemoveAnimations = new ArrayList<>(); protected ArrayList<ViewHolder> mAddAnimations = new ArrayList<>(); protected Interpolator mInterpolator = new LinearInterpolator(); private static class MoveInfo { public ViewHolder holder; public int fromX, fromY, toX, toY; private MoveInfo(ViewHolder holder, int fromX, int fromY, int toX, int toY) { this.holder = holder; this.fromX = fromX; this.fromY = fromY; this.toX = toX; this.toY = toY; } } private static class ChangeInfo { public ViewHolder oldHolder, newHolder; public int fromX, fromY, toX, toY; private ChangeInfo(ViewHolder oldHolder, ViewHolder newHolder) { this.oldHolder = oldHolder; this.newHolder = newHolder; } private ChangeInfo(ViewHolder oldHolder, ViewHolder newHolder, int fromX, int fromY, int toX, int toY) { this(oldHolder, newHolder); this.fromX = fromX; this.fromY = fromY; this.toX = toX; this.toY = toY; } @Override public String toString() { return "ChangeInfo{" + "oldHolder=" + oldHolder + ", newHolder=" + newHolder + ", fromX=" + fromX + ", fromY=" + fromY + ", toX=" + toX + ", toY=" + toY + '}'; } } public FlexibleItemAnimator() { super(); setSupportsChangeAnimations(true); } public void setInterpolator(Interpolator mInterpolator) { this.mInterpolator = mInterpolator; } @Override public final void runPendingAnimations() { boolean removalsPending = !mPendingRemovals.isEmpty(); boolean movesPending = !mPendingMoves.isEmpty(); boolean changesPending = !mPendingChanges.isEmpty(); boolean additionsPending = !mPendingAdditions.isEmpty(); if (!removalsPending && !movesPending && !additionsPending && !changesPending) { // Nothing to animate return; } // First, remove animations runRemoveAnimation(); // Next, move animations runMoveAnimation(removalsPending, movesPending); // Next, change animations runChangeAnimation(removalsPending, changesPending); // Next, add animations runAddAnimation(removalsPending, changesPending, movesPending, additionsPending); } //1st Remove private void runRemoveAnimation() { // Reverse sorting removal animations Collections.sort(mPendingRemovals, new Comparator<ViewHolder>() { @Override public int compare(ViewHolder vh1, ViewHolder vh2) { return (int) (vh2.getItemId() - vh1.getItemId()); } }); Runnable remover = new Runnable() { @Override public void run() { int index = 0; for (ViewHolder holder : mPendingRemovals) { doAnimateRemove(holder, index++); } mPendingRemovals.clear(); } }; remover.run(); } //2nd Move private void runMoveAnimation(boolean removalsPending, boolean movesPending) { if (movesPending) { final ArrayList<MoveInfo> moves = new ArrayList<>(); moves.addAll(mPendingMoves); mMovesList.add(moves); mPendingMoves.clear(); Runnable mover = new Runnable() { @Override public void run() { for (MoveInfo moveInfo : moves) { animateMoveImpl(moveInfo.holder, moveInfo.fromX, moveInfo.fromY, moveInfo.toX, moveInfo.toY); } moves.clear(); mMovesList.remove(moves); } }; if (removalsPending) { View view = moves.get(0).holder.itemView; ViewCompat.postOnAnimationDelayed(view, mover, getRemoveDuration()); } else { mover.run(); } } } //3rd Change private void runChangeAnimation(boolean removalsPending, boolean changesPending) { // Change animation to run in parallel with move animations if (changesPending) { final ArrayList<ChangeInfo> changes = new ArrayList<>(); changes.addAll(mPendingChanges); mChangesList.add(changes); mPendingChanges.clear(); Runnable changer = new Runnable() { @Override public void run() { for (ChangeInfo change : changes) { animateChangeImpl(change); } changes.clear(); mChangesList.remove(changes); } }; if (removalsPending) { ViewHolder holder = changes.get(0).oldHolder; ViewCompat.postOnAnimationDelayed(holder.itemView, changer, getRemoveDuration()); } else { changer.run(); } } } //4th Add private void runAddAnimation(boolean removalsPending, boolean changesPending, boolean movesPending, boolean additionsPending) { if (additionsPending) { final ArrayList<ViewHolder> additions = new ArrayList<>(); // Sorting addition animations based on it's original layout position Collections.sort(mPendingAdditions, new Comparator<ViewHolder>() { @Override public int compare(ViewHolder vh1, ViewHolder vh2) { return vh1.getLayoutPosition() - vh2.getLayoutPosition(); } }); additions.addAll(mPendingAdditions); mAdditionsList.add(additions); mPendingAdditions.clear(); Runnable adder = new Runnable() { public void run() { int index = 0; for (ViewHolder holder : additions) { doAnimateAdd(holder, index++); } additions.clear(); mAdditionsList.remove(additions); } }; if (removalsPending || movesPending || changesPending) { long removeDuration = removalsPending ? getRemoveDuration() : 0; long moveDuration = movesPending ? getMoveDuration() : 0; long changeDuration = changesPending ? getChangeDuration() : 0; long totalDelay = removeDuration + Math.max(moveDuration, changeDuration); View view = additions.get(0).itemView; ViewCompat.postOnAnimationDelayed(view, adder, totalDelay); } else { adder.run(); } } } /* ====== */ /* REMOVE */ /* ====== */ /** * Prepares the View for Remove Animation. * <p>- If {@link AnimatedViewHolder#preAnimateRemoveImpl()} is implemented and returns * {@code true}, then ViewHolder has precedence and the implementation of this method is ignored; * <br/>- If <u>not</u>, the implementation of this method is therefore performed.</p> * Default value is {@code true}. * * @param holder the ViewHolder * @return {@code true} if a later call to {@link #runPendingAnimations()} is requested, * false otherwise. */ protected boolean preAnimateRemoveImpl(final ViewHolder holder) { return true; } /** * Performs the Remove Animation of this ViewHolder. * <p>- If {@link AnimatedViewHolder#animateRemoveImpl(ViewPropertyAnimatorListener, long, int)} is * implemented and returns true, then ViewHolder has precedence and the implementation of this * method is ignored; * <br/>- If <u>not</u>, the implementation of this method is therefore performed.</p> * * @param holder the ViewHolder * @param index the progressive order of execution */ protected void animateRemoveImpl(final ViewHolder holder, int index) { //Free to implement } private boolean preAnimateRemove(final ViewHolder holder) { clear(holder.itemView); boolean consumed = false; if (holder instanceof AnimatedViewHolder) { consumed = ((AnimatedViewHolder) holder).preAnimateRemoveImpl(); } return consumed || preAnimateRemoveImpl(holder); } private void doAnimateRemove(final ViewHolder holder, final int index) { if (FlexibleAdapter.DEBUG) Log.v(TAG, "AnimateRemove on itemId " + holder.getItemId()); boolean consumed = false; if (holder instanceof AnimatedViewHolder) { consumed = ((AnimatedViewHolder) holder).animateRemoveImpl(new DefaultRemoveVpaListener(holder), getRemoveDuration(), index); } if (!consumed) { animateRemoveImpl(holder, index); } mRemoveAnimations.add(holder); } @Override public final boolean animateRemove(final ViewHolder holder) { endAnimation(holder); return preAnimateRemove(holder) && mPendingRemovals.add(holder); } /* === */ /* ADD */ /* === */ /** * Prepares the View for Add Animation. * <p>- If {@link AnimatedViewHolder#preAnimateAddImpl()} is implemented and returns * {@code true}, then ViewHolder has precedence and the implementation of this method is ignored; * <br/>- If <u>not</u>, the implementation of this method is therefore performed.</p> * Default value is {@code true}. * * @param holder the ViewHolder * @return {@code true} if a later call to {@link #runPendingAnimations()} is requested, * false otherwise. */ protected boolean preAnimateAddImpl(final ViewHolder holder) { return true; } /** * Performs the Add Animation of this ViewHolder. * <p>- If {@link AnimatedViewHolder#animateAddImpl(ViewPropertyAnimatorListener, long, int)} is * implemented and returns {@code true}, then ViewHolder has precedence and the implementation * of this method is ignored; * <br/>- If <u>not</u>, the implementation of this method is therefore performed.</p> * * @param holder the ViewHolder * @param index the progressive order of execution */ protected void animateAddImpl(final ViewHolder holder, int index) { //Free to implement } private boolean preAnimateAdd(final ViewHolder holder) { clear(holder.itemView); boolean consumed = false; if (holder instanceof AnimatedViewHolder) { consumed = ((AnimatedViewHolder) holder).preAnimateAddImpl(); } return consumed || preAnimateAddImpl(holder); } private void doAnimateAdd(final ViewHolder holder, final int index) { if (FlexibleAdapter.DEBUG) Log.v(TAG, "AnimateAdd on itemId=" + holder.getItemId() + " position=" + holder.getLayoutPosition()); boolean consumed = false; if (holder instanceof AnimatedViewHolder) { consumed = ((AnimatedViewHolder) holder).animateAddImpl(new DefaultAddVpaListener(holder), getAddDuration(), index); } if (!consumed) { animateAddImpl(holder, index); } mAddAnimations.add(holder); } @Override public final boolean animateAdd(final ViewHolder holder) { endAnimation(holder); return preAnimateAdd(holder) && mPendingAdditions.add(holder); } /* ==== */ /* MOVE */ /* ==== */ @Override public final boolean animateMove(final ViewHolder holder, int fromX, int fromY, int toX, int toY) { final View view = holder.itemView; fromX += ViewCompat.getTranslationX(holder.itemView); fromY += ViewCompat.getTranslationY(holder.itemView); resetAnimation(holder); int deltaX = toX - fromX; int deltaY = toY - fromY; if (deltaX == 0 && deltaY == 0) { dispatchMoveFinished(holder); return false; } if (deltaX != 0) { ViewCompat.setTranslationX(view, -deltaX); } if (deltaY != 0) { ViewCompat.setTranslationY(view, -deltaY); } mPendingMoves.add(new MoveInfo(holder, fromX, fromY, toX, toY)); return true; } private void animateMoveImpl(final ViewHolder holder, int fromX, int fromY, int toX, int toY) { final View view = holder.itemView; final int deltaX = toX - fromX; final int deltaY = toY - fromY; if (deltaX != 0) { ViewCompat.animate(view).translationX(0); } if (deltaY != 0) { ViewCompat.animate(view).translationY(0); } // TODO: make EndActions end listeners instead, since end actions aren't called when // VPAs are canceled (and can't end them. why?) // Need listener functionality in VPACompat for this. Ick. mMoveAnimations.add(holder); final ViewPropertyAnimatorCompat animation = ViewCompat.animate(view); animation.setDuration(getMoveDuration()).setListener(new VpaListenerAdapter() { @Override public void onAnimationStart(View view) { dispatchMoveStarting(holder); } @Override public void onAnimationCancel(View view) { if (deltaX != 0) { ViewCompat.setTranslationX(view, 0); } if (deltaY != 0) { ViewCompat.setTranslationY(view, 0); } } @Override public void onAnimationEnd(View view) { animation.setListener(null); dispatchMoveFinished(holder); mMoveAnimations.remove(holder); dispatchFinishedWhenDone(); } }).start(); } /* ====== */ /* CHANGE */ /* ====== */ @Override public boolean animateChange(ViewHolder oldHolder, ViewHolder newHolder, int fromX, int fromY, int toX, int toY) { if (oldHolder == newHolder) { // Don't know how to run change animations when the same view holder is re-used. // run a move animation to handle position changes. return animateMove(oldHolder, fromX, fromY, toX, toY); } final float prevTranslationX = ViewCompat.getTranslationX(oldHolder.itemView); final float prevTranslationY = ViewCompat.getTranslationY(oldHolder.itemView); final float prevAlpha = ViewCompat.getAlpha(oldHolder.itemView); resetAnimation(oldHolder); int deltaX = (int) (toX - fromX - prevTranslationX); int deltaY = (int) (toY - fromY - prevTranslationY); // Recover prev translation state after ending animation ViewCompat.setTranslationX(oldHolder.itemView, prevTranslationX); ViewCompat.setTranslationY(oldHolder.itemView, prevTranslationY); ViewCompat.setAlpha(oldHolder.itemView, prevAlpha); if (newHolder != null) { // Carry over translation values resetAnimation(newHolder); ViewCompat.setTranslationX(newHolder.itemView, -deltaX); ViewCompat.setTranslationY(newHolder.itemView, -deltaY); ViewCompat.setAlpha(newHolder.itemView, 0); } mPendingChanges.add(new ChangeInfo(oldHolder, newHolder, fromX, fromY, toX, toY)); return true; } private void animateChangeImpl(final ChangeInfo changeInfo) { final ViewHolder holder = changeInfo.oldHolder; final View view = holder == null ? null : holder.itemView; final ViewHolder newHolder = changeInfo.newHolder; final View newView = newHolder != null ? newHolder.itemView : null; if (view != null) { final ViewPropertyAnimatorCompat oldViewAnim = ViewCompat.animate(view) .setDuration(getChangeDuration()); mChangeAnimations.add(changeInfo.oldHolder); oldViewAnim.translationX(changeInfo.toX - changeInfo.fromX); oldViewAnim.translationY(changeInfo.toY - changeInfo.fromY); oldViewAnim.alpha(0).setListener(new VpaListenerAdapter() { @Override public void onAnimationStart(View view) { dispatchChangeStarting(changeInfo.oldHolder, true); } @Override public void onAnimationEnd(View view) { oldViewAnim.setListener(null); ViewCompat.setAlpha(view, 1); ViewCompat.setTranslationX(view, 0); ViewCompat.setTranslationY(view, 0); dispatchChangeFinished(changeInfo.oldHolder, true); mChangeAnimations.remove(changeInfo.oldHolder); dispatchFinishedWhenDone(); } }).start(); } if (newView != null) { final ViewPropertyAnimatorCompat newViewAnimation = ViewCompat.animate(newView); mChangeAnimations.add(changeInfo.newHolder); newViewAnimation.translationX(0).translationY(0).setDuration(getChangeDuration()).alpha(1) .setListener(new VpaListenerAdapter() { @Override public void onAnimationStart(View view) { dispatchChangeStarting(changeInfo.newHolder, false); } @Override public void onAnimationEnd(View view) { newViewAnimation.setListener(null); ViewCompat.setAlpha(newView, 1); ViewCompat.setTranslationX(newView, 0); ViewCompat.setTranslationY(newView, 0); dispatchChangeFinished(changeInfo.newHolder, false); mChangeAnimations.remove(changeInfo.newHolder); dispatchFinishedWhenDone(); } }).start(); } } private void endChangeAnimation(List<ChangeInfo> infoList, ViewHolder item) { for (int i = infoList.size() - 1; i >= 0; i--) { ChangeInfo changeInfo = infoList.get(i); if (endChangeAnimationIfNecessary(changeInfo, item)) { if (changeInfo.oldHolder == null && changeInfo.newHolder == null) { infoList.remove(changeInfo); } } } } private void endChangeAnimationIfNecessary(ChangeInfo changeInfo) { if (changeInfo.oldHolder != null) { endChangeAnimationIfNecessary(changeInfo, changeInfo.oldHolder); } if (changeInfo.newHolder != null) { endChangeAnimationIfNecessary(changeInfo, changeInfo.newHolder); } } private boolean endChangeAnimationIfNecessary(ChangeInfo changeInfo, ViewHolder item) { boolean oldItem = false; if (changeInfo.newHolder == item) { changeInfo.newHolder = null; } else if (changeInfo.oldHolder == item) { changeInfo.oldHolder = null; oldItem = true; } else { return false; } ViewCompat.setAlpha(item.itemView, 1); ViewCompat.setTranslationX(item.itemView, 0); ViewCompat.setTranslationY(item.itemView, 0); dispatchChangeFinished(item, oldItem); return true; } @Override public void endAnimation(ViewHolder item) { final View view = item.itemView; // This will trigger end callback which should set properties to their target values. ViewCompat.animate(view).cancel(); // TODO: if some other animations are chained to end, how do we cancel them as well? for (int i = mPendingMoves.size() - 1; i >= 0; i--) { MoveInfo moveInfo = mPendingMoves.get(i); if (moveInfo.holder == item) { ViewCompat.setTranslationY(view, 0); ViewCompat.setTranslationX(view, 0); dispatchMoveFinished(item); mPendingMoves.remove(i); } } endChangeAnimation(mPendingChanges, item); if (mPendingRemovals.remove(item)) { clear(item.itemView); dispatchRemoveFinished(item); } if (mPendingAdditions.remove(item)) { clear(item.itemView); dispatchAddFinished(item); } for (int i = mChangesList.size() - 1; i >= 0; i--) { ArrayList<ChangeInfo> changes = mChangesList.get(i); endChangeAnimation(changes, item); if (changes.isEmpty()) { mChangesList.remove(i); } } for (int i = mMovesList.size() - 1; i >= 0; i--) { ArrayList<MoveInfo> moves = mMovesList.get(i); for (int j = moves.size() - 1; j >= 0; j--) { MoveInfo moveInfo = moves.get(j); if (moveInfo.holder == item) { ViewCompat.setTranslationY(view, 0); ViewCompat.setTranslationX(view, 0); dispatchMoveFinished(item); moves.remove(j); if (moves.isEmpty()) { mMovesList.remove(i); } break; } } } for (int i = mAdditionsList.size() - 1; i >= 0; i--) { ArrayList<ViewHolder> additions = mAdditionsList.get(i); if (additions.remove(item)) { clear(item.itemView); dispatchAddFinished(item); if (additions.isEmpty()) { mAdditionsList.remove(i); } } } // Animations should be ended by the cancel above. // Used during DEBUGGING; Commented in final version. // if (mRemoveAnimations.remove(item)) { // throw new IllegalStateException( // "After animation is cancelled, item should not be in mRemoveAnimations list"); // } // if (mAddAnimations.remove(item)) { // throw new IllegalStateException( // "After animation is cancelled, item should not be in mAddAnimations list"); // } // if (mChangeAnimations.remove(item)) { // throw new IllegalStateException( // "After animation is cancelled, item should not be in mChangeAnimations list"); // } // if (mMoveAnimations.remove(item)) { // throw new IllegalStateException( // "After animation is cancelled, item should not be in mMoveAnimations list"); // } dispatchFinishedWhenDone(); } private void resetAnimation(ViewHolder holder) { AnimatorCompatHelper.clearInterpolator(holder.itemView); endAnimation(holder); } @Override public boolean isRunning() { return (!mPendingAdditions.isEmpty() || !mPendingChanges.isEmpty() || !mPendingMoves.isEmpty() || !mPendingRemovals.isEmpty() || !mMoveAnimations.isEmpty() || !mRemoveAnimations.isEmpty() || !mAddAnimations.isEmpty() || !mChangeAnimations.isEmpty() || !mMovesList.isEmpty() || !mAdditionsList.isEmpty() || !mChangesList.isEmpty()); } /** * Check the state of currently pending and running animations. If there are none * pending/running, call {@link #dispatchAnimationsFinished()} to notify any * listeners. */ private void dispatchFinishedWhenDone() { if (!isRunning()) { dispatchAnimationsFinished(); } } @Override public void endAnimations() { int count = mPendingMoves.size(); for (int i = count - 1; i >= 0; i--) { MoveInfo item = mPendingMoves.get(i); View view = item.holder.itemView; ViewCompat.setTranslationY(view, 0); ViewCompat.setTranslationX(view, 0); dispatchMoveFinished(item.holder); mPendingMoves.remove(i); } count = mPendingRemovals.size(); for (int i = count - 1; i >= 0; i--) { ViewHolder item = mPendingRemovals.get(i); dispatchRemoveFinished(item); mPendingRemovals.remove(i); } count = mPendingAdditions.size(); for (int i = count - 1; i >= 0; i--) { ViewHolder item = mPendingAdditions.get(i); clear(item.itemView); dispatchAddFinished(item); mPendingAdditions.remove(i); } count = mPendingChanges.size(); for (int i = count - 1; i >= 0; i--) { endChangeAnimationIfNecessary(mPendingChanges.get(i)); } mPendingChanges.clear(); if (!isRunning()) { return; } int listCount = mMovesList.size(); for (int i = listCount - 1; i >= 0; i--) { ArrayList<MoveInfo> moves = mMovesList.get(i); count = moves.size(); for (int j = count - 1; j >= 0; j--) { MoveInfo moveInfo = moves.get(j); ViewHolder item = moveInfo.holder; View view = item.itemView; ViewCompat.setTranslationY(view, 0); ViewCompat.setTranslationX(view, 0); dispatchMoveFinished(moveInfo.holder); moves.remove(j); if (moves.isEmpty()) { mMovesList.remove(moves); } } } listCount = mAdditionsList.size(); for (int i = listCount - 1; i >= 0; i--) { ArrayList<ViewHolder> additions = mAdditionsList.get(i); count = additions.size(); for (int j = count - 1; j >= 0; j--) { ViewHolder item = additions.get(j); View view = item.itemView; ViewCompat.setAlpha(view, 1); dispatchAddFinished(item); // Prevent exception when removal already occurred during finishing animation if (j < additions.size()) { additions.remove(j); } if (additions.isEmpty()) { mAdditionsList.remove(additions); } } } listCount = mChangesList.size(); for (int i = listCount - 1; i >= 0; i--) { ArrayList<ChangeInfo> changes = mChangesList.get(i); count = changes.size(); for (int j = count - 1; j >= 0; j--) { endChangeAnimationIfNecessary(changes.get(j)); if (changes.isEmpty()) { mChangesList.remove(changes); } } } cancelAll(mRemoveAnimations); cancelAll(mMoveAnimations); cancelAll(mAddAnimations); cancelAll(mChangeAnimations); dispatchAnimationsFinished(); } void cancelAll(List<ViewHolder> viewHolders) { for (int i = viewHolders.size() - 1; i >= 0; i--) { ViewCompat.animate(viewHolders.get(i).itemView).cancel(); } } private static void clear(View v) { ViewCompat.setAlpha(v, 1); ViewCompat.setScaleY(v, 1); ViewCompat.setScaleX(v, 1); ViewCompat.setTranslationY(v, 0); ViewCompat.setTranslationX(v, 0); ViewCompat.setRotation(v, 0); ViewCompat.setRotationY(v, 0); ViewCompat.setRotationX(v, 0); ViewCompat.setPivotY(v, v.getMeasuredHeight() / 2); ViewCompat.setPivotX(v, v.getMeasuredWidth() / 2); ViewCompat.animate(v).setInterpolator(null).setStartDelay(0); } /** * {@inheritDoc} * <p/> * If the payload list is not empty, DefaultItemAnimator returns <code>true</code>. * When this is the case: * <ul> * <li>If you override {@code animateChange()}, both ViewHolder arguments will be the same * instance.</li> * <li>If you are not overriding {@code animateChange()}, then DefaultItemAnimator will call * {@code animateMove()} and run a move animation instead.</li> * </ul> */ @Override public boolean canReuseUpdatedViewHolder(@NonNull ViewHolder viewHolder, @NonNull List<Object> payloads) { return !payloads.isEmpty() || super.canReuseUpdatedViewHolder(viewHolder, payloads); } private static class VpaListenerAdapter implements ViewPropertyAnimatorListener { @Override public void onAnimationStart(View view) { } @Override public void onAnimationEnd(View view) { } @Override public void onAnimationCancel(View view) { } } protected class DefaultAddVpaListener extends VpaListenerAdapter { ViewHolder mViewHolder; public DefaultAddVpaListener(final ViewHolder holder) { mViewHolder = holder; } @Override public void onAnimationStart(View view) { dispatchAddStarting(mViewHolder); } @Override public void onAnimationCancel(View view) { clear(view); } @Override public void onAnimationEnd(View view) { clear(view); dispatchAddFinished(mViewHolder); mAddAnimations.remove(mViewHolder); dispatchFinishedWhenDone(); } } protected class DefaultRemoveVpaListener extends VpaListenerAdapter { ViewHolder mViewHolder; public DefaultRemoveVpaListener(final ViewHolder holder) { mViewHolder = holder; } @Override public void onAnimationStart(View view) { dispatchRemoveStarting(mViewHolder); } @Override public void onAnimationCancel(View view) { clear(view); } @Override public void onAnimationEnd(View view) { clear(view); dispatchRemoveFinished(mViewHolder); mRemoveAnimations.remove(mViewHolder); dispatchFinishedWhenDone(); } } }