Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit abcd1d52 authored by George Mount's avatar George Mount Committed by Android (Google) Code Review
Browse files

Merge "Improve AnimatorSet seekability."

parents ad7b2fd6 936e3551
Loading
Loading
Loading
Loading
+30 −2
Original line number Diff line number Diff line
@@ -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.
+195 −90
Original line number Diff line number Diff line
@@ -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++) {
        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);
            if (event.getTime() > currentPlayTime || event.getTime() == DURATION_INFINITE) {
                break;
                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
                        );
                    }
                }
            }
        }
    }

            // 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);
    @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)
                    );
                }
            }
            // 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();
    private long[] ensureChildStartAndEndTimes() {
        if (mChildStartAndStopTimes == null) {
            LongArray startAndEndTimes = new LongArray();
            getStartAndEndTimes(startAndEndTimes, 0);
            long[] times = startAndEndTimes.toArray();
            Arrays.sort(times);
            mChildStartAndStopTimes = times;
        }
            node.mAnimation.animateBasedOnPlayTime(playTime, lastPlayTime, inReverse);
        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
@@ -932,17 +1043,19 @@ public final class AnimatorSet extends Animator implements AnimationHandler.Anim
        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);
                        }
                    }
                }
            }
        }

        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;
+43 −14
Original line number Diff line number Diff line
@@ -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,10 +659,12 @@ 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();
                }
            }
            mInitialized = true;
        }
    }
@@ -1209,12 +1212,16 @@ 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<AnimatorListener> tmpListeners =
                        (ArrayList<AnimatorListener>) mListeners.clone();
            for (AnimatorListener listener : tmpListeners) {
                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;