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

Commit 61324dbf authored by George Mount's avatar George Mount
Browse files

Use spring animation for stretch overscroll

Bug: 171228096

Use the spring animation from the prototype in
EdgeEffect.

Test: manual testing for fling and recede

Change-Id: I7eec03a98f5d26153512ba402d6bb5bc5dc70ffb
parent af1e8b4e
Loading
Loading
Loading
Loading
+102 −37
Original line number Diff line number Diff line
@@ -76,6 +76,28 @@ public class EdgeEffect {
     */
    public static final int TYPE_STRETCH = 1;

    /**
     * The velocity threshold before the spring animation is considered settled.
     * The idea here is that velocity should be less than 1 pixel per frame (~16ms).
     */
    private static final double VELOCITY_THRESHOLD = 1.0 / 0.016;

    /**
     * The value threshold before the spring animation is considered close enough to
     * the destination to be settled. This should be around 1 pixel.
     */
    private static final double VALUE_THRESHOLD = 1;

    /**
     * The natural frequency of the stretch spring.
     */
    private static final double NATURAL_FREQUENCY = 14.4222;

    /**
     * The damping ratio of the stretch spring.
     */
    private static final double DAMPING_RATIO = 0.875;

    /** @hide */
    @IntDef({TYPE_GLOW, TYPE_STRETCH})
    @Retention(RetentionPolicy.SOURCE)
@@ -119,13 +141,12 @@ public class EdgeEffect {
    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
    private float mGlowScaleY;
    private float mDistance;
    private float mVelocity; // only for stretch animations

    private float mGlowAlphaStart;
    private float mGlowAlphaFinish;
    private float mGlowScaleYStart;
    private float mGlowScaleYFinish;
    private float mDistanceStart;
    private float mDistanceFinish;

    private long mStartTime;
    private float mDuration;
@@ -239,6 +260,8 @@ public class EdgeEffect {
     */
    public void finish() {
        mState = STATE_IDLE;
        mDistance = 0;
        mVelocity = 0;
    }

    /**
@@ -293,7 +316,8 @@ public class EdgeEffect {
        mDuration = PULL_TIME;

        mPullDistance += deltaDistance;
        mDistanceStart = mDistanceFinish = mDistance = Math.max(0f, mPullDistance);
        mDistance = Math.max(0f, mPullDistance);
        mVelocity = 0;

        final float absdd = Math.abs(deltaDistance);
        mGlowAlpha = mGlowAlphaStart = Math.min(MAX_ALPHA,
@@ -387,11 +411,10 @@ public class EdgeEffect {
        mState = STATE_RECEDE;
        mGlowAlphaStart = mGlowAlpha;
        mGlowScaleYStart = mGlowScaleY;
        mDistanceStart = mDistance;

        mGlowAlphaFinish = 0.f;
        mGlowScaleYFinish = 0.f;
        mDistanceFinish = 0.f;
        mVelocity = 0.f;

        mStartTime = AnimationUtils.currentAnimationTimeMillis();
        mDuration = RECEDE_TIME;
@@ -408,7 +431,14 @@ public class EdgeEffect {
     * @param velocity Velocity at impact in pixels per second.
     */
    public void onAbsorb(int velocity) {
        if (mEdgeEffectType == TYPE_STRETCH) {
            mState = STATE_RECEDE;
            mVelocity = velocity / mHeight;
            mDistance = 0;
            mStartTime = AnimationUtils.currentAnimationTimeMillis();
        } else {
            mState = STATE_ABSORB;
            mVelocity = 0;
            velocity = Math.min(Math.max(MIN_VELOCITY, Math.abs(velocity)), MAX_VELOCITY);

            mStartTime = AnimationUtils.currentAnimationTimeMillis();
@@ -418,20 +448,19 @@ public class EdgeEffect {
            // nearly invisible.
            mGlowAlphaStart = GLOW_ALPHA_START;
            mGlowScaleYStart = Math.max(mGlowScaleY, 0.f);
        mDistanceStart = mDistance;

            // Growth for the size of the glow should be quadratic to properly
            // respond
            // to a user's scrolling speed. The faster the scrolling speed, the more
            // intense the effect should be for both the size and the saturation.
        mGlowScaleYFinish = Math.min(0.025f + (velocity * (velocity / 100) * 0.00015f) / 2, 1.f);
            mGlowScaleYFinish = Math.min(0.025f + (velocity * (velocity / 100) * 0.00015f) / 2,
                    1.f);
            // Alpha should change for the glow as well as size.
            mGlowAlphaFinish = Math.max(
                mGlowAlphaStart, Math.min(velocity * VELOCITY_GLOW_FACTOR * .00001f, MAX_ALPHA));
                    mGlowAlphaStart,
                    Math.min(velocity * VELOCITY_GLOW_FACTOR * .00001f, MAX_ALPHA));
            mTargetDisplacement = 0.5f;

        // Use glow values to estimate the absorption for stretch distance.
        mDistanceFinish = calculateDistanceFromGlowValues(mGlowScaleYFinish, mGlowAlphaFinish);
        }
    }

    /**
@@ -539,8 +568,8 @@ public class EdgeEffect {
            canvas.drawCircle(centerX, centerY, mRadius, mPaint);
            canvas.restoreToCount(count);
        } else if (canvas instanceof RecordingCanvas) {
            if (mState != STATE_PULL) {
                update();
            if (mState == STATE_RECEDE) {
                updateSpring();
            }
            RecordingCanvas recordingCanvas = (RecordingCanvas) canvas;
            if (mTmpMatrix == null) {
@@ -597,7 +626,7 @@ public class EdgeEffect {
        }

        boolean oneLastFrame = false;
        if (mState == STATE_RECEDE && mDistance == 0) {
        if (mState == STATE_RECEDE && mDistance == 0 && mVelocity == 0) {
            mState = STATE_IDLE;
            oneLastFrame = true;
        }
@@ -634,7 +663,7 @@ public class EdgeEffect {

        mGlowAlpha = mGlowAlphaStart + (mGlowAlphaFinish - mGlowAlphaStart) * interp;
        mGlowScaleY = mGlowScaleYStart + (mGlowScaleYFinish - mGlowScaleYStart) * interp;
        mDistance = mDistanceStart + (mDistanceFinish - mDistanceStart) * interp;
        mDistance = calculateDistanceFromGlowValues(mGlowScaleY, mGlowAlpha);
        mDisplacement = (mDisplacement + mTargetDisplacement) / 2;

        if (t >= 1.f - EPSILON) {
@@ -646,12 +675,10 @@ public class EdgeEffect {

                    mGlowAlphaStart = mGlowAlpha;
                    mGlowScaleYStart = mGlowScaleY;
                    mDistanceStart = mDistance;

                    // After absorb, the glow should fade to nothing.
                    mGlowAlphaFinish = 0.f;
                    mGlowScaleYFinish = 0.f;
                    mDistanceFinish = 0.f;
                    break;
                case STATE_PULL:
                    mState = STATE_PULL_DECAY;
@@ -660,12 +687,10 @@ public class EdgeEffect {

                    mGlowAlphaStart = mGlowAlpha;
                    mGlowScaleYStart = mGlowScaleY;
                    mDistanceStart = mDistance;

                    // After pull, the glow should fade to nothing.
                    mGlowAlphaFinish = 0.f;
                    mGlowScaleYFinish = 0.f;
                    mDistanceFinish = 0.f;
                    break;
                case STATE_PULL_DECAY:
                    mState = STATE_RECEDE;
@@ -677,6 +702,35 @@ public class EdgeEffect {
        }
    }

    private void updateSpring() {
        final long time = AnimationUtils.currentAnimationTimeMillis();
        final float deltaT = (time - mStartTime) / 1000f; // Convert from millis to seconds
        if (deltaT < 0.001f) {
            return; // Must have at least 1 ms difference
        }
        final double mDampedFreq = NATURAL_FREQUENCY * Math.sqrt(1 - DAMPING_RATIO * DAMPING_RATIO);

        // We're always underdamped, so we can use only those equations:
        double cosCoeff = mDistance;
        double sinCoeff = (1 / mDampedFreq) * (DAMPING_RATIO * NATURAL_FREQUENCY
                * mDistance + mVelocity);
        double distance = Math.pow(Math.E, -DAMPING_RATIO * NATURAL_FREQUENCY * deltaT)
                * (cosCoeff * Math.cos(mDampedFreq * deltaT)
                + sinCoeff * Math.sin(mDampedFreq * deltaT));
        double velocity = distance * (-NATURAL_FREQUENCY) * DAMPING_RATIO
                + Math.pow(Math.E, -DAMPING_RATIO * NATURAL_FREQUENCY * deltaT)
                * (-mDampedFreq * cosCoeff * Math.sin(mDampedFreq * deltaT)
                + mDampedFreq * sinCoeff * Math.cos(mDampedFreq * deltaT));
        mDistance = (float) distance;
        mVelocity = (float) velocity;
        mStartTime = time;
        if (isAtEquilibrium()) {
            mState = STATE_IDLE;
            mDistance = 0;
            mVelocity = 0;
        }
    }

    /**
     * @return The estimated pull distance as calculated from mGlowScaleY.
     */
@@ -692,4 +746,15 @@ public class EdgeEffect {
        }
        return alpha / PULL_DISTANCE_ALPHA_GLOW_FACTOR;
    }

    /**
     * @return true if the spring used for calculating the stretch animation is
     * considered at rest or false if it is still animating.
     */
    private boolean isAtEquilibrium() {
        double velocity = mVelocity * mHeight; // in pixels/second
        double displacement = mDistance * mHeight; // in pixels
        return Math.abs(velocity) < VELOCITY_THRESHOLD
                && Math.abs(displacement) < VALUE_THRESHOLD;
    }
}