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

Commit d348bb4f authored by Gilles Debunne's avatar Gilles Debunne Committed by Adam Powell
Browse files

Changes to scrolling physics

Spline curve for Scroller fling motion (debunne)

Flywheel motion for AbsListView

Change-Id: Ic1f226878745ff4c302dc6bd0752868fa182dd7b
parent 77a6be41
Loading
Loading
Loading
Loading
+14 −0
Original line number Diff line number Diff line
@@ -239966,6 +239966,20 @@
<parameter name="interpolator" type="android.view.animation.Interpolator">
</parameter>
</constructor>
<constructor name="Scroller"
 type="android.widget.Scroller"
 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="flywheel" type="boolean">
</parameter>
</constructor>
<method name="abortAnimation"
 return="void"
 abstract="false"
+83 −46
Original line number Diff line number Diff line
@@ -2543,6 +2543,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
                        mMotionCorrection = 0;
                        motionPosition = findMotionRow(y);
                        reportScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL);
                        mFlingRunnable.flywheelTouch();
                    }
                }
            }
@@ -2715,6 +2716,9 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
                        } else {
                            mTouchMode = TOUCH_MODE_REST;
                            reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
                            if (mFlingRunnable != null) {
                                mFlingRunnable.endFling();
                            }
                        }
                    }
                } else {
@@ -2758,7 +2762,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
        case MotionEvent.ACTION_CANCEL: {
            mTouchMode = TOUCH_MODE_REST;
            setPressed(false);
            View motionView = this.getChildAt(mMotionPosition - mFirstPosition);
            View motionView = getChildAt(mMotionPosition - mFirstPosition);
            if (motionView != null) {
                motionView.setPressed(false);
            }
@@ -2946,6 +2950,30 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
         */
        private int mLastFlingY;

        private final Runnable mCheckFlywheel = new Runnable() {
            public void run() {
                final int activeId = mActivePointerId;
                final VelocityTracker vt = mVelocityTracker;
                final Scroller scroller = mScroller;
                if (vt == null || activeId == INVALID_POINTER) {
                    return;
                }

                vt.computeCurrentVelocity(1000, mMaximumVelocity);
                final float yvel = -vt.getYVelocity(activeId);

                if (scroller.isScrollingInDirection(0, yvel)) {
                    // Keep the fling alive a little longer
                    postDelayed(this, FLYWHEEL_TIMEOUT);
                } else {
                    endFling();
                    mTouchMode = TOUCH_MODE_SCROLL;
                }
            }
        };

        private static final int FLYWHEEL_TIMEOUT = 40; // milliseconds

        FlingRunnable() {
            mScroller = new Scroller(getContext());
        }
@@ -2978,29 +3006,42 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
            post(this);
        }

        private void endFling() {
        void endFling() {
            mTouchMode = TOUCH_MODE_REST;

            removeCallbacks(this);
            removeCallbacks(mCheckFlywheel);
            if (mPositionScroller != null) {
                removeCallbacks(mPositionScroller);
            }

            reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
            clearScrollingCache();
            mScroller.abortAnimation();
        }

        void flywheelTouch() {
            postDelayed(mCheckFlywheel, FLYWHEEL_TIMEOUT);
        }

        public void run() {
            switch (mTouchMode) {
            default:
                endFling();
                return;
                
            case TOUCH_MODE_FLING: {
            case TOUCH_MODE_SCROLL:
                if (mScroller.isFinished()) {
                    return;
                }
                // Fall through
            case TOUCH_MODE_FLING:
                if (mItemCount == 0 || getChildCount() == 0) {
                    endFling();
                    return;
                }

                break;
            }
            final Scroller scroller = mScroller;
            boolean more = scroller.computeScrollOffset();
            final int y = scroller.getCurrY();
@@ -3045,17 +3086,13 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
                        Debug.stopMethodTracing();
                        mFlingProfilingStarted = false;
                    }
                    }

                    if (mFlingStrictSpan != null) {
                        mFlingStrictSpan.finish();
                        mFlingStrictSpan = null;
                    }
                }
                break;
            }
            }

        }
    }
    
+85 −16
Original line number Diff line number Diff line
@@ -18,6 +18,8 @@ package android.widget;

import android.content.Context;
import android.hardware.SensorManager;
import android.os.Build;
import android.util.FloatMath;
import android.view.ViewConfiguration;
import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
@@ -54,6 +56,7 @@ public class Scroller {
    private float mViscousFluidNormalize;
    private boolean mFinished;
    private Interpolator mInterpolator;
    private boolean mFlywheel;

    private float mCoeffX = 0.0f;
    private float mCoeffY = 1.0f;
@@ -63,9 +66,37 @@ public class Scroller {
    private static final int SCROLL_MODE = 0;
    private static final int FLING_MODE = 1;

    private static float DECELERATION_RATE = (float) (Math.log(0.75) / Math.log(0.9));
    private static float ALPHA = 400; // 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 = new float[NB_SAMPLES + 1];


    private float mDeceleration;
    private final float mPpi;

    static {
        float x_min = 0.0f;
        for (int i = 0; i <= NB_SAMPLES; i++) {
            final float t = (float) i / NB_SAMPLES;
            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 - t) < 1E-5) break;
                if (tx > t) x_max = x;
                else x_min = x;
            }
            final float d = coef + x * x * x;
            SPLINE[i] = d;
        }
        SPLINE[NB_SAMPLES] = 1.0f;
    }

    /**
     * Create a Scroller with the default duration and interpolator.
     */
@@ -73,22 +104,28 @@ public class Scroller {
        this(context, null);
    }

    public Scroller(Context context, Interpolator interpolator) {
        this(context, interpolator,
                context.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.HONEYCOMB);
    }

    /**
     * Create a Scroller with the specified interpolator. If the interpolator is
     * null, the default (viscous) interpolator will be used.
     */
    public Scroller(Context context, Interpolator interpolator) {
    public Scroller(Context context, Interpolator interpolator, boolean flywheel) {
        mFinished = true;
        mInterpolator = interpolator;
        mPpi = context.getResources().getDisplayMetrics().density * 160.0f;
        mDeceleration = computeDeceleration(ViewConfiguration.getScrollFriction());
        mFlywheel = flywheel;
    }

    /**
     * The amount of friction applied to flings. The default value
     * is {@link ViewConfiguration#getScrollFriction}.
     * 
     * @return A scalar dimensionless value representing the coefficient of
     * @param friction A scalar dimension-less value representing the coefficient of
     *         friction.
     */
    public final void setFriction(float friction) {
@@ -210,7 +247,7 @@ public class Scroller {
        if (timePassed < mDuration) {
            switch (mMode) {
            case SCROLL_MODE:
                float x = (float)timePassed * mDurationReciprocal;
                float x = timePassed * mDurationReciprocal;
    
                if (mInterpolator == null)
                    x = viscousFluid(x); 
@@ -221,16 +258,20 @@ public class Scroller {
                mCurrY = mStartY + Math.round(x * mDeltaY);
                break;
            case FLING_MODE:
                float timePassedSeconds = timePassed / 1000.0f;
                float distance = (mVelocity * timePassedSeconds)
                        - (mDeceleration * timePassedSeconds * timePassedSeconds / 2.0f);
                
                mCurrX = mStartX + Math.round(distance * mCoeffX);
                final float t = (float) timePassed / mDuration;
                final int index = (int) (NB_SAMPLES * t);
                final float t_inf = (float) index / NB_SAMPLES;
                final float t_sup = (float) (index + 1) / NB_SAMPLES;
                final float d_inf = SPLINE[index];
                final float d_sup = SPLINE[index + 1];
                final float distanceCoef = d_inf + (t - t_inf) / (t_sup - t_inf) * (d_sup - d_inf);
                
                mCurrX = mStartX + Math.round(distanceCoef * (mFinalX - mStartX));
                // Pin to mMinX <= mCurrX <= mMaxX
                mCurrX = Math.min(mCurrX, mMaxX);
                mCurrX = Math.max(mCurrX, mMinX);
                
                mCurrY = mStartY + Math.round(distance * mCoeffY);
                mCurrY = mStartY + Math.round(distanceCoef * (mFinalY - mStartY));
                // Pin to mMinY <= mCurrY <= mMaxY
                mCurrY = Math.min(mCurrY, mMaxY);
                mCurrY = Math.max(mCurrY, mMinY);
@@ -292,7 +333,7 @@ public class Scroller {
        mFinalY = startY + dy;
        mDeltaX = dx;
        mDeltaY = dy;
        mDurationReciprocal = 1.0f / (float) mDuration;
        mDurationReciprocal = 1.0f / mDuration;
        // This controls the viscous fluid effect (how much of it)
        mViscousFluidScale = 8.0f;
        // must be set to 1.0 (used in viscousFluid())
@@ -321,14 +362,34 @@ public class Scroller {
     */
    public void fling(int startX, int startY, int velocityX, int velocityY,
            int minX, int maxX, int minY, int maxY) {
        // Continue a scroll or fling in progress
        if (mFlywheel && !mFinished) {
            float oldVel = getCurrVelocity();

            float dx = (float) (mFinalX - mStartX);
            float dy = (float) (mFinalY - mStartY);
            float hyp = FloatMath.sqrt(dx * dx + dy * dy);

            float ndx = dx / hyp;
            float ndy = dy / hyp;

            float oldVelocityX = ndx * oldVel;
            float oldVelocityY = ndy * oldVel;
            if (Math.signum(velocityX) == Math.signum(oldVelocityX) &&
                    Math.signum(velocityY) == Math.signum(oldVelocityY)) {
                velocityX += oldVelocityX;
                velocityY += oldVelocityY;
            }
        }

        mMode = FLING_MODE;
        mFinished = false;

        float velocity = (float)Math.hypot(velocityX, velocityY);
        float velocity = FloatMath.sqrt(velocityX * velocityX + velocityY * velocityY);
     
        mVelocity = velocity;
        mDuration = (int) (1000 * velocity / mDeceleration); // Duration is in
                                                            // milliseconds
        final double l = Math.log(START_TENSION * velocity / ALPHA);
        mDuration = (int) (1000.0 * Math.exp(l / (DECELERATION_RATE - 1.0)));
        mStartTime = AnimationUtils.currentAnimationTimeMillis();
        mStartX = startX;
        mStartY = startY;
@@ -336,14 +397,14 @@ public class Scroller {
        mCoeffX = velocity == 0 ? 1.0f : velocityX / velocity; 
        mCoeffY = velocity == 0 ? 1.0f : velocityY / velocity;

        int totalDistance = (int) ((velocity * velocity) / (2 * mDeceleration));
        int totalDistance =
                (int) (ALPHA * Math.exp(DECELERATION_RATE / (DECELERATION_RATE - 1.0) * l));
        
        mMinX = minX;
        mMaxX = maxX;
        mMinY = minY;
        mMaxY = maxY;

        
        mFinalX = startX + Math.round(totalDistance * mCoeffX);
        // Pin to mMinX <= mFinalX <= mMaxX
        mFinalX = Math.min(mFinalX, mMaxX);
@@ -395,7 +456,7 @@ public class Scroller {
    public void extendDuration(int extend) {
        int passed = timePassed();
        mDuration = passed + extend;
        mDurationReciprocal = 1.0f / (float)mDuration;
        mDurationReciprocal = 1.0f / mDuration;
        mFinished = false;
    }

@@ -433,4 +494,12 @@ public class Scroller {
        mDeltaY = mFinalY - mStartY;
        mFinished = false;
    }

    /**
     * @hide
     */
    public boolean isScrollingInDirection(float xvel, float yvel) {
        return !mFinished && Math.signum(xvel) == Math.signum(mFinalX - mStartX) &&
                Math.signum(yvel) == Math.signum(mFinalY - mStartY);
    }
}