diff --git a/core/java/android/animation/Animator.java b/core/java/android/animation/Animator.java index 8142ee5806cad..a9d14df8bcf4d 100644 --- a/core/java/android/animation/Animator.java +++ b/core/java/android/animation/Animator.java @@ -23,7 +23,6 @@ import android.compat.annotation.UnsupportedAppUsage; import android.content.pm.ActivityInfo.Config; import android.content.res.ConstantState; import android.os.Build; -import android.util.LongArray; import java.util.ArrayList; @@ -560,36 +559,9 @@ public abstract class Animator implements Cloneable { } /** - * Internal use only. Changes the value of the animator as if currentPlayTime has passed since - * the start of the animation. Therefore, currentPlayTime includes the start delay, and any - * repetition. lastPlayTime is similar and is used to calculate how many repeats have been - * done between the two times. + * Internal use only. */ - void animateValuesInRange(long currentPlayTime, long lastPlayTime) {} - - /** - * Internal use only. This animates any animation that has ended since lastPlayTime. - * If an animation hasn't been finished, no change will be made. - */ - void animateSkipToEnds(long currentPlayTime, long lastPlayTime) {} - - /** - * Internal use only. Adds all start times (after delay) to and end times to times. - * The value must include offset. - */ - void getStartAndEndTimes(LongArray times, long offset) { - long startTime = offset + getStartDelay(); - if (times.indexOf(startTime) < 0) { - times.add(startTime); - } - long duration = getTotalDuration(); - if (duration != DURATION_INFINITE) { - long endTime = duration + offset; - if (times.indexOf(endTime) < 0) { - times.add(endTime); - } - } - } + void animateBasedOnPlayTime(long currentPlayTime, long lastPlayTime, boolean inReverse) {} /** *

An animation listener receives notifications from an animation. diff --git a/core/java/android/animation/AnimatorSet.java b/core/java/android/animation/AnimatorSet.java index 54aaafc6c30a1..bc8db029e06d6 100644 --- a/core/java/android/animation/AnimatorSet.java +++ b/core/java/android/animation/AnimatorSet.java @@ -23,11 +23,9 @@ import android.os.Looper; import android.util.AndroidRuntimeException; import android.util.ArrayMap; import android.util.Log; -import android.util.LongArray; import android.view.animation.Animation; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collection; import java.util.Comparator; import java.util.HashMap; @@ -183,11 +181,6 @@ public final class AnimatorSet extends Animator implements AnimationHandler.Anim */ private long mPauseTime = -1; - /** - * The start and stop times of all descendant animators. - */ - private long[] mChildStartAndStopTimes; - // This is to work around a bug in b/34736819. This needs to be removed once app team // fixes their side. private AnimatorListenerAdapter mAnimationEndListener = new AnimatorListenerAdapter() { @@ -786,25 +779,26 @@ public final class AnimatorSet extends Animator implements AnimationHandler.Anim @Override void skipToEndValue(boolean inReverse) { + if (!isInitialized()) { + throw new UnsupportedOperationException("Children must be initialized."); + } + // This makes sure the animation events are sorted an up to date. initAnimation(); - initChildren(); // Calling skip to the end in the sequence that they would be called in a forward/reverse // run, such that the sequential animations modifying the same property would have // the right value in the end. if (inReverse) { for (int i = mEvents.size() - 1; i >= 0; i--) { - AnimationEvent event = mEvents.get(i); - if (event.mEvent == AnimationEvent.ANIMATION_DELAY_ENDED) { - event.mNode.mAnimation.skipToEndValue(true); + if (mEvents.get(i).mEvent == AnimationEvent.ANIMATION_DELAY_ENDED) { + mEvents.get(i).mNode.mAnimation.skipToEndValue(true); } } } else { for (int i = 0; i < mEvents.size(); i++) { - AnimationEvent event = mEvents.get(i); - if (event.mEvent == AnimationEvent.ANIMATION_END) { - event.mNode.mAnimation.skipToEndValue(false); + if (mEvents.get(i).mEvent == AnimationEvent.ANIMATION_END) { + mEvents.get(i).mNode.mAnimation.skipToEndValue(false); } } } @@ -820,181 +814,72 @@ public final class AnimatorSet extends Animator implements AnimationHandler.Anim * {@link android.view.animation.Animation.AnimationListener#onAnimationRepeat(Animation)}, * as needed, based on the last play time and current play time. */ - private void animateBasedOnPlayTime( - long currentPlayTime, - long lastPlayTime, - boolean inReverse - ) { - if (currentPlayTime < 0 || lastPlayTime < -1) { + @Override + void animateBasedOnPlayTime(long currentPlayTime, long lastPlayTime, boolean inReverse) { + if (currentPlayTime < 0 || lastPlayTime < 0) { throw new UnsupportedOperationException("Error: Play time should never be negative."); } // TODO: take into account repeat counts and repeat callback when repeat is implemented. + // Clamp currentPlayTime and lastPlayTime + // TODO: Make this more efficient + + // Convert the play times to the forward direction. if (inReverse) { - long duration = getTotalDuration(); - if (duration == DURATION_INFINITE) { - throw new UnsupportedOperationException( - "Cannot reverse AnimatorSet with infinite duration" - ); + if (getTotalDuration() == DURATION_INFINITE) { + throw new UnsupportedOperationException("Cannot reverse AnimatorSet with infinite" + + " duration"); } - // Convert the play times to the forward direction. + long duration = getTotalDuration() - mStartDelay; currentPlayTime = Math.min(currentPlayTime, duration); currentPlayTime = duration - currentPlayTime; lastPlayTime = duration - lastPlayTime; + inReverse = false; } - long[] startEndTimes = ensureChildStartAndEndTimes(); - int index = findNextIndex(lastPlayTime, startEndTimes); - int endIndex = findNextIndex(currentPlayTime, startEndTimes); - - // Change values at the start/end times so that values are set in the right order. - // We don't want an animator that would finish before another to override the value - // set by another animator that finishes earlier. - if (currentPlayTime >= lastPlayTime) { - while (index < endIndex) { - long playTime = startEndTimes[index]; - if (lastPlayTime != playTime) { - animateSkipToEnds(playTime, lastPlayTime); - animateValuesInRange(playTime, lastPlayTime); - lastPlayTime = playTime; - } - index++; - } - } else { - while (index > endIndex) { - index--; - long playTime = startEndTimes[index]; - if (lastPlayTime != playTime) { - animateSkipToEnds(playTime, lastPlayTime); - animateValuesInRange(playTime, lastPlayTime); - lastPlayTime = playTime; - } - } - } - if (currentPlayTime != lastPlayTime) { - animateSkipToEnds(currentPlayTime, lastPlayTime); - animateValuesInRange(currentPlayTime, lastPlayTime); - } - } - - /** - * Looks through startEndTimes for playTime. If it is in startEndTimes, the index after - * is returned. Otherwise, it returns the index at which it would be placed if it were - * to be inserted. - */ - private int findNextIndex(long playTime, long[] startEndTimes) { - int index = Arrays.binarySearch(startEndTimes, playTime); - if (index < 0) { - index = -index - 1; - } else { - index++; - } - return index; - } - - @Override - void animateSkipToEnds(long currentPlayTime, long lastPlayTime) { - initAnimation(); - - if (lastPlayTime > currentPlayTime) { - for (int i = mEvents.size() - 1; i >= 0; i--) { - AnimationEvent event = mEvents.get(i); - Node node = event.mNode; - if (event.mEvent == AnimationEvent.ANIMATION_END - && node.mStartTime != DURATION_INFINITE - ) { - Animator animator = node.mAnimation; - long start = node.mStartTime + animator.getStartDelay(); - long end = node.mTotalDuration == DURATION_INFINITE - ? Long.MAX_VALUE : node.mEndTime; - if (currentPlayTime <= start && start < lastPlayTime) { - animator.animateSkipToEnds( - start - node.mStartTime, - lastPlayTime - node.mStartTime - ); - } else if (start <= currentPlayTime && currentPlayTime <= end) { - animator.animateSkipToEnds( - currentPlayTime - node.mStartTime, - lastPlayTime - node.mStartTime - ); - } - } - } - } else { - int eventsSize = mEvents.size(); - for (int i = 0; i < eventsSize; i++) { - AnimationEvent event = mEvents.get(i); - Node node = event.mNode; - if (event.mEvent == AnimationEvent.ANIMATION_DELAY_ENDED - && node.mStartTime != DURATION_INFINITE - ) { - Animator animator = node.mAnimation; - long start = node.mStartTime + animator.getStartDelay(); - long end = node.mTotalDuration == DURATION_INFINITE - ? Long.MAX_VALUE : node.mEndTime; - if (lastPlayTime < end && end <= currentPlayTime) { - animator.animateSkipToEnds( - end - node.mStartTime, - lastPlayTime - node.mStartTime - ); - } else if (start <= currentPlayTime && currentPlayTime <= end) { - animator.animateSkipToEnds( - currentPlayTime - node.mStartTime, - lastPlayTime - node.mStartTime - ); - } - } - } - } - } - - @Override - void animateValuesInRange(long currentPlayTime, long lastPlayTime) { - initAnimation(); - - int eventsSize = mEvents.size(); - for (int i = 0; i < eventsSize; i++) { + ArrayList unfinishedNodes = new ArrayList<>(); + // Assumes forward playing from here on. + for (int i = 0; i < mEvents.size(); i++) { AnimationEvent event = mEvents.get(i); - Node node = event.mNode; - if (event.mEvent == AnimationEvent.ANIMATION_DELAY_ENDED - && node.mStartTime != DURATION_INFINITE - ) { - Animator animator = node.mAnimation; - long start = node.mStartTime + animator.getStartDelay(); - long end = node.mTotalDuration == DURATION_INFINITE - ? Long.MAX_VALUE : node.mEndTime; - if (start < currentPlayTime && currentPlayTime < end) { - animator.animateValuesInRange( - currentPlayTime - node.mStartTime, - Math.max(-1, lastPlayTime - node.mStartTime) - ); + if (event.getTime() > currentPlayTime || event.getTime() == DURATION_INFINITE) { + break; + } + + // This animation started prior to the current play time, and won't finish before the + // play time, add to the unfinished list. + if (event.mEvent == AnimationEvent.ANIMATION_DELAY_ENDED) { + if (event.mNode.mEndTime == DURATION_INFINITE + || event.mNode.mEndTime > currentPlayTime) { + unfinishedNodes.add(event.mNode); } } - } - } - - private long[] ensureChildStartAndEndTimes() { - if (mChildStartAndStopTimes == null) { - LongArray startAndEndTimes = new LongArray(); - getStartAndEndTimes(startAndEndTimes, 0); - long[] times = startAndEndTimes.toArray(); - Arrays.sort(times); - mChildStartAndStopTimes = times; - } - return mChildStartAndStopTimes; - } - - @Override - void getStartAndEndTimes(LongArray times, long offset) { - int eventsSize = mEvents.size(); - for (int i = 0; i < eventsSize; i++) { - AnimationEvent event = mEvents.get(i); - if (event.mEvent == AnimationEvent.ANIMATION_DELAY_ENDED - && event.mNode.mStartTime != DURATION_INFINITE - ) { - event.mNode.mAnimation.getStartAndEndTimes(times, offset + event.mNode.mStartTime); + // For animations that do finish before the play time, end them in the sequence that + // they would in a normal run. + if (event.mEvent == AnimationEvent.ANIMATION_END) { + // Skip to the end of the animation. + event.mNode.mAnimation.skipToEndValue(false); } } + + // Seek unfinished animation to the right time. + 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); + } + + // Seek not yet started animations. + for (int i = 0; i < mEvents.size(); i++) { + AnimationEvent event = mEvents.get(i); + if (event.getTime() > currentPlayTime + && event.mEvent == AnimationEvent.ANIMATION_DELAY_ENDED) { + event.mNode.mAnimation.skipToEndValue(true); + } + } + } @Override @@ -1014,6 +899,10 @@ public final class AnimatorSet extends Animator implements AnimationHandler.Anim return mChildrenInitialized; } + private void skipToStartValue(boolean inReverse) { + skipToEndValue(!inReverse); + } + /** * Sets the position of the animation to the specified point in time. This time should * be between 0 and the total duration of the animation, including any repetition. If @@ -1037,25 +926,23 @@ public final class AnimatorSet extends Animator implements AnimationHandler.Anim if ((getTotalDuration() != DURATION_INFINITE && playTime > getTotalDuration() - mStartDelay) || playTime < 0) { throw new UnsupportedOperationException("Error: Play time should always be in between" - + " 0 and duration."); + + "0 and duration."); } initAnimation(); if (!isStarted() || isPaused()) { - if (mReversing && !isStarted()) { + if (mReversing) { throw new UnsupportedOperationException("Error: Something went wrong. mReversing" + " should not be set when AnimatorSet is not started."); } - long lastPlayTime = mSeekState.getPlayTime(); if (!mSeekState.isActive()) { findLatestEventIdForTime(0); - initChildren(); // Set all the values to start values. - skipToEndValue(!mReversing); + initChildren(); mSeekState.setPlayTime(0, mReversing); } - animateBasedOnPlayTime(playTime, lastPlayTime, mReversing); + animateBasedOnPlayTime(playTime, 0, mReversing); mSeekState.setPlayTime(playTime, mReversing); } else { // If the animation is running, just set the seek time and wait until the next frame @@ -1094,16 +981,10 @@ public final class AnimatorSet extends Animator implements AnimationHandler.Anim private void initChildren() { if (!isInitialized()) { mChildrenInitialized = true; - - // We have to initialize all the start values so that they are based on the previous - // values. - long[] times = ensureChildStartAndEndTimes(); - - long previousTime = -1; - for (long time : times) { - animateBasedOnPlayTime(time, previousTime, false); - previousTime = time; - } + // Forcefully initialize all children based on their end time, so that if the start + // value of a child is dependent on a previous animation, the animation will be + // initialized after the the previous animations have been advanced to the end. + skipToEndValue(false); } } @@ -1177,7 +1058,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) { - pulseFrame(node, getPlayTimeForNodeIncludingDelay(unscaledPlayTime, node)); + pulseFrame(node, getPlayTimeForNode(unscaledPlayTime, node)); } } @@ -1248,7 +1129,7 @@ public final class AnimatorSet extends Animator implements AnimationHandler.Anim pulseFrame(node, 0); } else if (event.mEvent == AnimationEvent.ANIMATION_DELAY_ENDED && !node.mEnded) { // end event: - pulseFrame(node, getPlayTimeForNodeIncludingDelay(playTime, node)); + pulseFrame(node, getPlayTimeForNode(playTime, node)); } } } else { @@ -1269,7 +1150,7 @@ public final class AnimatorSet extends Animator implements AnimationHandler.Anim pulseFrame(node, 0); } else if (event.mEvent == AnimationEvent.ANIMATION_END && !node.mEnded) { // start event: - pulseFrame(node, getPlayTimeForNodeIncludingDelay(playTime, node)); + pulseFrame(node, getPlayTimeForNode(playTime, node)); } } } @@ -1291,15 +1172,11 @@ public final class AnimatorSet extends Animator implements AnimationHandler.Anim } } - private long getPlayTimeForNodeIncludingDelay(long overallPlayTime, Node node) { - return getPlayTimeForNodeIncludingDelay(overallPlayTime, node, mReversing); + private long getPlayTimeForNode(long overallPlayTime, Node node) { + return getPlayTimeForNode(overallPlayTime, node, mReversing); } - private long getPlayTimeForNodeIncludingDelay( - long overallPlayTime, - Node node, - boolean inReverse - ) { + private long getPlayTimeForNode(long overallPlayTime, Node node, boolean inReverse) { if (inReverse) { overallPlayTime = getTotalDuration() - overallPlayTime; return node.mEndTime - overallPlayTime; @@ -1321,8 +1198,26 @@ public final class AnimatorSet extends Animator implements AnimationHandler.Anim } // Set the child animators to the right end: if (mShouldResetValuesAtStart) { - initChildren(); - skipToEndValue(!mReversing); + 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 + for (int i = mEvents.size() - 1; i >= 0; i--) { + if (mEvents.get(i).mEvent == AnimationEvent.ANIMATION_DELAY_ENDED) { + Animator anim = mEvents.get(i).mNode.mAnimation; + // Only reset the animations that have been initialized to start value, + // so that if they are defined without a start value, they will get the + // values set at the right time (i.e. the next animation run) + if (anim.isInitialized()) { + anim.skipToEndValue(true); + } + } + } + } } if (mReversing || mStartDelay == 0 || mSeekState.isActive()) { @@ -2027,11 +1922,11 @@ public final class AnimatorSet extends Animator implements AnimationHandler.Anim } void setPlayTime(long playTime, boolean inReverse) { + // TODO: This can be simplified. + // Clamp the play time if (getTotalDuration() != DURATION_INFINITE) { mPlayTime = Math.min(playTime, getTotalDuration() - mStartDelay); - } else { - mPlayTime = playTime; } mPlayTime = Math.max(0, mPlayTime); mSeekingInReverse = inReverse; diff --git a/core/java/android/animation/ValueAnimator.java b/core/java/android/animation/ValueAnimator.java index d41c03d9ab82e..6ab7ae6d0ceed 100644 --- a/core/java/android/animation/ValueAnimator.java +++ b/core/java/android/animation/ValueAnimator.java @@ -324,9 +324,8 @@ public class ValueAnimator extends Animator implements AnimationHandler.Animatio listenerCopy = new ArrayList<>(sDurationScaleChangeListeners); } - int listenersSize = listenerCopy.size(); - for (int i = 0; i < listenersSize; i++) { - final DurationScaleChangeListener listener = listenerCopy.get(i).get(); + for (WeakReference listenerRef : listenerCopy) { + final DurationScaleChangeListener listener = listenerRef.get(); if (listener != null) { listener.onChanged(durationScale); } @@ -625,7 +624,7 @@ public class ValueAnimator extends Animator implements AnimationHandler.Animatio public void setValues(PropertyValuesHolder... values) { int numValues = values.length; mValues = values; - mValuesMap = new HashMap<>(numValues); + mValuesMap = new HashMap(numValues); for (int i = 0; i < numValues; ++i) { PropertyValuesHolder valuesHolder = values[i]; mValuesMap.put(valuesHolder.getPropertyName(), valuesHolder); @@ -659,11 +658,9 @@ public class ValueAnimator extends Animator implements AnimationHandler.Animatio @CallSuper void initAnimation() { if (!mInitialized) { - if (mValues != null) { - int numValues = mValues.length; - for (int i = 0; i < numValues; ++i) { - mValues[i].init(); - } + int numValues = mValues.length; + for (int i = 0; i < numValues; ++i) { + mValues[i].init(); } mInitialized = true; } @@ -1212,14 +1209,10 @@ public class ValueAnimator extends Animator implements AnimationHandler.Animatio // If it's not yet running, then start listeners weren't called. Call them now. notifyStartListeners(); } - int listenersSize = mListeners.size(); - if (listenersSize > 0) { - ArrayList tmpListeners = - (ArrayList) mListeners.clone(); - for (int i = 0; i < listenersSize; i++) { - AnimatorListener listener = tmpListeners.get(i); - listener.onAnimationCancel(this); - } + ArrayList tmpListeners = + (ArrayList) mListeners.clone(); + for (AnimatorListener listener : tmpListeners) { + listener.onAnimationCancel(this); } } endAnimation(); @@ -1459,19 +1452,12 @@ public class ValueAnimator extends Animator implements AnimationHandler.Animatio * will be called. */ @Override - void animateValuesInRange(long currentPlayTime, long lastPlayTime) { - if (currentPlayTime < mStartDelay || lastPlayTime < -1) { + void animateBasedOnPlayTime(long currentPlayTime, long lastPlayTime, boolean inReverse) { + if (currentPlayTime < 0 || lastPlayTime < 0) { throw new UnsupportedOperationException("Error: Play time should never be negative."); } initAnimation(); - long duration = getTotalDuration(); - if (duration >= 0) { - lastPlayTime = Math.min(duration, lastPlayTime); - } - lastPlayTime -= mStartDelay; - currentPlayTime -= mStartDelay; - // Check whether repeat callback is needed only when repeat count is non-zero if (mRepeatCount > 0) { int iteration = (int) (currentPlayTime / mDuration); @@ -1492,27 +1478,15 @@ public class ValueAnimator extends Animator implements AnimationHandler.Animatio } if (mRepeatCount != INFINITE && currentPlayTime >= (mRepeatCount + 1) * mDuration) { - throw new IllegalStateException("Can't animate a value outside of the duration"); + skipToEndValue(inReverse); } else { // Find the current fraction: float fraction = currentPlayTime / (float) mDuration; - fraction = getCurrentIterationFraction(fraction, false); + fraction = getCurrentIterationFraction(fraction, inReverse); animateValue(fraction); } } - @Override - void animateSkipToEnds(long currentPlayTime, long lastPlayTime) { - if (currentPlayTime <= mStartDelay && lastPlayTime > mStartDelay) { - skipToEndValue(true); - } else { - long duration = getTotalDuration(); - if (duration >= 0 && currentPlayTime >= duration && lastPlayTime < duration) { - skipToEndValue(false); - } - } - } - /** * Internal use only. * Skips the animation value to end/start, depending on whether the play direction is forward @@ -1667,9 +1641,6 @@ public class ValueAnimator extends Animator implements AnimationHandler.Animatio Trace.traceCounter(Trace.TRACE_TAG_VIEW, getNameForTrace() + hashCode(), (int) (fraction * 1000)); } - if (mValues == null) { - return; - } fraction = mInterpolator.getInterpolation(fraction); mCurrentFraction = fraction; int numValues = mValues.length;