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 Original line Diff line number Diff line
@@ -76,6 +76,28 @@ public class EdgeEffect {
     */
     */
    public static final int TYPE_STRETCH = 1;
    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 */
    /** @hide */
    @IntDef({TYPE_GLOW, TYPE_STRETCH})
    @IntDef({TYPE_GLOW, TYPE_STRETCH})
    @Retention(RetentionPolicy.SOURCE)
    @Retention(RetentionPolicy.SOURCE)
@@ -119,13 +141,12 @@ public class EdgeEffect {
    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
    private float mGlowScaleY;
    private float mGlowScaleY;
    private float mDistance;
    private float mDistance;
    private float mVelocity; // only for stretch animations


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


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


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


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


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


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


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


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


            // Growth for the size of the glow should be quadratic to properly
            // Growth for the size of the glow should be quadratic to properly
            // respond
            // respond
            // to a user's scrolling speed. The faster the scrolling speed, the more
            // 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.
            // 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.
            // Alpha should change for the glow as well as size.
            mGlowAlphaFinish = Math.max(
            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;
            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.drawCircle(centerX, centerY, mRadius, mPaint);
            canvas.restoreToCount(count);
            canvas.restoreToCount(count);
        } else if (canvas instanceof RecordingCanvas) {
        } else if (canvas instanceof RecordingCanvas) {
            if (mState != STATE_PULL) {
            if (mState == STATE_RECEDE) {
                update();
                updateSpring();
            }
            }
            RecordingCanvas recordingCanvas = (RecordingCanvas) canvas;
            RecordingCanvas recordingCanvas = (RecordingCanvas) canvas;
            if (mTmpMatrix == null) {
            if (mTmpMatrix == null) {
@@ -597,7 +626,7 @@ public class EdgeEffect {
        }
        }


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


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


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


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


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


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


                    // After pull, the glow should fade to nothing.
                    // After pull, the glow should fade to nothing.
                    mGlowAlphaFinish = 0.f;
                    mGlowAlphaFinish = 0.f;
                    mGlowScaleYFinish = 0.f;
                    mGlowScaleYFinish = 0.f;
                    mDistanceFinish = 0.f;
                    break;
                    break;
                case STATE_PULL_DECAY:
                case STATE_PULL_DECAY:
                    mState = STATE_RECEDE;
                    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.
     * @return The estimated pull distance as calculated from mGlowScaleY.
     */
     */
@@ -692,4 +746,15 @@ public class EdgeEffect {
        }
        }
        return alpha / PULL_DISTANCE_ALPHA_GLOW_FACTOR;
        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;
    }
}
}