Modifying feel of scrolling / overscroll

-Modified overscroll on Workspace
-Added overscroll effectiveness curve to PagedView
-Modified the feel of scrolling in Workspace, AllApps,
 and Configure. Made it more sensitive to velocity.

Change-Id: I7f705e267a536c742fbb3b6556648bbf993bdd2f
diff --git a/src/com/android/launcher2/PagedView.java b/src/com/android/launcher2/PagedView.java
index 264e839..09133e0 100644
--- a/src/com/android/launcher2/PagedView.java
+++ b/src/com/android/launcher2/PagedView.java
@@ -36,6 +36,7 @@
 import android.view.ViewParent;
 import android.view.animation.Animation;
 import android.view.animation.AnimationUtils;
+import android.view.animation.Interpolator;
 import android.view.animation.Animation.AnimationListener;
 import android.widget.Checkable;
 import android.widget.Scroller;
@@ -55,10 +56,12 @@
     // The min drag distance to trigger a page shift (regardless of velocity)
     private static final int MIN_LENGTH_FOR_MOVE = 200;
 
-    private static final int PAGE_SNAP_ANIMATION_DURATION = 750;
+    private static final int PAGE_SNAP_ANIMATION_DURATION = 550;
     protected static final float NANOTIME_DIV = 1000000000.0f;
 
-    private static final float OVERSCROLL_DAMP_FACTOR = 0.22f;
+    private static final float OVERSCROLL_DAMP_FACTOR = 0.08f;
+    private static final int MINIMUM_SNAP_VELOCITY = 2200;
+    private static final int MIN_FLING_VELOCITY = 250;
 
     // the velocity at which a fling gesture will cause us to snap to the next page
     protected int mSnapVelocity = 500;
@@ -209,7 +212,7 @@
         mDirtyPageContent = new ArrayList<Boolean>();
         mDirtyPageContent.ensureCapacity(32);
         mPageViewIconCache = new PagedViewIconCache();
-        mScroller = new Scroller(getContext());
+        mScroller = new Scroller(getContext(), new ScrollInterpolator());
         mCurrentPage = 0;
         mCenterPagesVertically = true;
 
@@ -827,8 +830,22 @@
         return false;
     }
 
+    // This curve determines how the effect of scrolling over the limits of the page dimishes
+    // as the user pulls further and further from the bounds
+    private float overScrollInfluenceCurve(float f) {
+        f -= 1.0f;
+        return f * f * f + 1.0f;
+    }
+
     protected void overScroll(float amount) {
-        int overScrollAmount = (int) Math.round(OVERSCROLL_DAMP_FACTOR * amount);
+        int screenSize = getMeasuredWidth();
+
+        float f = (amount / screenSize);
+
+        if (f == 0) return;
+        f = f / (Math.abs(f)) * (overScrollInfluenceCurve(Math.abs(f)));
+
+        int overScrollAmount = (int) Math.round(OVERSCROLL_DAMP_FACTOR * f * screenSize);
         if (amount < 0) {
             mScrollX = overScrollAmount;
         } else {
@@ -902,8 +919,7 @@
 
                 final int snapVelocity = mSnapVelocity;
                 if ((isSignificantMove && deltaX > 0 ||
-                        (isfling && velocityX > snapVelocity)) &&
-                        mCurrentPage > 0) {
+                        (isfling && velocityX > snapVelocity)) && mCurrentPage > 0) {
                     snapToPageWithVelocity(mCurrentPage - 1, velocityX);
                 } else if ((isSignificantMove && deltaX < 0 ||
                         (isfling && velocityX < -snapVelocity)) &&
@@ -1048,10 +1064,58 @@
         snapToPage(getPageNearestToCenterOfScreen(), PAGE_SNAP_ANIMATION_DURATION);
     }
 
+    private static class ScrollInterpolator implements Interpolator {
+        public ScrollInterpolator() {
+        }
+
+        public float getInterpolation(float t) {
+            t -= 1.0f;
+            return t*t*t*t*t + 1;
+        }
+    }
+
+    // We want the duration of the page snap animation to be influenced by the distance that
+    // the screen has to travel, however, we don't want this duration to be effected in a
+    // purely linear fashion. Instead, we use this method to moderate the effect that the distance
+    // of travel has on the overall snap duration.
+    float distanceInfluenceForSnapDuration(float f) {
+        f -= 0.5f; // center the values about 0.
+        f *= 0.3f * Math.PI / 2.0f;
+        return (float) Math.sin(f);
+    }
+
     protected void snapToPageWithVelocity(int whichPage, int velocity) {
-        // We ignore velocity in this implementation, but children (e.g. SmoothPagedView)
-        // can use it
-        snapToPage(whichPage);
+        whichPage = Math.max(0, Math.min(whichPage, getChildCount() - 1));
+        int halfScreenSize = getMeasuredWidth() / 2;
+
+        final int newX = getChildOffset(whichPage) - getRelativeChildOffset(whichPage);
+        int delta = newX - mUnboundedScrollX;
+        int duration = 0;
+
+        if (Math.abs(velocity) < MIN_FLING_VELOCITY) {
+            // If the velocity is low enough, then treat this more as an automatic page advance
+            // as opposed to an apparent physical response to flinging
+            snapToPage(whichPage, PAGE_SNAP_ANIMATION_DURATION);
+            return;
+        }
+
+        // Here we compute a "distance" that will be used in the computation of the overall
+        // snap duration. This is a function of the actual distance that needs to be traveled;
+        // we keep this value close to half screen size in order to reduce the variance in snap
+        // duration as a function of the distance the page needs to travel.
+        float distanceRatio = 1.0f * Math.abs(delta) / 2 * halfScreenSize;
+        float distance = halfScreenSize + halfScreenSize *
+                distanceInfluenceForSnapDuration(distanceRatio);
+
+        velocity = Math.abs(velocity);
+        velocity = Math.max(MINIMUM_SNAP_VELOCITY, velocity);
+
+        // we want the page's snap velocity to approximately match the velocity at which the
+        // user flings, so we scale the duration by a value near to the derivative of the scroll
+        // interpolator at zero, ie. 5. We use 6 to make it a little slower.
+        duration = 6 * Math.round(1000 * Math.abs(distance / velocity));
+
+        snapToPage(whichPage, delta, duration);
     }
 
     protected void snapToPage(int whichPage) {
diff --git a/src/com/android/launcher2/SmoothPagedView.java b/src/com/android/launcher2/SmoothPagedView.java
index ee8bca2..8b0a835 100644
--- a/src/com/android/launcher2/SmoothPagedView.java
+++ b/src/com/android/launcher2/SmoothPagedView.java
@@ -21,7 +21,6 @@
 import android.view.animation.Interpolator;
 import android.widget.Scroller;
 
-
 public abstract class SmoothPagedView extends PagedView {
     private static final float SMOOTHING_SPEED = 0.75f;
     private static final float SMOOTHING_CONSTANT = (float) (0.016 / Math.log(SMOOTHING_SPEED));
@@ -29,8 +28,8 @@
     private float mBaseLineFlingVelocity;
     private float mFlingVelocityInfluence;
 
-    static final int OVERSHOOT_MODE = 0;
-    static final int QUINTIC_MODE = 1;
+    static final int DEFAULT_MODE = 0;
+    static final int X_LARGE_MODE = 1;
 
     int mScrollMode;
 
@@ -60,16 +59,6 @@
         }
     }
 
-    private static class QuinticInterpolator implements Interpolator {
-        public QuinticInterpolator() {
-        }
-
-        public float getInterpolation(float t) {
-            t -= 1.0f;
-            return t*t*t*t*t + 1;
-        }
-    }
-
     /**
      * Used to inflate the Workspace from XML.
      *
@@ -93,12 +82,12 @@
         mUsePagingTouchSlop = false;
 
         // This means that we'll take care of updating the scroll parameter ourselves (we do it
-        // in computeScroll)
-        mDeferScrollUpdate = true;
+        // in computeScroll), we only do this in the OVERSHOOT_MODE, ie. on phones
+        mDeferScrollUpdate = mScrollMode != X_LARGE_MODE;
     }
 
     protected int getScrollMode() {
-        return OVERSHOOT_MODE;
+        return DEFAULT_MODE;
     }
 
     /**
@@ -109,26 +98,30 @@
         super.init();
 
         mScrollMode = getScrollMode();
-        if (mScrollMode == QUINTIC_MODE) {
-            mBaseLineFlingVelocity = 700.0f;
-            mFlingVelocityInfluence = 0.8f;
-            mScrollInterpolator = new QuinticInterpolator();
-        } else {  // QUINTIC_MODE
+        if (mScrollMode == DEFAULT_MODE) {
             mBaseLineFlingVelocity = 2500.0f;
             mFlingVelocityInfluence = 0.4f;
             mScrollInterpolator = new WorkspaceOvershootInterpolator();
+            mScroller = new Scroller(getContext(), mScrollInterpolator);
         }
-        mScroller = new Scroller(getContext(), mScrollInterpolator);
     }
 
     @Override
     protected void snapToDestination() {
-        snapToPageWithVelocity(getPageNearestToCenterOfScreen(), 0);
+        if (mScrollMode == X_LARGE_MODE) {
+            super.snapToDestination();
+        } else {
+            snapToPageWithVelocity(getPageNearestToCenterOfScreen(), 0);
+        }
     }
 
     @Override
     protected void snapToPageWithVelocity(int whichPage, int velocity) {
-        snapToPageWithVelocity(whichPage, 0, true);
+        if (mScrollMode == X_LARGE_MODE) {
+            super.snapToPageWithVelocity(whichPage, velocity);
+        } else {
+            snapToPageWithVelocity(whichPage, 0, true);
+        }
     }
 
     private void snapToPageWithVelocity(int whichPage, int velocity, boolean settle) {
@@ -139,23 +132,16 @@
         final int screenDelta = Math.max(1, Math.abs(whichPage - mCurrentPage));
         final int newX = getChildOffset(whichPage) - getRelativeChildOffset(whichPage);
         final int delta = newX - mUnboundedScrollX;
-        int duration;
-        if (mScrollMode == OVERSHOOT_MODE) {
-            duration = (screenDelta + 1) * 100;
-        } else { // QUINTIC_MODE
-            duration = Math.round(Math.abs(delta) * 0.6f);
-        }
+        int duration = (screenDelta + 1) * 100;
 
         if (!mScroller.isFinished()) {
             mScroller.abortAnimation();
         }
 
-        if (mScrollMode == OVERSHOOT_MODE) {
-            if (settle) {
-                ((WorkspaceOvershootInterpolator) mScrollInterpolator).setDistance(screenDelta);
-            } else {
-                ((WorkspaceOvershootInterpolator) mScrollInterpolator).disableSettle();
-            }
+        if (settle) {
+            ((WorkspaceOvershootInterpolator) mScrollInterpolator).setDistance(screenDelta);
+        } else {
+            ((WorkspaceOvershootInterpolator) mScrollInterpolator).disableSettle();
         }
 
         velocity = Math.abs(velocity);
@@ -170,26 +156,33 @@
 
     @Override
     protected void snapToPage(int whichPage) {
-        snapToPageWithVelocity(whichPage, 0, false);
+       if (mScrollMode == X_LARGE_MODE) {
+           super.snapToPage(whichPage);
+       } else {
+           snapToPageWithVelocity(whichPage, 0, false);
+       }
     }
 
     @Override
     public void computeScroll() {
-        boolean scrollComputed = computeScrollHelper();
+        if (mScrollMode == X_LARGE_MODE) {
+            super.computeScroll();
+        } else {
+            boolean scrollComputed = computeScrollHelper();
 
-        if (!scrollComputed && mTouchState == TOUCH_STATE_SCROLLING) {
-            final float now = System.nanoTime() / NANOTIME_DIV;
-            final float e = (float) Math.exp((now - mSmoothingTime) / SMOOTHING_CONSTANT);
+            if (!scrollComputed && mTouchState == TOUCH_STATE_SCROLLING) {
+                final float now = System.nanoTime() / NANOTIME_DIV;
+                final float e = (float) Math.exp((now - mSmoothingTime) / SMOOTHING_CONSTANT);
 
-            final float dx = mTouchX - mUnboundedScrollX;
-            scrollTo(Math.round(mUnboundedScrollX + dx * e), mScrollY);
-            mSmoothingTime = now;
+                final float dx = mTouchX - mUnboundedScrollX;
+                scrollTo(Math.round(mUnboundedScrollX + dx * e), mScrollY);
+                mSmoothingTime = now;
 
-            // Keep generating points as long as we're more than 1px away from the target
-            if (dx > 1.f || dx < -1.f) {
-                invalidate();
+                // Keep generating points as long as we're more than 1px away from the target
+                if (dx > 1.f || dx < -1.f) {
+                    invalidate();
+                }
             }
         }
-
     }
 }
diff --git a/src/com/android/launcher2/Workspace.java b/src/com/android/launcher2/Workspace.java
index a79d236..0a3f915 100644
--- a/src/com/android/launcher2/Workspace.java
+++ b/src/com/android/launcher2/Workspace.java
@@ -109,6 +109,7 @@
     private Drawable mBackground;
     private float mBackgroundAlpha = 0;
     private float mOverScrollMaxBackgroundAlpha = 0.0f;
+    private int mOverScrollPageIndex = -1;
 
     private final WallpaperManager mWallpaperManager;
 
@@ -268,9 +269,9 @@
     @Override
     protected int getScrollMode() {
         if (LauncherApplication.isScreenXLarge()) {
-            return SmoothPagedView.QUINTIC_MODE;
+            return SmoothPagedView.X_LARGE_MODE;
         } else {
-            return SmoothPagedView.OVERSHOOT_MODE;
+            return SmoothPagedView.DEFAULT_MODE;
         }
     }
 
@@ -516,6 +517,7 @@
             mAnimOnPageEndMoving = null;
         }
         mOverScrollMaxBackgroundAlpha = 0.0f;
+        mOverScrollPageIndex = -1;
         mPageMoving = false;
     }
 
@@ -653,7 +655,7 @@
     }
 
     float overScrollBackgroundAlphaInterpolator(float r) {
-        float threshold = 0.1f;
+        float threshold = 0.08f;
 
         if (r > mOverScrollMaxBackgroundAlpha) {
             mOverScrollMaxBackgroundAlpha = r;
@@ -664,23 +666,6 @@
         return Math.min(r / threshold, 1.0f);
     }
 
-    protected void overScroll(float amount) {
-        final int lastChildIndex = getChildCount() - 1;
-
-        CellLayout cl;
-        if (amount < 0) {
-            cl = (CellLayout) getChildAt(0);
-        } else {
-            cl = (CellLayout) getChildAt(lastChildIndex);
-        }
-
-        final int totalDistance = cl.getMeasuredWidth() + mPageSpacing;
-        float r = 1.0f * amount / totalDistance;
-        float rotation = -WORKSPACE_ROTATION * r;
-        cl.setBackgroundAlphaMultiplier(overScrollBackgroundAlphaInterpolator(Math.abs(r)));
-        cl.setRotationY(rotation);
-    }
-
     @Override
     protected void screenScrolled(int screenCenter) {
         final int halfScreenSize = getMeasuredWidth() / 2;
@@ -696,7 +681,18 @@
                 scrollProgress = Math.min(scrollProgress, 1.0f);
                 scrollProgress = Math.max(scrollProgress, -1.0f);
 
-                cl.setBackgroundAlphaMultiplier(backgroundAlphaInterpolator(Math.abs(scrollProgress)));
+                // If the current page (i) is being overscrolled, we use a different
+                // set of rules for setting the background alpha multiplier.
+                if ((mScrollX < 0 && i == 0) || (mScrollX > mMaxScrollX &&
+                        i == getChildCount() -1 )) {
+                    cl.setBackgroundAlphaMultiplier(
+                            overScrollBackgroundAlphaInterpolator(Math.abs(scrollProgress)));
+                    mOverScrollPageIndex = i;
+                } else if (mOverScrollPageIndex != i) {
+                    cl.setBackgroundAlphaMultiplier(
+                            backgroundAlphaInterpolator(Math.abs(scrollProgress)));
+
+                }
 
                 float rotation = WORKSPACE_ROTATION * scrollProgress;
                 float translationX = getOffsetXForRotation(rotation, cl.getWidth(), cl.getHeight());