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

Commit 6a67db41 authored by Alan Viverette's avatar Alan Viverette
Browse files

Implement bounded ripple animation

Bug: 19431322
Change-Id: I5dc1a28d8675cc6fb036b815d6227113c3f1aa4b
parent 53776a2b
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;
                }
            };