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

Commit e2ab7ccd authored by Chet Haase's avatar Chet Haase
Browse files

Change cancel/end behavior of animations to be synchronous

Previously, cancel() and end() calls would simply log a message to
be handled later by the animation handler. This caused problems with
coordinating complex animations, where some start() events for
future animations would occur before end() events for animations already
completed.
The change is to make these events synchronous (and require them to be
called from the appropriate thread), simplifying the code and the usage.

Also, fixed various timing and event bugs in AnimatorSet, and removed
the getter/setter properties from ObjectAnimator, since an earlier change
makes these properties undesirable (because the code will use a faster
JNI approach instead of reflection when it can).

Change-Id: I05c16645c2a31a92048a6031ddb126eb4312a946
parent 475593e2
Loading
Loading
Loading
Loading
+1 −49
Original line number Diff line number Diff line
@@ -20161,17 +20161,6 @@
 visibility="public"
>
</method>
<method name="getGetter"
 return="java.lang.reflect.Method"
 abstract="false"
 native="false"
 synchronized="false"
 static="false"
 final="false"
 deprecated="not deprecated"
 visibility="public"
>
</method>
<method name="getPropertyName"
 return="java.lang.String"
 abstract="false"
@@ -20183,17 +20172,6 @@
 visibility="public"
>
</method>
<method name="getSetter"
 return="java.lang.reflect.Method"
 abstract="false"
 native="false"
 synchronized="false"
 static="false"
 final="false"
 deprecated="not deprecated"
 visibility="public"
>
</method>
<method name="ofFloat"
 return="android.animation.PropertyValuesHolder"
 abstract="false"
@@ -20282,19 +20260,6 @@
<parameter name="values" type="float...">
</parameter>
</method>
<method name="setGetter"
 return="void"
 abstract="false"
 native="false"
 synchronized="false"
 static="false"
 final="false"
 deprecated="not deprecated"
 visibility="public"
>
<parameter name="getter" type="java.lang.reflect.Method">
</parameter>
</method>
<method name="setIntValues"
 return="void"
 abstract="false"
@@ -20347,19 +20312,6 @@
<parameter name="propertyName" type="java.lang.String">
</parameter>
</method>
<method name="setSetter"
 return="void"
 abstract="false"
 native="false"
 synchronized="false"
 static="false"
 final="false"
 deprecated="not deprecated"
 visibility="public"
>
<parameter name="setter" type="java.lang.reflect.Method">
</parameter>
</method>
</class>
<class name="RGBEvaluator"
 extends="java.lang.Object"
@@ -250407,7 +250359,7 @@
 deprecated="not deprecated"
 visibility="public"
>
<parameter name="arg0" type="T">
<parameter name="t" type="T">
</parameter>
</method>
</interface>
+17 −3
Original line number Diff line number Diff line
@@ -36,22 +36,36 @@ public abstract class Animator implements Cloneable {
     * this call, because all animation events are posted to a central timing loop so that animation
     * times are all synchronized on a single timing pulse on the UI thread. So the animation will
     * start the next time that event handler processes events.
     *
     * <p>The animation started by calling this method will be run on the thread that called
     * this method. This thread should have a Looper on it (a runtime exception will be thrown if
     * this is not the case). Also, if the animation will animate
     * properties of objects in the view hierarchy, then the calling thread should be the UI
     * thread for that view hierarchy.</p>
     *
     */
    public void start() {
    }

    /**
     * Cancels the animation. Unlike {@link #end()}, <code>cancel()</code> causes the animation to
     * stop in its tracks, sending an {@link android.animation.Animator.AnimatorListener#onAnimationCancel(Animator)} to
     * its listeners, followed by an {@link android.animation.Animator.AnimatorListener#onAnimationEnd(Animator)} message.
     * stop in its tracks, sending an
     * {@link android.animation.Animator.AnimatorListener#onAnimationCancel(Animator)} to
     * its listeners, followed by an
     * {@link android.animation.Animator.AnimatorListener#onAnimationEnd(Animator)} message.
     *
     * <p>This method must be called on the thread that is running the animation.</p>
     */
    public void cancel() {
    }

    /**
     * Ends the animation. This causes the animation to assign the end value of the property being
     * animated, then calling the {@link android.animation.Animator.AnimatorListener#onAnimationEnd(Animator)} method on
     * animated, then calling the
     * {@link android.animation.Animator.AnimatorListener#onAnimationEnd(Animator)} method on
     * its listeners.
     *
     * <p>This method must be called on the thread that is running the animation.</p>
     */
    public void end() {
    }
+48 −10
Original line number Diff line number Diff line
@@ -96,6 +96,9 @@ public final class AnimatorSet extends Animator {
    // The amount of time in ms to delay starting the animation after start() is called
    private long mStartDelay = 0;

    // Animator used for a nonzero startDelay
    private ValueAnimator mDelayAnim = null;


    // How long the child animations should last in ms. The default value is negative, which
    // simply means that there is no duration set on the AnimatorSet. When a real duration is
@@ -276,6 +279,19 @@ public final class AnimatorSet extends Animator {
                listener.onAnimationCancel(this);
            }
        }
        if (mDelayAnim != null && mDelayAnim.isRunning()) {
            // If we're currently in the startDelay period, just cancel that animator and
            // send out the end event to all listeners
            mDelayAnim.cancel();
            if (mListeners != null) {
                ArrayList<AnimatorListener> tmpListeners =
                        (ArrayList<AnimatorListener>) mListeners.clone();
                for (AnimatorListener listener : tmpListeners) {
                    listener.onAnimationEnd(this);
                }
            }
            return;
        }
        if (mSortedNodes.size() > 0) {
            for (Node node : mSortedNodes) {
                node.animation.cancel();
@@ -302,6 +318,9 @@ public final class AnimatorSet extends Animator {
                node.animation.addListener(mSetListener);
            }
        }
        if (mDelayAnim != null) {
            mDelayAnim.cancel();
        }
        if (mSortedNodes.size() > 0) {
            for (Node node : mSortedNodes) {
                node.animation.end();
@@ -411,12 +430,25 @@ public final class AnimatorSet extends Animator {
        // contains the animation nodes in the correct order.
        sortNodes();

        int numSortedNodes = mSortedNodes.size();
        for (int i = 0; i < numSortedNodes; ++i) {
            Node node = mSortedNodes.get(i);
            // First, clear out the old listeners
            ArrayList<AnimatorListener> oldListeners = node.animation.getListeners();
            if (oldListeners != null && oldListeners.size() > 0) {
                for (AnimatorListener listener : oldListeners) {
                    if (listener instanceof DependencyListener) {
                        node.animation.removeListener(listener);
                    }
                }
            }
        }

        // nodesToStart holds the list of nodes to be started immediately. We don't want to
        // start the animations in the loop directly because we first need to set up
        // dependencies on all of the nodes. For example, we don't want to start an animation
        // when some other animation also wants to start when the first animation begins.
        final ArrayList<Node> nodesToStart = new ArrayList<Node>();
        int numSortedNodes = mSortedNodes.size();
        for (int i = 0; i < numSortedNodes; ++i) {
            Node node = mSortedNodes.get(i);
            if (mSetListener == null) {
@@ -443,10 +475,15 @@ public final class AnimatorSet extends Animator {
            }
        } else {
            // TODO: Need to cancel out of the delay appropriately
            ValueAnimator delayAnim = ValueAnimator.ofFloat(0f, 1f);
            delayAnim.setDuration(mStartDelay);
            delayAnim.addListener(new AnimatorListenerAdapter() {
            mDelayAnim = ValueAnimator.ofFloat(0f, 1f);
            mDelayAnim.setDuration(mStartDelay);
            mDelayAnim.addListener(new AnimatorListenerAdapter() {
                boolean canceled = false;
                public void onAnimationCancel(Animator anim) {
                    canceled = true;
                }
                public void onAnimationEnd(Animator anim) {
                    if (!canceled) {
                        int numNodes = nodesToStart.size();
                        for (int i = 0; i < numNodes; ++i) {
                            Node node = nodesToStart.get(i);
@@ -454,8 +491,9 @@ public final class AnimatorSet extends Animator {
                            mPlayingSet.add(node.animation);
                        }
                    }
                }
            });
            delayAnim.start();
            mDelayAnim.start();
        }
        if (mListeners != null) {
            ArrayList<AnimatorListener> tmpListeners =
+17 −0
Original line number Diff line number Diff line
@@ -19,6 +19,7 @@ package android.animation;
import android.util.Log;

import java.lang.reflect.Method;
import java.util.ArrayList;

/**
 * This subclass of {@link ValueAnimator} provides support for animating properties on target objects.
@@ -31,6 +32,7 @@ import java.lang.reflect.Method;
 *
 */
public final class ObjectAnimator extends ValueAnimator {
    private static final boolean DBG = false;

    // The target object on which the property exists, set in the constructor
    private Object mTarget;
@@ -265,6 +267,21 @@ public final class ObjectAnimator extends ValueAnimator {
        }
    }

    @Override
    public void start() {
        if (DBG) {
            Log.d("ObjectAnimator", "Anim target, duration" + mTarget + ", " + getDuration());
            for (int i = 0; i < mValues.length; ++i) {
                PropertyValuesHolder pvh = mValues[i];
                ArrayList<Keyframe> keyframes = pvh.mKeyframeSet.mKeyframes;
                Log.d("ObjectAnimator", "   Values[" + i + "]: " +
                    pvh.getPropertyName() + ", " + keyframes.get(0).getValue() + ", " +
                    keyframes.get(pvh.mKeyframeSet.mNumKeyframes - 1).getValue());
            }
        }
        super.start();
    }

    /**
     * This function is called immediately before processing the first animation
     * frame of an animation. If there is a nonzero <code>startDelay</code>, the
+0 −61
Original line number Diff line number Diff line
@@ -543,67 +543,6 @@ public class PropertyValuesHolder implements Cloneable {
        mAnimatedValue = mKeyframeSet.getValue(fraction);
    }

    /**
     * Sets the <code>Method</code> that is called with the animated values calculated
     * during the animation. Setting the setter method is an alternative to supplying a
     * {@link #setPropertyName(String) propertyName} from which the method is derived. This
     * approach is more direct, and is especially useful when a function must be called that does
     * not correspond to the convention of <code>setName()</code>. For example, if a function
     * called <code>offset()</code> is to be called with the animated values, there is no way
     * to tell <code>ObjectAnimator</code> how to call that function simply through a property
     * name, so a setter method should be supplied instead.
     *
     * <p>Note that the setter function must take the same parameter type as the
     * <code>valueFrom</code> and <code>valueTo</code> properties, otherwise the call to
     * the setter function will fail.</p>
     *
     * @param setter The setter method that should be called with the animated values.
     */
    public void setSetter(Method setter) {
        mSetter = setter;
    }

    /**
     * Gets the <code>Method</code> that is called with the animated values calculated
     * during the animation.
     */
    public Method getSetter() {
        return mSetter;
    }

    /**
     * Sets the <code>Method</code> that is called to get unsupplied <code>valueFrom</code> or
     * <code>valueTo</code> properties. Setting the getter method is an alternative to supplying a
     * {@link #setPropertyName(String) propertyName} from which the method is derived. This
     * approach is more direct, and is especially useful when a function must be called that does
     * not correspond to the convention of <code>setName()</code>. For example, if a function
     * called <code>offset()</code> is to be called to get an initial value, there is no way
     * to tell <code>ObjectAnimator</code> how to call that function simply through a property
     * name, so a getter method should be supplied instead.
     *
     * <p>Note that the getter method is only called whether supplied here or derived
     * from the property name, if one of <code>valueFrom</code> or <code>valueTo</code> are
     * null. If both of those values are non-null, then there is no need to get one of the
     * values and the getter is not called.
     *
     * <p>Note that the getter function must return the same parameter type as the
     * <code>valueFrom</code> and <code>valueTo</code> properties (whichever of them are
     * non-null), otherwise the call to the getter function will fail.</p>
     *
     * @param getter The getter method that should be called to get initial animation values.
     */
    public void setGetter(Method getter) {
        mGetter = getter;
    }

    /**
     * Gets the <code>Method</code> that is called to get unsupplied <code>valueFrom</code> or
     * <code>valueTo</code> properties.
     */
    public Method getGetter() {
        return mGetter;
    }

    /**
     * Sets the name of the property that will be animated. This name is used to derive
     * a setter function that will be called to set animated values.
Loading