Anthony Restaino
8 years ago
4 changed files with 1335 additions and 13 deletions
@ -0,0 +1,654 @@ |
|||||||
|
/* |
||||||
|
* 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 acr.browser.lightning.fragment.anim; |
||||||
|
|
||||||
|
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.view.View; |
||||||
|
|
||||||
|
import java.util.ArrayList; |
||||||
|
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 HorizontalItemAnimator by default. |
||||||
|
* |
||||||
|
* @see RecyclerView#setItemAnimator(RecyclerView.ItemAnimator) |
||||||
|
*/ |
||||||
|
public class HorizontalItemAnimator extends SimpleItemAnimator { |
||||||
|
private static final boolean DEBUG = false; |
||||||
|
|
||||||
|
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> mAddAnimations = new ArrayList<>(); |
||||||
|
private ArrayList<ViewHolder> mMoveAnimations = new ArrayList<>(); |
||||||
|
private ArrayList<ViewHolder> mRemoveAnimations = new ArrayList<>(); |
||||||
|
private ArrayList<ViewHolder> mChangeAnimations = new ArrayList<>(); |
||||||
|
|
||||||
|
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 + |
||||||
|
'}'; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public 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 stuff
|
||||||
|
for (ViewHolder holder : mPendingRemovals) { |
||||||
|
animateRemoveImpl(holder); |
||||||
|
} |
||||||
|
mPendingRemovals.clear(); |
||||||
|
// Next, move stuff
|
||||||
|
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(); |
||||||
|
} |
||||||
|
} |
||||||
|
// Next, change stuff, 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(); |
||||||
|
} |
||||||
|
} |
||||||
|
// Next, add stuff
|
||||||
|
if (additionsPending) { |
||||||
|
final ArrayList<ViewHolder> additions = new ArrayList<>(); |
||||||
|
additions.addAll(mPendingAdditions); |
||||||
|
mAdditionsList.add(additions); |
||||||
|
mPendingAdditions.clear(); |
||||||
|
Runnable adder = new Runnable() { |
||||||
|
public void run() { |
||||||
|
for (ViewHolder holder : additions) { |
||||||
|
animateAddImpl(holder); |
||||||
|
} |
||||||
|
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(); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public boolean animateRemove(final ViewHolder holder) { |
||||||
|
resetAnimation(holder); |
||||||
|
mPendingRemovals.add(holder); |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
private void animateRemoveImpl(final ViewHolder holder) { |
||||||
|
final View view = holder.itemView; |
||||||
|
final ViewPropertyAnimatorCompat animation = ViewCompat.animate(view); |
||||||
|
mRemoveAnimations.add(holder); |
||||||
|
animation.setDuration(getRemoveDuration()) |
||||||
|
.alpha(0).translationY(holder.itemView.getHeight()).setListener(new VpaListenerAdapter() { |
||||||
|
@Override |
||||||
|
public void onAnimationStart(View view) { |
||||||
|
dispatchRemoveStarting(holder); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onAnimationEnd(View view) { |
||||||
|
animation.setListener(null); |
||||||
|
ViewCompat.setAlpha(view, 1); |
||||||
|
dispatchRemoveFinished(holder); |
||||||
|
mRemoveAnimations.remove(holder); |
||||||
|
dispatchFinishedWhenDone(); |
||||||
|
} |
||||||
|
}).start(); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public boolean animateAdd(final ViewHolder holder) { |
||||||
|
resetAnimation(holder); |
||||||
|
ViewCompat.setAlpha(holder.itemView, 0); |
||||||
|
ViewCompat.setTranslationY(holder.itemView, holder.itemView.getHeight()); |
||||||
|
mPendingAdditions.add(holder); |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
private void animateAddImpl(final ViewHolder holder) { |
||||||
|
final View view = holder.itemView; |
||||||
|
final ViewPropertyAnimatorCompat animation = ViewCompat.animate(view); |
||||||
|
mAddAnimations.add(holder); |
||||||
|
animation.alpha(1).translationY(0).setDuration(getAddDuration()). |
||||||
|
setListener(new VpaListenerAdapter() { |
||||||
|
@Override |
||||||
|
public void onAnimationStart(View view) { |
||||||
|
dispatchAddStarting(holder); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onAnimationCancel(View view) { |
||||||
|
ViewCompat.setAlpha(view, 1); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onAnimationEnd(View view) { |
||||||
|
animation.setListener(null); |
||||||
|
dispatchAddFinished(holder); |
||||||
|
mAddAnimations.remove(holder); |
||||||
|
dispatchFinishedWhenDone(); |
||||||
|
} |
||||||
|
}).start(); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public 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.
|
||||||
|
final ViewPropertyAnimatorCompat animation = ViewCompat.animate(view); |
||||||
|
mMoveAnimations.add(holder); |
||||||
|
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(); |
||||||
|
} |
||||||
|
|
||||||
|
@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)) { |
||||||
|
ViewCompat.setAlpha(view, 1); |
||||||
|
dispatchRemoveFinished(item); |
||||||
|
} |
||||||
|
if (mPendingAdditions.remove(item)) { |
||||||
|
ViewCompat.setAlpha(view, 1); |
||||||
|
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)) { |
||||||
|
ViewCompat.setAlpha(view, 1); |
||||||
|
dispatchAddFinished(item); |
||||||
|
if (additions.isEmpty()) { |
||||||
|
mAdditionsList.remove(i); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// animations should be ended by the cancel above.
|
||||||
|
//noinspection PointlessBooleanExpression,ConstantConditions
|
||||||
|
if (mRemoveAnimations.remove(item) && DEBUG) { |
||||||
|
throw new IllegalStateException("after animation is cancelled, item should not be in " |
||||||
|
+ "mRemoveAnimations list"); |
||||||
|
} |
||||||
|
|
||||||
|
//noinspection PointlessBooleanExpression,ConstantConditions
|
||||||
|
if (mAddAnimations.remove(item) && DEBUG) { |
||||||
|
throw new IllegalStateException("after animation is cancelled, item should not be in " |
||||||
|
+ "mAddAnimations list"); |
||||||
|
} |
||||||
|
|
||||||
|
//noinspection PointlessBooleanExpression,ConstantConditions
|
||||||
|
if (mChangeAnimations.remove(item) && DEBUG) { |
||||||
|
throw new IllegalStateException("after animation is cancelled, item should not be in " |
||||||
|
+ "mChangeAnimations list"); |
||||||
|
} |
||||||
|
|
||||||
|
//noinspection PointlessBooleanExpression,ConstantConditions
|
||||||
|
if (mMoveAnimations.remove(item) && DEBUG) { |
||||||
|
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); |
||||||
|
View view = item.itemView; |
||||||
|
ViewCompat.setAlpha(view, 1); |
||||||
|
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); |
||||||
|
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 class VpaListenerAdapter implements ViewPropertyAnimatorListener { |
||||||
|
@Override |
||||||
|
public void onAnimationStart(View view) {} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onAnimationEnd(View view) {} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onAnimationCancel(View view) {} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,654 @@ |
|||||||
|
/* |
||||||
|
* 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 acr.browser.lightning.fragment.anim; |
||||||
|
|
||||||
|
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.view.View; |
||||||
|
|
||||||
|
import java.util.ArrayList; |
||||||
|
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 VerticalItemAnimator by default. |
||||||
|
* |
||||||
|
* @see RecyclerView#setItemAnimator(RecyclerView.ItemAnimator) |
||||||
|
*/ |
||||||
|
public class VerticalItemAnimator extends SimpleItemAnimator { |
||||||
|
private static final boolean DEBUG = false; |
||||||
|
|
||||||
|
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> mAddAnimations = new ArrayList<>(); |
||||||
|
private ArrayList<ViewHolder> mMoveAnimations = new ArrayList<>(); |
||||||
|
private ArrayList<ViewHolder> mRemoveAnimations = new ArrayList<>(); |
||||||
|
private ArrayList<ViewHolder> mChangeAnimations = new ArrayList<>(); |
||||||
|
|
||||||
|
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 + |
||||||
|
'}'; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public 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 stuff
|
||||||
|
for (ViewHolder holder : mPendingRemovals) { |
||||||
|
animateRemoveImpl(holder); |
||||||
|
} |
||||||
|
mPendingRemovals.clear(); |
||||||
|
// Next, move stuff
|
||||||
|
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(); |
||||||
|
} |
||||||
|
} |
||||||
|
// Next, change stuff, 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(); |
||||||
|
} |
||||||
|
} |
||||||
|
// Next, add stuff
|
||||||
|
if (additionsPending) { |
||||||
|
final ArrayList<ViewHolder> additions = new ArrayList<>(); |
||||||
|
additions.addAll(mPendingAdditions); |
||||||
|
mAdditionsList.add(additions); |
||||||
|
mPendingAdditions.clear(); |
||||||
|
Runnable adder = new Runnable() { |
||||||
|
public void run() { |
||||||
|
for (ViewHolder holder : additions) { |
||||||
|
animateAddImpl(holder); |
||||||
|
} |
||||||
|
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(); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public boolean animateRemove(final ViewHolder holder) { |
||||||
|
resetAnimation(holder); |
||||||
|
mPendingRemovals.add(holder); |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
private void animateRemoveImpl(final ViewHolder holder) { |
||||||
|
final View view = holder.itemView; |
||||||
|
final ViewPropertyAnimatorCompat animation = ViewCompat.animate(view); |
||||||
|
mRemoveAnimations.add(holder); |
||||||
|
animation.setDuration(getRemoveDuration()) |
||||||
|
.alpha(0).translationX(-holder.itemView.getWidth() / 2).setListener(new VpaListenerAdapter() { |
||||||
|
@Override |
||||||
|
public void onAnimationStart(View view) { |
||||||
|
dispatchRemoveStarting(holder); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onAnimationEnd(View view) { |
||||||
|
animation.setListener(null); |
||||||
|
ViewCompat.setAlpha(view, 1); |
||||||
|
dispatchRemoveFinished(holder); |
||||||
|
mRemoveAnimations.remove(holder); |
||||||
|
dispatchFinishedWhenDone(); |
||||||
|
} |
||||||
|
}).start(); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public boolean animateAdd(final ViewHolder holder) { |
||||||
|
resetAnimation(holder); |
||||||
|
ViewCompat.setAlpha(holder.itemView, 0); |
||||||
|
ViewCompat.setTranslationX(holder.itemView, -holder.itemView.getWidth() / 2); |
||||||
|
mPendingAdditions.add(holder); |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
private void animateAddImpl(final ViewHolder holder) { |
||||||
|
final View view = holder.itemView; |
||||||
|
final ViewPropertyAnimatorCompat animation = ViewCompat.animate(view); |
||||||
|
mAddAnimations.add(holder); |
||||||
|
animation.alpha(1).translationX(0).setDuration(getAddDuration()). |
||||||
|
setListener(new VpaListenerAdapter() { |
||||||
|
@Override |
||||||
|
public void onAnimationStart(View view) { |
||||||
|
dispatchAddStarting(holder); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onAnimationCancel(View view) { |
||||||
|
ViewCompat.setAlpha(view, 1); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onAnimationEnd(View view) { |
||||||
|
animation.setListener(null); |
||||||
|
dispatchAddFinished(holder); |
||||||
|
mAddAnimations.remove(holder); |
||||||
|
dispatchFinishedWhenDone(); |
||||||
|
} |
||||||
|
}).start(); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public 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.
|
||||||
|
final ViewPropertyAnimatorCompat animation = ViewCompat.animate(view); |
||||||
|
mMoveAnimations.add(holder); |
||||||
|
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(); |
||||||
|
} |
||||||
|
|
||||||
|
@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)) { |
||||||
|
ViewCompat.setAlpha(view, 1); |
||||||
|
dispatchRemoveFinished(item); |
||||||
|
} |
||||||
|
if (mPendingAdditions.remove(item)) { |
||||||
|
ViewCompat.setAlpha(view, 1); |
||||||
|
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)) { |
||||||
|
ViewCompat.setAlpha(view, 1); |
||||||
|
dispatchAddFinished(item); |
||||||
|
if (additions.isEmpty()) { |
||||||
|
mAdditionsList.remove(i); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// animations should be ended by the cancel above.
|
||||||
|
//noinspection PointlessBooleanExpression,ConstantConditions
|
||||||
|
if (mRemoveAnimations.remove(item) && DEBUG) { |
||||||
|
throw new IllegalStateException("after animation is cancelled, item should not be in " |
||||||
|
+ "mRemoveAnimations list"); |
||||||
|
} |
||||||
|
|
||||||
|
//noinspection PointlessBooleanExpression,ConstantConditions
|
||||||
|
if (mAddAnimations.remove(item) && DEBUG) { |
||||||
|
throw new IllegalStateException("after animation is cancelled, item should not be in " |
||||||
|
+ "mAddAnimations list"); |
||||||
|
} |
||||||
|
|
||||||
|
//noinspection PointlessBooleanExpression,ConstantConditions
|
||||||
|
if (mChangeAnimations.remove(item) && DEBUG) { |
||||||
|
throw new IllegalStateException("after animation is cancelled, item should not be in " |
||||||
|
+ "mChangeAnimations list"); |
||||||
|
} |
||||||
|
|
||||||
|
//noinspection PointlessBooleanExpression,ConstantConditions
|
||||||
|
if (mMoveAnimations.remove(item) && DEBUG) { |
||||||
|
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); |
||||||
|
View view = item.itemView; |
||||||
|
ViewCompat.setAlpha(view, 1); |
||||||
|
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); |
||||||
|
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 class VpaListenerAdapter implements ViewPropertyAnimatorListener { |
||||||
|
@Override |
||||||
|
public void onAnimationStart(View view) {} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onAnimationEnd(View view) {} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onAnimationCancel(View view) {} |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue