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

Commit ad019a24 authored by Alan Viverette's avatar Alan Viverette Committed by Android (Google) Code Review
Browse files

Merge "Implement bounded ripple animation"

parents 94ac7556 6a67db41
Loading
Loading
Loading
Loading
+4 −10
Original line number Diff line number Diff line
@@ -69,7 +69,6 @@ abstract class RippleComponent {

        mDensity = density;

        onSetup();
        onTargetRadiusChanged(mTargetRadius);
    }

@@ -82,8 +81,11 @@ abstract class RippleComponent {
        cancel();

        mSoftwareAnimator = createSoftwareEnter(fast);

        if (mSoftwareAnimator != null) {
            mSoftwareAnimator.start();
        }
    }

    /**
     * Starts a ripple exit animation.
@@ -250,14 +252,6 @@ abstract class RippleComponent {
        // Stub.
    }

    /**
     * Called during ripple setup, which occurs before the first enter
     * animation.
     */
    protected void onSetup() {
        // Stub.
    }

    protected abstract Animator createSoftwareEnter(boolean fast);

    protected abstract Animator createSoftwareExit();
+3 −1
Original line number Diff line number Diff line
@@ -564,7 +564,9 @@ public class RippleDrawable extends LayerDrawable {
                x = mHotspotBounds.exactCenterX();
                y = mHotspotBounds.exactCenterY();
            }
            mRipple = new RippleForeground(this, mHotspotBounds, x, y);

            final boolean isBounded = !isProjected();
            mRipple = new RippleForeground(this, mHotspotBounds, x, y, isBounded);
        }

        mRipple.setup(mState.mMaxRadius, mDensity);
+137 −48
Original line number Diff line number Diff line
@@ -44,9 +44,16 @@ class RippleForeground extends RippleComponent {
    private static final float WAVE_TOUCH_UP_ACCELERATION = 3400;
    private static final float WAVE_OPACITY_DECAY_VELOCITY = 3;

    // Bounded ripple animation properties.
    private static final int BOUNDED_ORIGIN_EXIT_DURATION = 300;
    private static final int BOUNDED_RADIUS_EXIT_DURATION = 800;
    private static final int BOUNDED_OPACITY_EXIT_DURATION = 400;
    private static final float MAX_BOUNDED_RADIUS = 350;

    private static final int RIPPLE_ENTER_DELAY = 80;
    private static final int OPACITY_ENTER_DURATION_FAST = 120;

    // Parent-relative values for starting position.
    private float mStartingX;
    private float mStartingY;
    private float mClampedStartingX;
@@ -58,30 +65,41 @@ class RippleForeground extends RippleComponent {
    private CanvasProperty<Float> mPropX;
    private CanvasProperty<Float> mPropY;

    // Target values for tween animations.
    private float mTargetX = 0;
    private float mTargetY = 0;

    /** Ripple target radius used when bounded. Not used for clamping. */
    private float mBoundedRadius = 0;

    // Software rendering properties.
    private float mOpacity = 1;
    private float mOuterX;
    private float mOuterY;

    // Values used to tween between the start and end positions.
    private float mTweenRadius = 0;
    private float mTweenX = 0;
    private float mTweenY = 0;

    /** Whether this ripple is bounded. */
    private boolean mIsBounded;

    /** Whether this ripple has finished its exit animation. */
    private boolean mHasFinishedExit;

    public RippleForeground(RippleDrawable owner, Rect bounds, float startingX, float startingY) {
    public RippleForeground(RippleDrawable owner, Rect bounds, float startingX, float startingY,
            boolean isBounded) {
        super(owner, bounds);

        mIsBounded = isBounded;
        mStartingX = startingX;
        mStartingY = startingY;
    }

    @Override
    public void onSetup() {
        mOuterX = 0;
        mOuterY = 0;
        if (isBounded) {
            mBoundedRadius = MAX_BOUNDED_RADIUS * 0.9f
                    + (float) (MAX_BOUNDED_RADIUS * Math.random() * 0.1);
        } else {
            mBoundedRadius = 0;
        }
    }

    @Override
@@ -95,12 +113,10 @@ class RippleForeground extends RippleComponent {

        final int origAlpha = p.getAlpha();
        final int alpha = (int) (origAlpha * mOpacity + 0.5f);
        final float radius = MathUtils.lerp(0, mTargetRadius, mTweenRadius);
        final float radius = getCurrentRadius();
        if (alpha > 0 && radius > 0) {
            final float x = MathUtils.lerp(
                    mClampedStartingX - mBounds.exactCenterX(), mOuterX, mTweenX);
            final float y = MathUtils.lerp(
                    mClampedStartingY - mBounds.exactCenterY(), mOuterY, mTweenY);
            final float x = getCurrentX();
            final float y = getCurrentY();
            p.setAlpha(alpha);
            c.drawCircle(x, y, radius, p);
            p.setAlpha(origAlpha);
@@ -120,8 +136,8 @@ class RippleForeground extends RippleComponent {
     * Returns the maximum bounds of the ripple relative to the ripple center.
     */
    public void getBounds(Rect bounds) {
        final int outerX = (int) mOuterX;
        final int outerY = (int) mOuterY;
        final int outerX = (int) mTargetX;
        final int outerY = (int) mTargetY;
        final int r = (int) mTargetRadius + 1;
        bounds.set(outerX - r, outerY - r, outerX + r, outerY + r);
    }
@@ -146,14 +162,25 @@ class RippleForeground extends RippleComponent {

    @Override
    protected Animator createSoftwareEnter(boolean fast) {
        // Bounded ripples don't have enter animations.
        if (mIsBounded) {
            return null;
        }

        final int duration = (int)
                (1000 * Math.sqrt(mTargetRadius / WAVE_TOUCH_DOWN_ACCELERATION * mDensity) + 0.5);

        final ObjectAnimator tweenAll = ObjectAnimator.ofFloat(this, TWEEN_ALL, 1);
        tweenAll.setAutoCancel(true);
        tweenAll.setDuration(duration);
        tweenAll.setInterpolator(LINEAR_INTERPOLATOR);
        tweenAll.setStartDelay(RIPPLE_ENTER_DELAY);
        final ObjectAnimator tweenRadius = ObjectAnimator.ofFloat(this, TWEEN_RADIUS, 1);
        tweenRadius.setAutoCancel(true);
        tweenRadius.setDuration(duration);
        tweenRadius.setInterpolator(LINEAR_INTERPOLATOR);
        tweenRadius.setStartDelay(RIPPLE_ENTER_DELAY);

        final ObjectAnimator tweenOrigin = ObjectAnimator.ofFloat(this, TWEEN_ORIGIN, 1);
        tweenOrigin.setAutoCancel(true);
        tweenOrigin.setDuration(duration);
        tweenOrigin.setInterpolator(LINEAR_INTERPOLATOR);
        tweenOrigin.setStartDelay(RIPPLE_ENTER_DELAY);

        final ObjectAnimator opacity = ObjectAnimator.ofFloat(this, OPACITY, 1);
        opacity.setAutoCancel(true);
@@ -161,31 +188,68 @@ class RippleForeground extends RippleComponent {
        opacity.setInterpolator(LINEAR_INTERPOLATOR);

        final AnimatorSet set = new AnimatorSet();
        set.play(tweenAll).with(opacity);
        set.play(tweenOrigin).with(tweenRadius).with(opacity);

        return set;
    }

    private float getCurrentX() {
        return MathUtils.lerp(mClampedStartingX - mBounds.exactCenterX(), mTargetX, mTweenX);
    }

    private float getCurrentY() {
        return MathUtils.lerp(mClampedStartingY - mBounds.exactCenterY(), mTargetY, mTweenY);
    }

    private int getRadiusExitDuration() {
        final float radius = MathUtils.lerp(0, mTargetRadius, mTweenRadius);
        final float remaining = mTargetRadius - radius;
        return (int) (1000 * Math.sqrt(remaining / (WAVE_TOUCH_UP_ACCELERATION
        final float remainingRadius = mTargetRadius - getCurrentRadius();
        return (int) (1000 * Math.sqrt(remainingRadius / (WAVE_TOUCH_UP_ACCELERATION
                + WAVE_TOUCH_DOWN_ACCELERATION) * mDensity) + 0.5);
    }

    private float getCurrentRadius() {
        return MathUtils.lerp(0, mTargetRadius, mTweenRadius);
    }

    private int getOpacityExitDuration() {
        return (int) (1000 * mOpacity / WAVE_OPACITY_DECAY_VELOCITY + 0.5f);
    }

    /**
     * Compute target values that are dependent on bounding.
     */
    private void computeBoundedTargetValues() {
        mTargetX = (mClampedStartingX - mBounds.exactCenterX()) * .7f;
        mTargetY = (mClampedStartingY - mBounds.exactCenterY()) * .7f;
        mTargetRadius = mBoundedRadius;
    }

    @Override
    protected Animator createSoftwareExit() {
        final int radiusDuration = getRadiusExitDuration();
        final int opacityDuration = getOpacityExitDuration();
        final int radiusDuration;
        final int originDuration;
        final int opacityDuration;
        if (mIsBounded) {
            computeBoundedTargetValues();

            radiusDuration = BOUNDED_RADIUS_EXIT_DURATION;
            originDuration = BOUNDED_ORIGIN_EXIT_DURATION;
            opacityDuration = BOUNDED_OPACITY_EXIT_DURATION;
        } else {
            radiusDuration = getRadiusExitDuration();
            originDuration = radiusDuration;
            opacityDuration = getOpacityExitDuration();
        }

        final ObjectAnimator tweenAll = ObjectAnimator.ofFloat(this, TWEEN_ALL, 1);
        tweenAll.setAutoCancel(true);
        tweenAll.setDuration(radiusDuration);
        tweenAll.setInterpolator(DECELERATE_INTERPOLATOR);
        final ObjectAnimator tweenRadius = ObjectAnimator.ofFloat(this, TWEEN_RADIUS, 1);
        tweenRadius.setAutoCancel(true);
        tweenRadius.setDuration(radiusDuration);
        tweenRadius.setInterpolator(DECELERATE_INTERPOLATOR);

        final ObjectAnimator tweenOrigin = ObjectAnimator.ofFloat(this, TWEEN_ORIGIN, 1);
        tweenOrigin.setAutoCancel(true);
        tweenOrigin.setDuration(originDuration);
        tweenOrigin.setInterpolator(DECELERATE_INTERPOLATOR);

        final ObjectAnimator opacity = ObjectAnimator.ofFloat(this, OPACITY, 0);
        opacity.setAutoCancel(true);
@@ -193,7 +257,7 @@ class RippleForeground extends RippleComponent {
        opacity.setInterpolator(LINEAR_INTERPOLATOR);

        final AnimatorSet set = new AnimatorSet();
        set.play(tweenAll).with(opacity);
        set.play(tweenOrigin).with(tweenRadius).with(opacity);
        set.addListener(mAnimationListener);

        return set;
@@ -201,15 +265,25 @@ class RippleForeground extends RippleComponent {

    @Override
    protected RenderNodeAnimatorSet createHardwareExit(Paint p) {
        final int radiusDuration = getRadiusExitDuration();
        final int opacityDuration = getOpacityExitDuration();
        final int radiusDuration;
        final int originDuration;
        final int opacityDuration;
        if (mIsBounded) {
            computeBoundedTargetValues();

            radiusDuration = BOUNDED_RADIUS_EXIT_DURATION;
            originDuration = BOUNDED_ORIGIN_EXIT_DURATION;
            opacityDuration = BOUNDED_OPACITY_EXIT_DURATION;
        } else {
            radiusDuration = getRadiusExitDuration();
            originDuration = radiusDuration;
            opacityDuration = getOpacityExitDuration();
        }

        final float startX = MathUtils.lerp(
                mClampedStartingX - mBounds.exactCenterX(), mOuterX, mTweenX);
        final float startY = MathUtils.lerp(
                mClampedStartingY - mBounds.exactCenterY(), mOuterY, mTweenY);
        final float startX = getCurrentX();
        final float startY = getCurrentY();
        final float startRadius = getCurrentRadius();

        final float startRadius = MathUtils.lerp(0, mTargetRadius, mTweenRadius);
        p.setAlpha((int) (p.getAlpha() * mOpacity + 0.5f));

        mPropPaint = CanvasProperty.createPaint(p);
@@ -221,12 +295,12 @@ class RippleForeground extends RippleComponent {
        radius.setDuration(radiusDuration);
        radius.setInterpolator(DECELERATE_INTERPOLATOR);

        final RenderNodeAnimator x = new RenderNodeAnimator(mPropX, mOuterX);
        x.setDuration(radiusDuration);
        final RenderNodeAnimator x = new RenderNodeAnimator(mPropX, mTargetX);
        x.setDuration(originDuration);
        x.setInterpolator(DECELERATE_INTERPOLATOR);

        final RenderNodeAnimator y = new RenderNodeAnimator(mPropY, mOuterY);
        y.setDuration(radiusDuration);
        final RenderNodeAnimator y = new RenderNodeAnimator(mPropY, mTargetY);
        y.setDuration(originDuration);
        y.setInterpolator(DECELERATE_INTERPOLATOR);

        final RenderNodeAnimator opacity = new RenderNodeAnimator(mPropPaint,
@@ -307,14 +381,29 @@ class RippleForeground extends RippleComponent {
    }

    /**
     * Property for animating radius, center X, and center Y between their
     * initial and target values.
     * Property for animating radius between its initial and target values.
     */
    private static final FloatProperty<RippleForeground> TWEEN_ALL =
            new FloatProperty<RippleForeground>("tweenAll") {
    private static final FloatProperty<RippleForeground> TWEEN_RADIUS =
            new FloatProperty<RippleForeground>("tweenRadius") {
        @Override
        public void setValue(RippleForeground object, float value) {
            object.mTweenRadius = value;
            object.invalidateSelf();
        }

        @Override
        public Float get(RippleForeground object) {
            return object.mTweenRadius;
        }
    };

    /**
     * Property for animating origin between its initial and target values.
     */
    private static final FloatProperty<RippleForeground> TWEEN_ORIGIN =
            new FloatProperty<RippleForeground>("tweenOrigin") {
                @Override
                public void setValue(RippleForeground object, float value) {
                    object.mTweenX = value;
                    object.mTweenY = value;
                    object.invalidateSelf();
@@ -322,7 +411,7 @@ class RippleForeground extends RippleComponent {

                @Override
                public Float get(RippleForeground object) {
            return object.mTweenRadius;
                    return object.mTweenX;
                }
            };