diff options
| -rw-r--r-- | core/java/android/animation/Animator.java | 32 | ||||
| -rw-r--r-- | core/java/android/animation/AnimatorSet.java | 285 | ||||
| -rw-r--r-- | core/java/android/animation/ValueAnimator.java | 57 |
3 files changed, 268 insertions, 106 deletions
diff --git a/core/java/android/animation/Animator.java b/core/java/android/animation/Animator.java index a9d14df8bcf4..8142ee5806ca 100644 --- a/core/java/android/animation/Animator.java +++ b/core/java/android/animation/Animator.java @@ -23,6 +23,7 @@ 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; @@ -559,9 +560,36 @@ public abstract class Animator implements Cloneable { } /** - * Internal use only. + * 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. + */ + 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 animateBasedOnPlayTime(long currentPlayTime, long lastPlayTime, boolean inReverse) {} + 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); + } + } + } /** * <p>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 bc8db029e06d..54aaafc6c30a 100644 --- a/core/java/android/animation/AnimatorSet.java +++ b/core/java/android/animation/AnimatorSet.java @@ -23,9 +23,11 @@ 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; @@ -181,6 +183,11 @@ 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() { @@ -779,26 +786,25 @@ 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--) { - if (mEvents.get(i).mEvent == AnimationEvent.ANIMATION_DELAY_ENDED) { - mEvents.get(i).mNode.mAnimation.skipToEndValue(true); + AnimationEvent event = mEvents.get(i); + if (event.mEvent == AnimationEvent.ANIMATION_DELAY_ENDED) { + event.mNode.mAnimation.skipToEndValue(true); } } } else { for (int i = 0; i < mEvents.size(); i++) { - if (mEvents.get(i).mEvent == AnimationEvent.ANIMATION_END) { - mEvents.get(i).mNode.mAnimation.skipToEndValue(false); + AnimationEvent event = mEvents.get(i); + if (event.mEvent == AnimationEvent.ANIMATION_END) { + event.mNode.mAnimation.skipToEndValue(false); } } } @@ -814,72 +820,181 @@ 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. */ - @Override - void animateBasedOnPlayTime(long currentPlayTime, long lastPlayTime, boolean inReverse) { - if (currentPlayTime < 0 || lastPlayTime < 0) { + private void animateBasedOnPlayTime( + long currentPlayTime, + long lastPlayTime, + boolean inReverse + ) { + if (currentPlayTime < 0 || lastPlayTime < -1) { 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) { - if (getTotalDuration() == DURATION_INFINITE) { - throw new UnsupportedOperationException("Cannot reverse AnimatorSet with infinite" - + " duration"); + long duration = getTotalDuration(); + if (duration == DURATION_INFINITE) { + throw new UnsupportedOperationException( + "Cannot reverse AnimatorSet with infinite duration" + ); } - long duration = getTotalDuration() - mStartDelay; + // Convert the play times to the forward direction. currentPlayTime = Math.min(currentPlayTime, duration); currentPlayTime = duration - currentPlayTime; lastPlayTime = duration - lastPlayTime; - inReverse = false; } - ArrayList<Node> unfinishedNodes = new ArrayList<>(); - // Assumes forward playing from here on. - for (int i = 0; i < mEvents.size(); i++) { - AnimationEvent event = mEvents.get(i); - if (event.getTime() > currentPlayTime || event.getTime() == DURATION_INFINITE) { - break; + 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); + } + } - // 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); + /** + * 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 + ); + } } } - // 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); + } 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 + ); + } + } } } + } - // 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(); + @Override + void animateValuesInRange(long currentPlayTime, long lastPlayTime) { + initAnimation(); + + 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 (start < currentPlayTime && currentPlayTime < end) { + animator.animateValuesInRange( + currentPlayTime - node.mStartTime, + Math.max(-1, lastPlayTime - node.mStartTime) + ); + } } - node.mAnimation.animateBasedOnPlayTime(playTime, lastPlayTime, inReverse); } + } + + private long[] ensureChildStartAndEndTimes() { + if (mChildStartAndStopTimes == null) { + LongArray startAndEndTimes = new LongArray(); + getStartAndEndTimes(startAndEndTimes, 0); + long[] times = startAndEndTimes.toArray(); + Arrays.sort(times); + mChildStartAndStopTimes = times; + } + return mChildStartAndStopTimes; + } - // Seek not yet started animations. - for (int i = 0; i < mEvents.size(); i++) { + @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.getTime() > currentPlayTime - && event.mEvent == AnimationEvent.ANIMATION_DELAY_ENDED) { - event.mNode.mAnimation.skipToEndValue(true); + if (event.mEvent == AnimationEvent.ANIMATION_DELAY_ENDED + && event.mNode.mStartTime != DURATION_INFINITE + ) { + event.mNode.mAnimation.getStartAndEndTimes(times, offset + event.mNode.mStartTime); } } - } @Override @@ -899,10 +1014,6 @@ 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 @@ -926,23 +1037,25 @@ 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) { + if (mReversing && !isStarted()) { 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); - // Set all the values to start values. initChildren(); + // Set all the values to start values. + skipToEndValue(!mReversing); mSeekState.setPlayTime(0, mReversing); } - animateBasedOnPlayTime(playTime, 0, mReversing); + animateBasedOnPlayTime(playTime, lastPlayTime, mReversing); mSeekState.setPlayTime(playTime, mReversing); } else { // If the animation is running, just set the seek time and wait until the next frame @@ -981,10 +1094,16 @@ public final class AnimatorSet extends Animator implements AnimationHandler.Anim private void initChildren() { if (!isInitialized()) { mChildrenInitialized = true; - // 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); + + // 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; + } } } @@ -1058,7 +1177,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, getPlayTimeForNode(unscaledPlayTime, node)); + pulseFrame(node, getPlayTimeForNodeIncludingDelay(unscaledPlayTime, node)); } } @@ -1129,7 +1248,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, getPlayTimeForNode(playTime, node)); + pulseFrame(node, getPlayTimeForNodeIncludingDelay(playTime, node)); } } } else { @@ -1150,7 +1269,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, getPlayTimeForNode(playTime, node)); + pulseFrame(node, getPlayTimeForNodeIncludingDelay(playTime, node)); } } } @@ -1172,11 +1291,15 @@ public final class AnimatorSet extends Animator implements AnimationHandler.Anim } } - private long getPlayTimeForNode(long overallPlayTime, Node node) { - return getPlayTimeForNode(overallPlayTime, node, mReversing); + private long getPlayTimeForNodeIncludingDelay(long overallPlayTime, Node node) { + return getPlayTimeForNodeIncludingDelay(overallPlayTime, node, mReversing); } - private long getPlayTimeForNode(long overallPlayTime, Node node, boolean inReverse) { + private long getPlayTimeForNodeIncludingDelay( + long overallPlayTime, + Node node, + boolean inReverse + ) { if (inReverse) { overallPlayTime = getTotalDuration() - overallPlayTime; return node.mEndTime - overallPlayTime; @@ -1198,26 +1321,8 @@ public final class AnimatorSet extends Animator implements AnimationHandler.Anim } // Set the child animators to the right end: if (mShouldResetValuesAtStart) { - 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); - } - } - } - } + initChildren(); + skipToEndValue(!mReversing); } if (mReversing || mStartDelay == 0 || mSeekState.isActive()) { @@ -1922,11 +2027,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 6ab7ae6d0cee..d41c03d9ab82 100644 --- a/core/java/android/animation/ValueAnimator.java +++ b/core/java/android/animation/ValueAnimator.java @@ -324,8 +324,9 @@ public class ValueAnimator extends Animator implements AnimationHandler.Animatio listenerCopy = new ArrayList<>(sDurationScaleChangeListeners); } - for (WeakReference<DurationScaleChangeListener> listenerRef : listenerCopy) { - final DurationScaleChangeListener listener = listenerRef.get(); + int listenersSize = listenerCopy.size(); + for (int i = 0; i < listenersSize; i++) { + final DurationScaleChangeListener listener = listenerCopy.get(i).get(); if (listener != null) { listener.onChanged(durationScale); } @@ -624,7 +625,7 @@ public class ValueAnimator extends Animator implements AnimationHandler.Animatio public void setValues(PropertyValuesHolder... values) { int numValues = values.length; mValues = values; - mValuesMap = new HashMap<String, PropertyValuesHolder>(numValues); + mValuesMap = new HashMap<>(numValues); for (int i = 0; i < numValues; ++i) { PropertyValuesHolder valuesHolder = values[i]; mValuesMap.put(valuesHolder.getPropertyName(), valuesHolder); @@ -658,9 +659,11 @@ public class ValueAnimator extends Animator implements AnimationHandler.Animatio @CallSuper void initAnimation() { if (!mInitialized) { - int numValues = mValues.length; - for (int i = 0; i < numValues; ++i) { - mValues[i].init(); + if (mValues != null) { + int numValues = mValues.length; + for (int i = 0; i < numValues; ++i) { + mValues[i].init(); + } } mInitialized = true; } @@ -1209,10 +1212,14 @@ public class ValueAnimator extends Animator implements AnimationHandler.Animatio // If it's not yet running, then start listeners weren't called. Call them now. notifyStartListeners(); } - ArrayList<AnimatorListener> tmpListeners = - (ArrayList<AnimatorListener>) mListeners.clone(); - for (AnimatorListener listener : tmpListeners) { - listener.onAnimationCancel(this); + int listenersSize = mListeners.size(); + if (listenersSize > 0) { + ArrayList<AnimatorListener> tmpListeners = + (ArrayList<AnimatorListener>) mListeners.clone(); + for (int i = 0; i < listenersSize; i++) { + AnimatorListener listener = tmpListeners.get(i); + listener.onAnimationCancel(this); + } } } endAnimation(); @@ -1452,12 +1459,19 @@ public class ValueAnimator extends Animator implements AnimationHandler.Animatio * will be called. */ @Override - void animateBasedOnPlayTime(long currentPlayTime, long lastPlayTime, boolean inReverse) { - if (currentPlayTime < 0 || lastPlayTime < 0) { + void animateValuesInRange(long currentPlayTime, long lastPlayTime) { + if (currentPlayTime < mStartDelay || lastPlayTime < -1) { 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); @@ -1478,15 +1492,27 @@ public class ValueAnimator extends Animator implements AnimationHandler.Animatio } if (mRepeatCount != INFINITE && currentPlayTime >= (mRepeatCount + 1) * mDuration) { - skipToEndValue(inReverse); + throw new IllegalStateException("Can't animate a value outside of the duration"); } else { // Find the current fraction: float fraction = currentPlayTime / (float) mDuration; - fraction = getCurrentIterationFraction(fraction, inReverse); + fraction = getCurrentIterationFraction(fraction, false); 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 @@ -1641,6 +1667,9 @@ 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; |