Fix reverse with start delay
Test: cts in the same topic branch Change-Id: Ie8793f25d39e0104c880f1a873f68650b918bffa
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user