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

Commit fff4ab09 authored by Gilles Debunne's avatar Gilles Debunne
Browse files

Refactored OverScroller

Restored the interpolator and a constructor with 4 parameters.

New spline coefficients, spline computation moved to MagneticOverScroller, which has been
renamed SplineOverScroller.

Change-Id: If1ab2653bb998600e9c5d6f46dfd6cd30fa44efc
parent 12df6f72
Loading
Loading
Loading
Loading
+29 −1
Original line number Original line Diff line number Diff line
@@ -238771,7 +238771,35 @@
>
>
<parameter name="context" type="android.content.Context">
<parameter name="context" type="android.content.Context">
</parameter>
</parameter>
<parameter name="interpolator" type="android.graphics.Interpolator">
<parameter name="interpolator" type="android.view.animation.Interpolator">
</parameter>
</constructor>
<constructor name="OverScroller"
 type="android.widget.OverScroller"
 static="false"
 final="false"
 deprecated="not deprecated"
 visibility="public"
>
<parameter name="context" type="android.content.Context">
</parameter>
<parameter name="interpolator" type="android.view.animation.Interpolator">
</parameter>
<parameter name="bounceCoefficientX" type="float">
</parameter>
<parameter name="bounceCoefficientY" type="float">
</parameter>
</constructor>
<constructor name="OverScroller"
 type="android.widget.OverScroller"
 static="false"
 final="false"
 deprecated="not deprecated"
 visibility="public"
>
<parameter name="context" type="android.content.Context">
</parameter>
<parameter name="interpolator" type="android.view.animation.Interpolator">
</parameter>
</parameter>
<parameter name="bounceCoefficientX" type="float">
<parameter name="bounceCoefficientX" type="float">
</parameter>
</parameter>
+1 −1
Original line number Original line Diff line number Diff line
@@ -166,7 +166,7 @@ public class ViewConfiguration {
    /**
    /**
     * Max distance to overfling for edge effects
     * Max distance to overfling for edge effects
     */
     */
    private static final int OVERFLING_DISTANCE = 4;
    private static final int OVERFLING_DISTANCE = 12;


    private final int mEdgeSlop;
    private final int mEdgeSlop;
    private final int mFadingEdgeLength;
    private final int mFadingEdgeLength;
+176 −107
Original line number Original line Diff line number Diff line
@@ -17,9 +17,12 @@
package android.widget;
package android.widget;


import android.content.Context;
import android.content.Context;
import android.graphics.Interpolator;
import android.hardware.SensorManager;
import android.util.FloatMath;
import android.util.Log;
import android.view.ViewConfiguration;
import android.view.ViewConfiguration;
import android.view.animation.AnimationUtils;
import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;


/**
/**
 * This class encapsulates scrolling with the ability to overshoot the bounds
 * This class encapsulates scrolling with the ability to overshoot the bounds
@@ -27,65 +30,51 @@ import android.view.animation.AnimationUtils;
 * {@link android.widget.Scroller} in most cases.
 * {@link android.widget.Scroller} in most cases.
 */
 */
public class OverScroller {
public class OverScroller {
    int mMode;
    private int mMode;


    private final MagneticOverScroller mScrollerX;
    private final SplineOverScroller mScrollerX;
    private final MagneticOverScroller mScrollerY;
    private final SplineOverScroller mScrollerY;


    private float mDeceleration;
    private final Interpolator mInterpolator;
    private final float mPpi;
    private final boolean mFlywheel;


    private static float DECELERATION_RATE = (float) (Math.log(0.75) / Math.log(0.9));
    private final boolean mFlywheel;
    private static float ALPHA = 800; // pixels / seconds
    private static float START_TENSION = 0.4f; // Tension at start: (0.4 * total T, 1.0 * Distance)
    private static float END_TENSION = 1.0f - START_TENSION;
    private static final int NB_SAMPLES = 100;
    private static final float[] SPLINE_POSITION = new float[NB_SAMPLES + 1];
    private static final float[] SPLINE_TIME = new float[NB_SAMPLES + 1];


    private static final int DEFAULT_DURATION = 250;
    private static final int DEFAULT_DURATION = 250;
    private static final int SCROLL_MODE = 0;
    private static final int SCROLL_MODE = 0;
    private static final int FLING_MODE = 1;
    private static final int FLING_MODE = 1;


    static {
    /**
        float x_min = 0.0f;
     * Creates an OverScroller with a viscous fluid scroll interpolator.
        float y_min = 0.0f;
     * @param context
        for (int i = 0; i < NB_SAMPLES; i++) {
     */
            final float alpha = (float) i / NB_SAMPLES;
    public OverScroller(Context context) {
            {
        this(context, null);
                float x_max = 1.0f;
                float x, tx, coef;
                while (true) {
                    x = x_min + (x_max - x_min) / 2.0f;
                    coef = 3.0f * x * (1.0f - x);
                    tx = coef * ((1.0f - x) * START_TENSION + x * END_TENSION) + x * x * x;
                    if (Math.abs(tx - alpha) < 1E-5) break;
                    if (tx > alpha) x_max = x;
                    else x_min = x;
                }
                SPLINE_POSITION[i] = coef + x * x * x;
    }
    }


            {
    /**
                float y_max = 1.0f;
     * Creates an OverScroller with default edge bounce coefficients and flywheel enabled.
                float y, dy, coef;
     * @param context The context of this application.
                while (true) {
     * @param interpolator The scroll interpolator. If null, a default (viscous) interpolator will
                    y = y_min + (y_max - y_min) / 2.0f;
     * be used.
                    coef = 3.0f * y * (1.0f - y);
     */
                    dy = coef + y * y * y;
    public OverScroller(Context context, Interpolator interpolator) {
                    if (Math.abs(dy - alpha) < 1E-5) break;
        this(context, interpolator, SplineOverScroller.DEFAULT_BOUNCE_COEFFICIENT,
                    if (dy > alpha) y_max = y;
                SplineOverScroller.DEFAULT_BOUNCE_COEFFICIENT);
                    else y_min = y;
                }
                SPLINE_TIME[i] = coef * ((1.0f - y) * START_TENSION + y * END_TENSION) + y * y * y;
            }
        }
        SPLINE_POSITION[NB_SAMPLES] = SPLINE_TIME[NB_SAMPLES] = 1.0f;
    }
    }


    public OverScroller(Context context) {
    /**
        this(context, null, 0.f, 0.f, true);
     * Creates an OverScroller with flywheel enabled.
     * @param context The context of this application.
     * @param interpolator The scroll interpolator. If null, a default (viscous) interpolator will
     * be used.
     * @param bounceCoefficientX A value between 0 and 1 that will determine the proportion of the
     * velocity which is preserved in the bounce when the horizontal edge is reached. A null value
     * means no bounce.
     * @param bounceCoefficientY Same as bounceCoefficientX but for the vertical direction.
     */
    public OverScroller(Context context, Interpolator interpolator,
            float bounceCoefficientX, float bounceCoefficientY) {
        this(context, interpolator, bounceCoefficientX, bounceCoefficientY, true);
    }
    }


    /**
    /**
@@ -97,20 +86,21 @@ public class OverScroller {
     * velocity which is preserved in the bounce when the horizontal edge is reached. A null value
     * velocity which is preserved in the bounce when the horizontal edge is reached. A null value
     * means no bounce.
     * means no bounce.
     * @param bounceCoefficientY Same as bounceCoefficientX but for the vertical direction.
     * @param bounceCoefficientY Same as bounceCoefficientX but for the vertical direction.
     * @param flywheel If true, successive fling motions will keep on increasing scroll speed.
     */
     */
    public OverScroller(Context context, Interpolator interpolator,
    public OverScroller(Context context, Interpolator interpolator,
            float bounceCoefficientX, float bounceCoefficientY, boolean flywheel) {
            float bounceCoefficientX, float bounceCoefficientY, boolean flywheel) {
        mInterpolator = interpolator;
        mFlywheel = flywheel;
        mFlywheel = flywheel;
        mPpi = context.getResources().getDisplayMetrics().density * 160.0f;
        mScrollerX = new SplineOverScroller();
        mDeceleration = computeDeceleration(ViewConfiguration.getScrollFriction());
        mScrollerY = new SplineOverScroller();
        mScrollerX = new MagneticOverScroller();

        mScrollerY = new MagneticOverScroller();
        SplineOverScroller.initFromContext(context);


        mScrollerX.setBounceCoefficient(bounceCoefficientX);
        mScrollerX.setBounceCoefficient(bounceCoefficientX);
        mScrollerY.setBounceCoefficient(bounceCoefficientY);
        mScrollerY.setBounceCoefficient(bounceCoefficientY);
    }
    }



    /**
    /**
     * The amount of friction applied to flings. The default value
     * The amount of friction applied to flings. The default value
     * is {@link ViewConfiguration#getScrollFriction}.
     * is {@link ViewConfiguration#getScrollFriction}.
@@ -119,14 +109,8 @@ public class OverScroller {
     *         friction.
     *         friction.
     */
     */
    public final void setFriction(float friction) {
    public final void setFriction(float friction) {
        mDeceleration = computeDeceleration(friction);
        mScrollerX.setFriction(friction);
    }
        mScrollerY.setFriction(friction);

    private float computeDeceleration(float friction) {
        return 9.81f   // g (m/s^2)
        * 39.37f               // inch/meter
        * mPpi                 // pixels per inch
        * friction;
    }
    }


    /**
    /**
@@ -178,7 +162,7 @@ public class OverScroller {
    public float getCurrVelocity() {
    public float getCurrVelocity() {
        float squaredNorm = mScrollerX.mCurrVelocity * mScrollerX.mCurrVelocity;
        float squaredNorm = mScrollerX.mCurrVelocity * mScrollerX.mCurrVelocity;
        squaredNorm += mScrollerY.mCurrVelocity * mScrollerY.mCurrVelocity;
        squaredNorm += mScrollerY.mCurrVelocity * mScrollerY.mCurrVelocity;
        return (float) Math.sqrt(squaredNorm);
        return FloatMath.sqrt(squaredNorm);
    }
    }


    /**
    /**
@@ -307,7 +291,11 @@ public class OverScroller {
                if (elapsedTime < duration) {
                if (elapsedTime < duration) {
                    float q = (float) (elapsedTime) / duration;
                    float q = (float) (elapsedTime) / duration;


                    if (mInterpolator == null) {
                        q = Scroller.viscousFluid(q);
                        q = Scroller.viscousFluid(q);
                    } else {
                        q = mInterpolator.getInterpolation(q);
                    }


                    mScrollerX.updateScroll(q);
                    mScrollerX.updateScroll(q);
                    mScrollerY.updateScroll(q);
                    mScrollerY.updateScroll(q);
@@ -496,9 +484,9 @@ public class OverScroller {
     */
     */
    public boolean isOverScrolled() {
    public boolean isOverScrolled() {
        return ((!mScrollerX.mFinished &&
        return ((!mScrollerX.mFinished &&
                mScrollerX.mState != MagneticOverScroller.TO_EDGE) ||
                mScrollerX.mState != SplineOverScroller.TO_EDGE) ||
                (!mScrollerY.mFinished &&
                (!mScrollerY.mFinished &&
                        mScrollerY.mState != MagneticOverScroller.TO_EDGE));
                        mScrollerY.mState != SplineOverScroller.TO_EDGE));
    }
    }


    /**
    /**
@@ -536,60 +524,126 @@ public class OverScroller {
        Math.signum(yvel) == Math.signum(dy);
        Math.signum(yvel) == Math.signum(dy);
    }
    }


    class MagneticOverScroller {
    static class SplineOverScroller {
        // Initial position
        // Initial position
        int mStart;
        private int mStart;


        // Current position
        // Current position
        int mCurrentPosition;
        private int mCurrentPosition;


        // Final position
        // Final position
        int mFinal;
        private int mFinal;


        // Initial velocity
        // Initial velocity
        int mVelocity;
        private int mVelocity;


        // Current velocity
        // Current velocity
        float mCurrVelocity;
        private float mCurrVelocity;


        // Constant current deceleration
        // Constant current deceleration
        float mDeceleration;
        private float mDeceleration;


        // Animation starting time, in system milliseconds
        // Animation starting time, in system milliseconds
        long mStartTime;
        private long mStartTime;


        // Animation duration, in milliseconds
        // Animation duration, in milliseconds
        int mDuration;
        private int mDuration;


        // Duration to complete spline component of animation
        // Duration to complete spline component of animation
        int mSplineDuration;
        private int mSplineDuration;


        // Distance to travel along spline animation
        // Distance to travel along spline animation
        int mSplineDistance;
        private int mSplineDistance;


        // Whether the animation is currently in progress
        // Whether the animation is currently in progress
        boolean mFinished;
        private boolean mFinished;


        private static final int TO_EDGE = 0;
        // The allowed overshot distance before boundary is reached.
        private static final int TO_BOUNDARY = 1;
        private int mOver;
        private static final int TO_BOUNCE = 2;

        // Fling friction
        private float mFlingFriction = ViewConfiguration.getScrollFriction();


        // Proportion of velocity preserved at the end of a bounce animation.
        private float mBounceCoefficient = DEFAULT_BOUNCE_COEFFICIENT;

        // Current state of the animation.
        private int mState = TO_EDGE;
        private int mState = TO_EDGE;


        // The allowed overshot distance before boundary is reached.
        // Constant gravity value, used in the deceleration phase.
        private int mOver;
        private static final float GRAVITY = 2000.0f;

        // A device specific coefficient adjusted to physical values.
        private static float PHYSICAL_COEF;

        private static float DECELERATION_RATE = (float) (Math.log(0.75) / Math.log(0.9));
        private static final float INFLEXION = 0.3f; // Tension lines cross at (INFLEXION, 1)
        private static final float START_TENSION = 0.7f;
        private static final float END_TENSION = 0.8f;
        private static final float P1 = START_TENSION * INFLEXION;
        private static final float P2 = 1.0f - END_TENSION * (1.0f - INFLEXION);

        private static final int NB_SAMPLES = 100;
        private static final float[] SPLINE_POSITION = new float[NB_SAMPLES + 1];
        private static final float[] SPLINE_TIME = new float[NB_SAMPLES + 1];

        private static final int TO_EDGE = 0;
        private static final int TO_BOUNDARY = 1;
        private static final int TO_BOUNCE = 2;


        // If the velocity is smaller than this value, no bounce is triggered
        // If the velocity is smaller than this value, no bounce is triggered
        // when the edge limits are reached (would result in a zero pixels
        // when the edge limits are reached.
        // displacement anyway).
        private static final float MINIMUM_VELOCITY_FOR_BOUNCE = Float.MAX_VALUE;//140.0f;
        private static final float MINIMUM_VELOCITY_FOR_BOUNCE = 140.0f; //Float.MAX_VALUE;//140.0f;


        // Proportion of the velocity that is preserved when the edge is reached.
        // Proportion of the velocity that is preserved when the edge is reached.
        private static final float DEFAULT_BOUNCE_COEFFICIENT = 0.36f;
        private static final float DEFAULT_BOUNCE_COEFFICIENT = 0.16f;


        private float mBounceCoefficient = DEFAULT_BOUNCE_COEFFICIENT;
        static {
            float x_min = 0.0f;
            float y_min = 0.0f;
            for (int i = 0; i < NB_SAMPLES; i++) {
                final float alpha = (float) i / NB_SAMPLES;


        MagneticOverScroller() {
                float x_max = 1.0f;
                float x, tx, coef;
                while (true) {
                    x = x_min + (x_max - x_min) / 2.0f;
                    coef = 3.0f * x * (1.0f - x);
                    tx = coef * ((1.0f - x) * P1 + x * P2) + x * x * x;
                    if (Math.abs(tx - alpha) < 1E-5) break;
                    if (tx > alpha) x_max = x;
                    else x_min = x;
                }
                SPLINE_POSITION[i] = coef * ((1.0f - x) * START_TENSION + x) + x * x * x;

                float y_max = 1.0f;
                float y, dy;
                while (true) {
                    y = y_min + (y_max - y_min) / 2.0f;
                    coef = 3.0f * y * (1.0f - y);
                    dy = coef * ((1.0f - y) * START_TENSION + y) + y * y * y;
                    if (Math.abs(dy - alpha) < 1E-5) break;
                    if (dy > alpha) y_max = y;
                    else y_min = y;
                }
                SPLINE_TIME[i] = coef * ((1.0f - y) * P1 + y * P2) + y * y * y;
            }
            SPLINE_POSITION[NB_SAMPLES] = SPLINE_TIME[NB_SAMPLES] = 1.0f;
        }

        static void initFromContext(Context context) {
            final float ppi = context.getResources().getDisplayMetrics().density * 160.0f;
            PHYSICAL_COEF = SensorManager.GRAVITY_EARTH // g (m/s^2)
                    * 39.37f // inch/meter
                    * ppi
                    * 0.84f; // look and feel tuning
        }

        void setFriction(float friction) {
            mFlingFriction = friction;
        }

        SplineOverScroller() {
            mFinished = true;
            mFinished = true;
        }
        }


@@ -600,18 +654,18 @@ public class OverScroller {
        /*
        /*
         * Get a signed deceleration that will reduce the velocity.
         * Get a signed deceleration that will reduce the velocity.
         */
         */
        float getDeceleration(int velocity) {
        static private float getDeceleration(int velocity) {
            return velocity > 0 ? -OverScroller.this.mDeceleration : OverScroller.this.mDeceleration;
            return velocity > 0 ? -GRAVITY : GRAVITY;
        }
        }


        /*
        /*
         * Modifies mDuration to the duration it takes to get from start to newFinal using the
         * Modifies mDuration to the duration it takes to get from start to newFinal using the
         * spline interpolation. The previous duration was needed to get to oldFinal.
         * spline interpolation. The previous duration was needed to get to oldFinal.
         */
         */
        void adjustDuration(int start, int oldFinal, int newFinal) {
        private void adjustDuration(int start, int oldFinal, int newFinal) {
            final int oldDistance = oldFinal - start;
            final int oldDistance = oldFinal - start;
            final int newDistance = newFinal - start;
            final int newDistance = newFinal - start;
            final float x = (float) Math.abs((float) newDistance / oldDistance);
            final float x = Math.abs((float) newDistance / oldDistance);
            final int index = (int) (NB_SAMPLES * x);
            final int index = (int) (NB_SAMPLES * x);
            if (index < NB_SAMPLES) {
            if (index < NB_SAMPLES) {
                final float x_inf = (float) index / NB_SAMPLES;
                final float x_inf = (float) index / NB_SAMPLES;
@@ -619,7 +673,6 @@ public class OverScroller {
                final float t_inf = SPLINE_TIME[index];
                final float t_inf = SPLINE_TIME[index];
                final float t_sup = SPLINE_TIME[index + 1];
                final float t_sup = SPLINE_TIME[index + 1];
                final float timeCoef = t_inf + (x - x_inf) / (x_sup - x_inf) * (t_sup - t_inf);
                final float timeCoef = t_inf + (x - x_inf) / (x_sup - x_inf) * (t_sup - t_inf);

                mDuration *= timeCoef;
                mDuration *= timeCoef;
            }
            }
        }
        }
@@ -696,7 +749,7 @@ public class OverScroller {
            mCurrVelocity = mVelocity = velocity;
            mCurrVelocity = mVelocity = velocity;
            mDuration = mSplineDuration = 0;
            mDuration = mSplineDuration = 0;
            mStartTime = AnimationUtils.currentAnimationTimeMillis();
            mStartTime = AnimationUtils.currentAnimationTimeMillis();
            mStart = start;
            mCurrentPosition = mStart = start;


            if (start > max || start < min) {
            if (start > max || start < min) {
                startAfterEdge(start, min, max, velocity);
                startAfterEdge(start, min, max, velocity);
@@ -707,10 +760,8 @@ public class OverScroller {
            double totalDistance = 0.0;
            double totalDistance = 0.0;


            if (velocity != 0) {
            if (velocity != 0) {
                final double l = Math.log(START_TENSION * Math.abs(velocity) / ALPHA);
                mDuration = mSplineDuration = getSplineFlingDuration(velocity);
                // Duration are expressed in milliseconds
                totalDistance = getSplineFlingDistance(velocity);
                mDuration = mSplineDuration = (int) (1000.0 * Math.exp(l / (DECELERATION_RATE - 1.0)));
                totalDistance = (ALPHA * Math.exp(DECELERATION_RATE / (DECELERATION_RATE - 1.0) * l));
            }
            }


            mSplineDistance = (int) (totalDistance * Math.signum(velocity));
            mSplineDistance = (int) (totalDistance * Math.signum(velocity));
@@ -728,6 +779,23 @@ public class OverScroller {
            }
            }
        }
        }


        private double getSplineDeceleration(int velocity) {
            return Math.log(INFLEXION * Math.abs(velocity) / (mFlingFriction * PHYSICAL_COEF));
        }

        private double getSplineFlingDistance(int velocity) {
            final double l = getSplineDeceleration(velocity);
            final double decelMinusOne = DECELERATION_RATE - 1.0;
            return mFlingFriction * PHYSICAL_COEF * Math.exp(DECELERATION_RATE / decelMinusOne * l);
        }

        /* Returns the duration, expressed in milliseconds */
        private int getSplineFlingDuration(int velocity) {
            final double l = getSplineDeceleration(velocity);
            final double decelMinusOne = DECELERATION_RATE - 1.0;
            return (int) (1000.0 * Math.exp(l / decelMinusOne));
        }

        private void fitOnBounceCurve(int start, int end, int velocity) {
        private void fitOnBounceCurve(int start, int end, int velocity) {
            // Simulate a bounce that started from edge
            // Simulate a bounce that started from edge
            final float durationToApex = - velocity / mDeceleration;
            final float durationToApex = - velocity / mDeceleration;
@@ -748,6 +816,7 @@ public class OverScroller {


        private void startAfterEdge(int start, int min, int max, int velocity) {
        private void startAfterEdge(int start, int min, int max, int velocity) {
            if (start > min && start < max) {
            if (start > min && start < max) {
                Log.e("OverScroller", "startAfterEdge called from a valid position");
                mFinished = true;
                mFinished = true;
                return;
                return;
            }
            }
@@ -759,9 +828,7 @@ public class OverScroller {
                // Will result in a bounce or a to_boundary depending on velocity.
                // Will result in a bounce or a to_boundary depending on velocity.
                startBounceAfterEdge(start, edge, velocity);
                startBounceAfterEdge(start, edge, velocity);
            } else {
            } else {
                final double l = Math.log(START_TENSION * Math.abs(velocity) / ALPHA);
                final double totalDistance = getSplineFlingDistance(velocity);
                final double totalDistance =
                    (ALPHA * Math.exp(DECELERATION_RATE / (DECELERATION_RATE - 1.0) * l));
                if (totalDistance > Math.abs(overDistance)) {
                if (totalDistance > Math.abs(overDistance)) {
                    fling(start, velocity, positive ? min : start, positive ? start : max, mOver);
                    fling(start, velocity, positive ? min : start, positive ? start : max, mOver);
                } else {
                } else {
@@ -771,12 +838,14 @@ public class OverScroller {
        }
        }


        void notifyEdgeReached(int start, int end, int over) {
        void notifyEdgeReached(int start, int end, int over) {
            if (mState == TO_EDGE) {
                mOver = over;
                mOver = over;
                mStartTime = AnimationUtils.currentAnimationTimeMillis();
                mStartTime = AnimationUtils.currentAnimationTimeMillis();
            // We were in fling/scroll mode before: current velocity is such that distance to edge
                // We were in fling/scroll mode before: current velocity is such that distance to
            // is increasing. Ensures that startAfterEdge will not start a new fling.
                // edge is increasing. Ensures that startAfterEdge will not start a new fling.
                startAfterEdge(start, end, end, (int) mCurrVelocity);
                startAfterEdge(start, end, end, (int) mCurrVelocity);
            }
            }
        }


        private void onEdgeReached() {
        private void onEdgeReached() {
            // mStart, mVelocity and mStartTime were adjusted to their values when edge was reached.
            // mStart, mVelocity and mStartTime were adjusted to their values when edge was reached.