Fix reverse with start delay

Test: cts in the same topic branch

Change-Id: Ie8793f25d39e0104c880f1a873f68650b918bffa
This commit is contained in:
Doris Liu
2017-02-08 14:47:08 -08:00
parent 47c4c94100
commit 6d45209205
2 changed files with 150 additions and 64 deletions

View File

@@ -142,11 +142,17 @@ public final class AnimatorSet extends Animator implements AnimationHandler.Anim
// the animation was previously seeked and therefore doesn't start from the beginning).
private final boolean mShouldResetValuesAtStart;
// In pre-O releases, end() may never explicitly called on a child animator. As a result, end()
// may not even be properly implemented in a lot of cases. After a few apps crashing on this,
// it became necessary to use an sdk target guard for calling end().
private final boolean mEndCanBeCalled;
// The time, in milliseconds, when last frame of the animation came in. -1 when the animation is
// not running.
private long mLastFrameTime = -1;
// The time, in milliseconds, when the first frame of the animation came in.
// The time, in milliseconds, when the first frame of the animation came in. This is the
// frame before we start counting down the start delay, if any.
// -1 when the animation is not running.
private long mFirstFrame = -1;
@@ -191,11 +197,12 @@ public final class AnimatorSet extends Animator implements AnimationHandler.Anim
super();
mNodeMap.put(mDelayAnim, mRootNode);
mNodes.add(mRootNode);
boolean isPreO;
// Set the flag to ignore calling end() without start() for pre-N releases
Application app = ActivityThread.currentApplication();
if (app == null || app.getApplicationInfo() == null) {
mShouldIgnoreEndWithoutStart = true;
mShouldResetValuesAtStart = false;
isPreO = true;
} else {
if (app.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.N) {
mShouldIgnoreEndWithoutStart = true;
@@ -203,12 +210,10 @@ public final class AnimatorSet extends Animator implements AnimationHandler.Anim
mShouldIgnoreEndWithoutStart = false;
}
if (app.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.O) {
mShouldResetValuesAtStart = false;
} else {
mShouldResetValuesAtStart = true;
}
isPreO = app.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.O;
}
mShouldResetValuesAtStart = !isPreO;
mEndCanBeCalled = !isPreO;
}
/**
@@ -424,6 +429,35 @@ public final class AnimatorSet extends Animator implements AnimationHandler.Anim
}
}
// Force all the animations to end when the duration scale is 0.
private void forceToEnd() {
if (mEndCanBeCalled) {
end();
} else {
// Note: we don't want to combine this case with the end() method below because in
// the case of developer calling end(), we still need to make sure end() is explicitly
// called on the child animators to maintain the old behavior.
if (mReversing) {
mLastEventId = mLastEventId == -1 ? mEvents.size() : mLastEventId;
for (int j = mLastEventId - 1; j >= 0; j--) {
AnimationEvent event = mEvents.get(j);
if (event.mEvent == AnimationEvent.ANIMATION_END) {
event.mNode.mAnimation.reverse();
}
}
} else {
for (int j = mLastEventId + 1; j < mEvents.size(); j++) {
AnimationEvent event = mEvents.get(j);
if (event.mEvent == AnimationEvent.ANIMATION_START) {
event.mNode.mAnimation.start();
}
}
}
mPlayingSet.clear();
endAnimation();
}
}
/**
* {@inheritDoc}
*
@@ -445,19 +479,28 @@ public final class AnimatorSet extends Animator implements AnimationHandler.Anim
mLastEventId = mLastEventId == -1 ? mEvents.size() : mLastEventId;
for (int j = mLastEventId - 1; j >= 0; j--) {
AnimationEvent event = mEvents.get(j);
Animator anim = event.mNode.mAnimation;
if (event.mEvent == AnimationEvent.ANIMATION_END) {
event.mNode.mAnimation.reverse();
} else if (event.mEvent == AnimationEvent.ANIMATION_DELAY_ENDED) {
event.mNode.mAnimation.end();
anim.reverse();
} else if (event.mEvent == AnimationEvent.ANIMATION_DELAY_ENDED
&& anim.isStarted()) {
// Make sure anim hasn't finished before calling end() so that we don't end
// already ended animations, which will cause start and end callbacks to be
// triggered again.
anim.end();
}
}
} else {
for (int j = mLastEventId + 1; j < mEvents.size(); j++) {
AnimationEvent event = mEvents.get(j);
Animator anim = event.mNode.mAnimation;
if (event.mEvent == AnimationEvent.ANIMATION_START) {
event.mNode.mAnimation.start();
} else if (event.mEvent == AnimationEvent.ANIMATION_END) {
event.mNode.mAnimation.end();
anim.start();
} else if (event.mEvent == AnimationEvent.ANIMATION_END && anim.isStarted()) {
// Make sure anim hasn't finished before calling end() so that we don't end
// already ended animations, which will cause start and end callbacks to be
// triggered again.
anim.end();
}
}
}
@@ -476,12 +519,10 @@ public final class AnimatorSet extends Animator implements AnimationHandler.Anim
*/
@Override
public boolean isRunning() {
if (mStartDelay > 0) {
return mStarted && !mDelayAnim.isRunning();
} else {
// No start delay, animation should start right away
if (mStartDelay == 0) {
return mStarted;
}
return mLastFrameTime > 0;
}
@Override
@@ -673,8 +714,8 @@ public final class AnimatorSet extends Animator implements AnimationHandler.Anim
mReversing = inReverse;
// Now that all dependencies are set up, start the animations that should be started.
boolean setIsEmpty = isEmptySet(this);
if (!setIsEmpty) {
boolean isZeroDuration = ValueAnimator.getDurationScale() == 0f || isEmptySet(this);
if (!isZeroDuration) {
startAnimation();
}
@@ -686,9 +727,10 @@ public final class AnimatorSet extends Animator implements AnimationHandler.Anim
tmpListeners.get(i).onAnimationStart(this, inReverse);
}
}
if (setIsEmpty) {
// In the case of empty AnimatorSet, we will trigger the onAnimationEnd() right away.
end();
if (isZeroDuration) {
// In the case of empty AnimatorSet, or 0 duration scale, we will trigger the
// onAnimationEnd() right away.
forceToEnd();
}
}
@@ -815,6 +857,9 @@ public final class AnimatorSet extends Animator implements AnimationHandler.Anim
for (int i = 0; i < unfinishedNodes.size(); i++) {
Node node = unfinishedNodes.get(i);
long playTime = getPlayTimeForNode(currentPlayTime, node, inReverse);
if (!inReverse) {
playTime -= node.mAnimation.getStartDelay();
}
node.mAnimation.animateBasedOnPlayTime(playTime, lastPlayTime, inReverse);
}
}
@@ -907,8 +952,17 @@ public final class AnimatorSet extends Animator implements AnimationHandler.Anim
*/
@Override
public boolean doAnimationFrame(long frameTime) {
if (mLastFrameTime < 0) {
mFirstFrame = mLastFrameTime = frameTime;
float durationScale = ValueAnimator.getDurationScale();
if (durationScale == 0f) {
// Duration scale changed to 0 amid animation, end the animation right away.
forceToEnd();
return true;
}
// After the first frame comes in, we need to wait for start delay to pass before updating
// any animation values.
if (mFirstFrame < 0) {
mFirstFrame = frameTime;
}
// Handle pause/resume
@@ -928,19 +982,31 @@ public final class AnimatorSet extends Animator implements AnimationHandler.Anim
// Continue at seeked position
if (mSeekState.isActive()) {
mSeekState.updateSeekDirection(mReversing);
mFirstFrame = frameTime - mSeekState.getPlayTime() - mStartDelay;
if (mReversing) {
mFirstFrame = (long) (frameTime - mSeekState.getPlayTime() * durationScale);
} else {
mFirstFrame = (long) (frameTime - (mSeekState.getPlayTime() + mStartDelay)
* durationScale);
}
mSeekState.reset();
}
// This playTime includes the start delay.
long playTime = frameTime - mFirstFrame;
if (!mReversing && frameTime < mFirstFrame + mStartDelay * durationScale) {
// Still during start delay in a forward playing case.
return false;
}
// From here on, we always use unscaled play time. Note this unscaled playtime includes
// the start delay.
long unscaledPlayTime = (long) ((frameTime - mFirstFrame) / durationScale);
mLastFrameTime = frameTime;
// 1. Pulse the animators that will start or end in this frame
// 2. Pulse the animators that will finish in a later frame
int latestId = findLatestEventIdForTime(playTime);
int latestId = findLatestEventIdForTime(unscaledPlayTime);
int startId = mLastEventId;
handleAnimationEvents(startId, latestId, playTime);
handleAnimationEvents(startId, latestId, unscaledPlayTime);
mLastEventId = latestId;
@@ -948,8 +1014,7 @@ public final class AnimatorSet extends Animator implements AnimationHandler.Anim
for (int i = 0; i < mPlayingSet.size(); i++) {
Node node = mPlayingSet.get(i);
if (!node.mEnded) {
node.mEnded = node.mAnimation.pulseAnimationFrame(
getPlayTimeForNode(playTime, node));
pulseFrame(node, getPlayTimeForNode(unscaledPlayTime, node));
}
}
@@ -960,20 +1025,22 @@ public final class AnimatorSet extends Animator implements AnimationHandler.Anim
}
}
mLastFrameTime = frameTime;
if (mPlayingSet.isEmpty()) {
boolean finished;
if (mReversing) {
// Make sure there's no more END event before current event id and after start delay
finished = mLastEventId <= 3;
} else {
// Make sure there's no more START event before current event id:
finished = (mLastEventId == mEvents.size() - 1);
}
if (finished) {
endAnimation();
return true;
boolean finished = false;
if (mReversing) {
if (mPlayingSet.size() == 1 && mPlayingSet.get(0) == mRootNode) {
// The only animation that is running is the delay animation.
finished = true;
} else if (mPlayingSet.isEmpty() && mLastEventId < 3) {
// The only remaining animation is the delay animation
finished = true;
}
} else {
finished = mPlayingSet.isEmpty() && mLastEventId == mEvents.size() - 1;
}
if (finished) {
endAnimation();
return true;
}
return false;
}
@@ -1029,9 +1096,17 @@ public final class AnimatorSet extends Animator implements AnimationHandler.Anim
}
}
private void pulseFrame(Node node, long frameTime) {
/**
* This method pulses frames into child animations. It scales the input animation play time
* with the duration scale and pass that to the child animation via pulseAnimationFrame(long).
*
* @param node child animator node
* @param animPlayTime unscaled play time (including start delay) for the child animator
*/
private void pulseFrame(Node node, long animPlayTime) {
if (!node.mEnded) {
node.mEnded = node.mAnimation.pulseAnimationFrame(frameTime);
node.mEnded = node.mAnimation.pulseAnimationFrame(
(long) (animPlayTime * ValueAnimator.getDurationScale()));
}
}
@@ -1052,7 +1127,7 @@ public final class AnimatorSet extends Animator implements AnimationHandler.Anim
addDummyListener();
// Register animation callback
addAnimationCallback(mStartDelay);
addAnimationCallback(0);
if (mSeekState.getPlayTimeNormalized() == 0 && mReversing) {
// Maintain old behavior, if seeked to 0 then call reverse, we'll treat the case
@@ -1061,7 +1136,11 @@ public final class AnimatorSet extends Animator implements AnimationHandler.Anim
}
// Set the child animators to the right end:
if (mShouldResetValuesAtStart) {
if (mReversing || isInitialized()) {
if (isInitialized()) {
skipToEndValue(!mReversing);
} else if (mReversing) {
// Reversing but haven't initialized all the children yet.
initChildren();
skipToEndValue(!mReversing);
} else {
// If not all children are initialized and play direction is forward
@@ -1091,6 +1170,11 @@ public final class AnimatorSet extends Animator implements AnimationHandler.Anim
}
int toId = findLatestEventIdForTime(playTime);
handleAnimationEvents(-1, toId, playTime);
for (int i = mPlayingSet.size() - 1; i >= 0; i--) {
if (mPlayingSet.get(i).mEnded) {
mPlayingSet.remove(i);
}
}
mLastEventId = toId;
}
}
@@ -1797,6 +1881,7 @@ public final class AnimatorSet extends Animator implements AnimationHandler.Anim
if (mPlayTime >= 0) {
if (inReverse != mSeekingInReverse) {
mPlayTime = getTotalDuration() - mStartDelay - mPlayTime;
mSeekingInReverse = inReverse;
}
}
}

View File

@@ -1031,6 +1031,7 @@ public class ValueAnimator extends Animator implements AnimationHandler.Animatio
// started-but-not-yet-reached-the-first-frame phase.
mLastFrameTime = -1;
mFirstFrameTime = -1;
mStartTime = -1;
addAnimationCallback(0);
if (mStartDelay == 0 || mSeekFraction >= 0 || mReversing) {
@@ -1199,7 +1200,7 @@ public class ValueAnimator extends Animator implements AnimationHandler.Animatio
mStartListenersCalled = false;
mLastFrameTime = -1;
mFirstFrameTime = -1;
mReversing = false;
mStartTime = -1;
if (notify && mListeners != null) {
ArrayList<AnimatorListener> tmpListeners =
(ArrayList<AnimatorListener>) mListeners.clone();
@@ -1208,6 +1209,7 @@ public class ValueAnimator extends Animator implements AnimationHandler.Animatio
tmpListeners.get(i).onAnimationEnd(this, mReversing);
}
}
// mReversing needs to be reset *after* notifying the listeners for the end callbacks.
mReversing = false;
if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
Trace.asyncTraceEnd(Trace.TRACE_TAG_VIEW, getNameForTrace(),
@@ -1392,9 +1394,10 @@ public class ValueAnimator extends Animator implements AnimationHandler.Animatio
* @hide
*/
public final boolean doAnimationFrame(long frameTime) {
if (!mRunning && mStartTime < 0) {
// First frame during delay
mStartTime = frameTime + mStartDelay;
if (mStartTime < 0) {
// First frame. If there is start delay, start delay count down will happen *after* this
// frame.
mStartTime = mReversing ? frameTime : frameTime + mStartDelay;
}
// Handle pause/resume
@@ -1411,25 +1414,23 @@ public class ValueAnimator extends Animator implements AnimationHandler.Animatio
}
if (!mRunning) {
// If not running, that means the animation is in the start delay phase. In the case of
// reversing, we want to run start delay in the end.
if (mStartTime > frameTime) {
// During start delay
// If not running, that means the animation is in the start delay phase of a forward
// running animation. In the case of reversing, we want to run start delay in the end.
if (mStartTime > frameTime && mSeekFraction == -1) {
// This is when no seek fraction is set during start delay. If developers change the
// seek fraction during the delay, animation will start from the seeked position
// right away.
return false;
} else {
// Start delay has passed.
// If mRunning is not set by now, that means non-zero start delay,
// no seeking, not reversing. At this point, start delay has passed.
mRunning = true;
startAnimation();
}
}
if (mLastFrameTime < 0) {
// First frame
if (mStartDelay > 0) {
startAnimation();
}
if (mSeekFraction < 0) {
mStartTime = frameTime;
} else {
if (mSeekFraction >= 0) {
long seekTime = (long) (getScaledDuration() * mSeekFraction);
mStartTime = frameTime - seekTime;
mSeekFraction = -1;