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

Commit d3de42ca authored by John Reck's avatar John Reck
Browse files

Add RT-enabled reveal animator

 Bug: 16161431

 Also re-writes RevealAnimator to avoid using any listeners internally,
 removing the logic around shadowing the update listeners.

Change-Id: I6ed8126398eed971a87f20bccb7584c9acafbb6c
parent fac77c46
Loading
Loading
Loading
Loading
+147 −84
Original line number Diff line number Diff line
@@ -16,10 +16,9 @@

package android.animation;

import android.view.RenderNodeAnimator;
import android.view.View;

import java.util.ArrayList;

/**
 * Reveals a View with an animated clipping circle.
 * The clipping is implemented efficiently by talking to a private reveal API on View.
@@ -28,114 +27,178 @@ import java.util.ArrayList;
 * @hide
 */
public class RevealAnimator extends ValueAnimator {
    private final static String LOGTAG = "RevealAnimator";
    private ValueAnimator.AnimatorListener mListener;
    private ValueAnimator.AnimatorUpdateListener mUpdateListener;
    private RevealCircle mReuseRevealCircle = new RevealCircle(0);
    private RevealAnimator(final View clipView, final int x, final int y,
            float startRadius, float endRadius, final boolean inverseClip) {

        setObjectValues(new RevealCircle(startRadius), new RevealCircle(endRadius));
        setEvaluator(new RevealCircleEvaluator(mReuseRevealCircle));
    private View mClipView;
    private int mX, mY;
    private boolean mInverseClip;
    private float mStartRadius, mEndRadius;
    private float mDelta;
    private boolean mMayRunAsync;

        mUpdateListener = new ValueAnimator.AnimatorUpdateListener() {
                @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                RevealCircle circle = (RevealCircle) animation.getAnimatedValue();
                float radius = circle.getRadius();
                clipView.setRevealClip(true, inverseClip, x, y, radius);
    // If this is null, we are running on the UI thread driven by the base
    // ValueAnimator class. If this is not null, forward requests on to this
    // Animator instead.
    private RenderNodeAnimator mRtAnimator;

    public RevealAnimator(View clipView, int x, int y,
            float startRadius, float endRadius, boolean inverseClip) {
        mClipView = clipView;
        mStartRadius = startRadius;
        mEndRadius = endRadius;
        mDelta = endRadius - startRadius;
        mX = x;
        mY = y;
        mInverseClip = inverseClip;
        super.setValues(PropertyValuesHolder.ofFloat("radius", startRadius, endRadius));
    }
        };
        mListener = new AnimatorListenerAdapter() {

    @Override
            public void onAnimationCancel(Animator animation) {
                clipView.setRevealClip(false, false, 0, 0, 0);
    void animateValue(float fraction) {
        super.animateValue(fraction);
        fraction = getAnimatedFraction();
        float radius = mStartRadius + (mDelta * fraction);
        mClipView.setRevealClip(true, mInverseClip, mX, mY, radius);
    }

    @Override
            public void onAnimationEnd(Animator animation) {
                clipView.setRevealClip(false, false, 0, 0, 0);
    protected void endAnimation(AnimationHandler handler) {
        mClipView.setRevealClip(false, false, 0, 0, 0);
        super.endAnimation(handler);
    }
        };
        addUpdateListener(mUpdateListener);
        addListener(mListener);

    @Override
    public void setAllowRunningAsynchronously(boolean mayRunAsync) {
        mMayRunAsync = mayRunAsync;
    }

    public static RevealAnimator ofRevealCircle(View clipView, int x, int y,
            float startRadius, float endRadius, boolean inverseClip) {
        RevealAnimator anim = new RevealAnimator(clipView, x, y,
                startRadius, endRadius, inverseClip);
        return anim;
    private boolean canRunAsync() {
        if (!mMayRunAsync) {
            return false;
        }
        if (mUpdateListeners != null && mUpdateListeners.size() > 0) {
            return false;
        }
        // TODO: Have RNA support this
        if (getRepeatCount() != 0) {
            return false;
        }
        return true;
    }

    @Override
    public void start() {
        if (mRtAnimator != null) {
            mRtAnimator.end();
            mRtAnimator = null;
        }
        if (canRunAsync()) {
            mRtAnimator = new RenderNodeAnimator(mX, mY, mInverseClip, mStartRadius, mEndRadius);
            mRtAnimator.setDuration(getDuration());
            mRtAnimator.setInterpolator(getInterpolator());
            mRtAnimator.setTarget(mClipView);
            // TODO: Listeners
            mRtAnimator.start();
        } else {
            super.start();
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void removeAllUpdateListeners() {
        super.removeAllUpdateListeners();
        addUpdateListener(mUpdateListener);
    public void cancel() {
        if (mRtAnimator != null) {
            mRtAnimator.cancel();
        } else {
            super.cancel();
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void removeAllListeners() {
        super.removeAllListeners();
        addListener(mListener);
    public void end() {
        if (mRtAnimator != null) {
            mRtAnimator.end();
        } else {
            super.end();
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public ArrayList<AnimatorListener> getListeners() {
        ArrayList<AnimatorListener> allListeners =
                (ArrayList<AnimatorListener>) super.getListeners().clone();
        allListeners.remove(mListener);
        return allListeners;
    public void resume() {
        if (mRtAnimator != null) {
            // TODO: Support? Reject?
        } else {
            super.resume();
        }
    }

    private class RevealCircle {
        float mRadius;
    @Override
    public void pause() {
        if (mRtAnimator != null) {
            // TODO: see resume()
        } else {
            super.pause();
        }
    }

        public RevealCircle(float radius) {
            mRadius = radius;
    @Override
    public boolean isRunning() {
        if (mRtAnimator != null) {
            return mRtAnimator.isRunning();
        } else {
            return super.isRunning();
        }
    }

        public void setRadius(float radius) {
            mRadius = radius;
    @Override
    public boolean isStarted() {
        if (mRtAnimator != null) {
            return mRtAnimator.isStarted();
        } else {
            return super.isStarted();
        }
    }

        public float getRadius() {
            return mRadius;
    @Override
    public void reverse() {
        if (mRtAnimator != null) {
            // TODO support
        } else {
            super.reverse();
        }
    }

    private class RevealCircleEvaluator implements TypeEvaluator<RevealCircle> {
    @Override
    public ValueAnimator clone() {
        RevealAnimator anim = (RevealAnimator) super.clone();
        anim.mRtAnimator = null;
        return anim;
    }

        private RevealCircle mRevealCircle;
    // ----------------------------------------
    //  All the things we don't allow
    // ----------------------------------------

        public RevealCircleEvaluator() {
    @Override
    public void setValues(PropertyValuesHolder... values) {
        throw new IllegalStateException("Cannot change the values of RevealAnimator");
    }

        public RevealCircleEvaluator(RevealCircle reuseCircle) {
            mRevealCircle = reuseCircle;
    @Override
    public void setFloatValues(float... values) {
        throw new IllegalStateException("Cannot change the values of RevealAnimator");
    }

    @Override
        public RevealCircle evaluate(float fraction, RevealCircle startValue,
                RevealCircle endValue) {
            float currentRadius = startValue.mRadius
                    + ((endValue.mRadius - startValue.mRadius) * fraction);
            if (mRevealCircle == null) {
                return new RevealCircle(currentRadius);
            } else {
                mRevealCircle.setRadius(currentRadius);
                return mRevealCircle;
    public void setIntValues(int... values) {
        throw new IllegalStateException("Cannot change the values of RevealAnimator");
    }

    @Override
    public void setObjectValues(Object... values) {
        throw new IllegalStateException("Cannot change the values of RevealAnimator");
    }

    @Override
    public void setEvaluator(TypeEvaluator value) {
        throw new IllegalStateException("Cannot change the evaluator of RevealAnimator");
    }
}
+41 −2
Original line number Diff line number Diff line
@@ -206,7 +206,7 @@ public class ValueAnimator extends Animator {
    /**
     * The set of listeners to be sent events through the life of an animation.
     */
    private ArrayList<AnimatorUpdateListener> mUpdateListeners = null;
    ArrayList<AnimatorUpdateListener> mUpdateListeners = null;

    /**
     * The property/value sets being animated.
@@ -1064,8 +1064,9 @@ public class ValueAnimator extends Animator {
    /**
     * Called internally to end an animation by removing it from the animations list. Must be
     * called on the UI thread.
     * @hide
     */
    private void endAnimation(AnimationHandler handler) {
    protected void endAnimation(AnimationHandler handler) {
        handler.mAnimations.remove(this);
        handler.mPendingAnimations.remove(this);
        handler.mDelayedAnims.remove(this);
@@ -1373,4 +1374,42 @@ public class ValueAnimator extends Animator {
        }
        return returnVal;
    }

    /**
     * <p>Whether or not the ValueAnimator is allowed to run asynchronously off of
     * the UI thread. This is a hint that informs the ValueAnimator that it is
     * OK to run the animation off-thread, however ValueAnimator may decide
     * that it must run the animation on the UI thread anyway. For example if there
     * is an {@link AnimatorUpdateListener} the animation will run on the UI thread,
     * regardless of the value of this hint.</p>
     *
     * <p>Regardless of whether or not the animation runs asynchronously, all
     * listener callbacks will be called on the UI thread.</p>
     *
     * <p>To be able to use this hint the following must be true:</p>
     * <ol>
     * <li>{@link #getAnimatedFraction()} is not needed (it will return undefined values).</li>
     * <li>The animator is immutable while {@link #isStarted()} is true. Requests
     *    to change values, duration, delay, etc... may be ignored.</li>
     * <li>Lifecycle callback events may be asynchronous. Events such as
     *    {@link Animator.AnimatorListener#onAnimationEnd(Animator)} or
     *    {@link Animator.AnimatorListener#onAnimationRepeat(Animator)} may end up delayed
     *    as they must be posted back to the UI thread, and any actions performed
     *    by those callbacks (such as starting new animations) will not happen
     *    in the same frame.</li>
     * <li>State change requests ({@link #cancel()}, {@link #end()}, {@link #reverse()}, etc...)
     *    may be asynchronous. It is guaranteed that all state changes that are
     *    performed on the UI thread in the same frame will be applied as a single
     *    atomic update, however that frame may be the current frame,
     *    the next frame, or some future frame. This will also impact the observed
     *    state of the Animator. For example, {@link #isStarted()} may still return true
     *    after a call to {@link #end()}. Using the lifecycle callbacks is preferred over
     *    queries to {@link #isStarted()}, {@link #isRunning()}, and {@link #isPaused()}
     *    for this reason.</li>
     * </ol>
     * @hide
     */
    public void setAllowRunningAsynchronously(boolean mayRunAsync) {
        // It is up to subclasses to support this, if they can.
    }
}
+18 −3
Original line number Diff line number Diff line
@@ -125,6 +125,12 @@ public class RenderNodeAnimator extends Animator {
                property.getNativeContainer(), paintField, finalValue));
    }

    public RenderNodeAnimator(int x, int y, boolean inverseClip,
            float startRadius, float endRadius) {
        init(nCreateRevealAnimator(new WeakReference<>(this),
                x, y, inverseClip, startRadius, endRadius));
    }

    private void init(long ptr) {
        mNativePtr = new VirtualRefBasePtr(ptr);
    }
@@ -190,7 +196,7 @@ public class RenderNodeAnimator extends Animator {
    @Override
    public void cancel() {
        if (!mFinished) {
            nCancel(mNativePtr.get());
            nEnd(mNativePtr.get());

            final ArrayList<AnimatorListener> listeners = getListeners();
            final int numListeners = listeners == null ? 0 : listeners.size();
@@ -202,7 +208,9 @@ public class RenderNodeAnimator extends Animator {

    @Override
    public void end() {
        throw new UnsupportedOperationException();
        if (!mFinished) {
            nEnd(mNativePtr.get());
        }
    }

    @Override
@@ -280,6 +288,11 @@ public class RenderNodeAnimator extends Animator {
        return mStarted && !mFinished;
    }

    @Override
    public boolean isStarted() {
        return mStarted;
    }

    @Override
    public void setInterpolator(TimeInterpolator interpolator) {
        checkMutable();
@@ -319,6 +332,8 @@ public class RenderNodeAnimator extends Animator {
            long canvasProperty, float finalValue);
    private static native long nCreateCanvasPropertyPaintAnimator(WeakReference<RenderNodeAnimator> weakThis,
            long canvasProperty, int paintField, float finalValue);
    private static native long nCreateRevealAnimator(WeakReference<RenderNodeAnimator> weakThis,
            int x, int y, boolean inverseClip, float startRadius, float endRadius);

    private static native void nSetStartValue(long nativePtr, float startValue);
    private static native void nSetDuration(long nativePtr, long duration);
@@ -328,5 +343,5 @@ public class RenderNodeAnimator extends Animator {
    private static native void nSetInterpolator(long animPtr, long interpolatorPtr);

    private static native void nStart(long animPtr);
    private static native void nCancel(long animPtr);
    private static native void nEnd(long animPtr);
}
+7 −0
Original line number Diff line number Diff line
@@ -31,6 +31,7 @@ public class RenderNodeAnimatorCompat extends RenderNodeAnimator {
    private long mStartDelay = 0;
    private long mStartTime;
    private boolean mCanceled;
    private boolean mStarted;

    public RenderNodeAnimatorCompat(int property, float finalValue) {
        super(property, finalValue);
@@ -49,6 +50,7 @@ public class RenderNodeAnimatorCompat extends RenderNodeAnimator {

    @Override
    public void start() {
        mStarted = true;
        if (mStartDelay <= 0) {
            doStart();
        } else {
@@ -56,6 +58,11 @@ public class RenderNodeAnimatorCompat extends RenderNodeAnimator {
        }
    }

    @Override
    public boolean isStarted() {
        return mStarted;
    }

    private void doStart() {
        if (!mCanceled) {
            super.start();
+2 −4
Original line number Diff line number Diff line
@@ -10724,8 +10724,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
     */
    public final ValueAnimator createClearCircleAnimator(int centerX,  int centerY,
            float startRadius, float endRadius) {
        return RevealAnimator.ofRevealCircle(this, centerX, centerY,
                startRadius, endRadius, true);
        return new RevealAnimator(this, centerX, centerY, startRadius, endRadius, true);
    }
    /**
@@ -10867,8 +10866,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
    public void setRevealClip(boolean shouldClip, boolean inverseClip,
            float x, float y, float radius) {
        mRenderNode.setRevealClip(shouldClip, inverseClip, x, y, radius);
        // TODO: Handle this invalidate in a better way, or purely in native.
        invalidate();
        invalidateViewProperty(false, false);
    }
    /**
Loading