Workspace overscroll effect

-> Made AllAppsCustomize scroll/over scroll more subtle

Change-Id: Icf1889b3adccce22d4a2d80c5b487518b0ab3157
diff --git a/src/com/android/launcher2/AppsCustomizePagedView.java b/src/com/android/launcher2/AppsCustomizePagedView.java
index a583415..dd04039 100644
--- a/src/com/android/launcher2/AppsCustomizePagedView.java
+++ b/src/com/android/launcher2/AppsCustomizePagedView.java
@@ -31,10 +31,10 @@
 import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.graphics.Bitmap;
-import android.graphics.Bitmap.Config;
 import android.graphics.Canvas;
 import android.graphics.PorterDuff;
 import android.graphics.Rect;
+import android.graphics.Bitmap.Config;
 import android.graphics.drawable.Drawable;
 import android.os.AsyncTask;
 import android.os.Process;
@@ -46,6 +46,7 @@
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.animation.AccelerateInterpolator;
+import android.view.animation.DecelerateInterpolator;
 import android.widget.GridLayout;
 import android.widget.ImageView;
 import android.widget.Toast;
@@ -191,11 +192,12 @@
 
     // Relating to the scroll and overscroll effects
     Workspace.ZInterpolator mZInterpolator = new Workspace.ZInterpolator(0.5f);
-    private float mDensity;
     private static float CAMERA_DISTANCE = 3500;
-    private static float TRANSITION_SCALE_FACTOR = 0.6f;
+    private static float TRANSITION_SCALE_FACTOR = 0.74f;
     private static float TRANSITION_PIVOT = 0.75f;
     private static float TRANSITION_MAX_ROTATION = 26f;
+    private static final boolean PERFORM_OVERSCROLL_ROTATION = false;
+    private AccelerateInterpolator mAlphaInterpolator = new AccelerateInterpolator(0.9f);
 
     // Previews & outlines
     ArrayList<AppsCustomizeAsyncTask> mRunningTasks;
@@ -219,7 +221,6 @@
         mDefaultWidgetBackground = resources.getDrawable(R.drawable.default_widget_preview_holo);
         mAppIconSize = resources.getDimensionPixelSize(R.dimen.app_icon_size);
         mDragViewMultiplyColor = resources.getColor(R.color.drag_view_multiply_color);
-        mDensity = resources.getDisplayMetrics().density;
 
         TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.PagedView, 0, 0);
         // TODO-APPS_CUSTOMIZE: remove these unnecessary attrs after
@@ -1131,49 +1132,47 @@
     @Override
     protected void screenScrolled(int screenCenter) {
         super.screenScrolled(screenCenter);
-        final int halfScreenSize = getMeasuredWidth() / 2;
 
         for (int i = 0; i < getChildCount(); i++) {
             View v = getPageAt(i);
             if (v != null) {
-                int totalDistance = getScaledMeasuredWidth(v) + mPageSpacing;
-                int delta = screenCenter - (getChildOffset(i) -
-                        getRelativeChildOffset(i) + halfScreenSize);
-
-                float scrollProgress = delta / (totalDistance * 1.0f);
-                scrollProgress = Math.min(scrollProgress, 1.0f);
-                scrollProgress = Math.max(scrollProgress, -1.0f);
+                float scrollProgress = getScrollProgress(screenCenter, v, i);
 
                 float interpolatedProgress =
                         mZInterpolator.getInterpolation(Math.abs(Math.min(scrollProgress, 0)));
                 float scale = (1 - interpolatedProgress) +
                         interpolatedProgress * TRANSITION_SCALE_FACTOR;
                 float translationX = Math.min(0, scrollProgress) * v.getMeasuredWidth();
-                float alpha = scrollProgress < 0 ? 1 - Math.abs(scrollProgress) : 1.0f;
+
+                float alpha = scrollProgress < 0 ? mAlphaInterpolator.getInterpolation(
+                        1 - Math.abs(scrollProgress)) : 1.0f;
 
                 v.setCameraDistance(mDensity * CAMERA_DISTANCE);
                 int pageWidth = v.getMeasuredWidth();
                 int pageHeight = v.getMeasuredHeight();
-                if (i == 0 && scrollProgress < 0) {
-                    // Overscroll to the left
-                    v.setPivotX(TRANSITION_PIVOT * pageWidth);
-                    v.setRotationY(-TRANSITION_MAX_ROTATION * scrollProgress);
-                    scale = 1.0f;
-                    alpha = 1.0f;
-                    // On the first page, we don't want the page to have any lateral motion
-                    translationX = getScrollX();
-                } else if (i == getChildCount() - 1 && scrollProgress > 0) {
-                    // Overscroll to the right
-                    v.setPivotX((1 - TRANSITION_PIVOT) * pageWidth);
-                    v.setRotationY(-TRANSITION_MAX_ROTATION * scrollProgress);
-                    scale = 1.0f;
-                    alpha = 1.0f;
-                    // On the last page, we don't want the page to have any lateral motion.
-                    translationX =  getScrollX() - mMaxScrollX;
-                } else {
-                    v.setPivotY(pageHeight / 2.0f);
-                    v.setPivotX(pageWidth / 2.0f);
-                    v.setRotationY(0f);
+
+                if (PERFORM_OVERSCROLL_ROTATION) {
+                    if (i == 0 && scrollProgress < 0) {
+                        // Overscroll to the left
+                        v.setPivotX(TRANSITION_PIVOT * pageWidth);
+                        v.setRotationY(-TRANSITION_MAX_ROTATION * scrollProgress);
+                        scale = 1.0f;
+                        alpha = 1.0f;
+                        // On the first page, we don't want the page to have any lateral motion
+                        translationX = getScrollX();
+                    } else if (i == getChildCount() - 1 && scrollProgress > 0) {
+                        // Overscroll to the right
+                        v.setPivotX((1 - TRANSITION_PIVOT) * pageWidth);
+                        v.setRotationY(-TRANSITION_MAX_ROTATION * scrollProgress);
+                        scale = 1.0f;
+                        alpha = 1.0f;
+                        // On the last page, we don't want the page to have any lateral motion.
+                        translationX =  getScrollX() - mMaxScrollX;
+                    } else {
+                        v.setPivotY(pageHeight / 2.0f);
+                        v.setPivotX(pageWidth / 2.0f);
+                        v.setRotationY(0f);
+                    }
                 }
 
                 v.setTranslationX(translationX);
@@ -1185,26 +1184,7 @@
     }
 
     protected void overScroll(float amount) {
-        int screenSize = getMeasuredWidth();
-
-        // We want to reach the max overscroll effect when the user has
-        // overscrolled half the size of the screen
-        float f = 2 * (amount / screenSize);
-
-        if (f == 0) return;
-
-        // Clamp this factor, f, to -1 < f < 1
-        if (Math.abs(f) >= 1) {
-            f /= Math.abs(f);
-        }
-
-        int overScrollAmount = (int) Math.round(f * screenSize);
-        if (amount < 0) {
-            mScrollX = overScrollAmount;
-        } else {
-            mScrollX = mMaxScrollX + overScrollAmount;
-        }
-        invalidate();
+        dampedOverScroll(amount);
     }
 
     /**
diff --git a/src/com/android/launcher2/CellLayout.java b/src/com/android/launcher2/CellLayout.java
index 667df8b..0d80d16 100644
--- a/src/com/android/launcher2/CellLayout.java
+++ b/src/com/android/launcher2/CellLayout.java
@@ -32,10 +32,13 @@
 import android.graphics.Paint;
 import android.graphics.Point;
 import android.graphics.PointF;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffXfermode;
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.graphics.Region;
 import android.graphics.drawable.Drawable;
+import android.graphics.drawable.NinePatchDrawable;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.view.MotionEvent;
@@ -88,6 +91,7 @@
     private ArrayList<FolderRingAnimator> mFolderOuterRings = new ArrayList<FolderRingAnimator>();
     private int[] mFolderLeaveBehindCell = {-1, -1};
 
+    private int mForegroundAlpha = 0;
     private float mBackgroundAlpha;
     private float mBackgroundAlphaMultiplier = 1.0f;
 
@@ -98,10 +102,15 @@
     private Drawable mNormalGlowBackgroundMini;
     private Drawable mActiveBackgroundMini;
     private Drawable mActiveGlowBackgroundMini;
+    private Drawable mOverScrollForegroundDrawable;
+    private Drawable mOverScrollLeft;
+    private Drawable mOverScrollRight;
     private Rect mBackgroundRect;
+    private Rect mForegroundRect;
     private Rect mGlowBackgroundRect;
     private float mGlowBackgroundScale;
     private float mGlowBackgroundAlpha;
+    private int mForegroundPadding;
 
     private boolean mAcceptsDrops = true;
     // If we're actively dragging something over this screen, mIsDragOverlapping is true
@@ -180,6 +189,10 @@
         mNormalGlowBackgroundMini = res.getDrawable(R.drawable.homescreen_small_blue_strong);
         mActiveBackgroundMini = res.getDrawable(R.drawable.homescreen_small_green);
         mActiveGlowBackgroundMini = res.getDrawable(R.drawable.homescreen_small_green_strong);
+        mOverScrollLeft = res.getDrawable(R.drawable.overscroll_glow_left);
+        mOverScrollRight = res.getDrawable(R.drawable.overscroll_glow_right);
+        mForegroundPadding =
+                res.getDimensionPixelSize(R.dimen.workspace_overscroll_drawable_padding);
 
         mNormalBackground.setFilterBitmap(true);
         mActiveBackground.setFilterBitmap(true);
@@ -261,6 +274,7 @@
         }
 
         mBackgroundRect = new Rect();
+        mForegroundRect = new Rect();
         mGlowBackgroundRect = new Rect();
         setHoverScale(1.0f);
         setHoverAlpha(1.0f);
@@ -311,6 +325,18 @@
                 icon.getBottom() + getPaddingTop() + padding);
     }
 
+    void setOverScrollAmount(float r, boolean left) {
+        if (left && mOverScrollForegroundDrawable != mOverScrollLeft) {
+            mOverScrollForegroundDrawable = mOverScrollLeft;
+        } else if (!left && mOverScrollForegroundDrawable != mOverScrollRight) {
+            mOverScrollForegroundDrawable = mOverScrollRight;
+        }
+
+        mForegroundAlpha = (int) Math.round((r * 255));
+        mOverScrollForegroundDrawable.setAlpha(mForegroundAlpha);
+        invalidate();
+    }
+
     void setPressedOrFocusedIcon(BubbleTextView icon) {
         // We draw the pressed or focused BubbleTextView's background in CellLayout because it
         // requires an expanded clip rect (due to the glow's blur radius)
@@ -577,6 +603,18 @@
         }
     }
 
+    @Override
+    protected void dispatchDraw(Canvas canvas) {
+        super.dispatchDraw(canvas);
+        if (mForegroundAlpha > 0) {
+            mOverScrollForegroundDrawable.setBounds(mForegroundRect);
+            Paint p = ((NinePatchDrawable) mOverScrollForegroundDrawable).getPaint();
+            p.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.ADD));
+            mOverScrollForegroundDrawable.draw(canvas);
+            p.setXfermode(null);
+        }
+    }
+
     public void showFolderAccept(FolderRingAnimator fra) {
         mFolderOuterRings.add(fra);
     }
@@ -962,6 +1000,8 @@
     protected void onSizeChanged(int w, int h, int oldw, int oldh) {
         super.onSizeChanged(w, h, oldw, oldh);
         mBackgroundRect.set(0, 0, w, h);
+        mForegroundRect.set(mForegroundPadding, mForegroundPadding,
+                w - 2 * mForegroundPadding, h - 2 * mForegroundPadding);
         updateGlowRect();
     }
 
diff --git a/src/com/android/launcher2/PagedView.java b/src/com/android/launcher2/PagedView.java
index 9f9450c..2b5847b 100644
--- a/src/com/android/launcher2/PagedView.java
+++ b/src/com/android/launcher2/PagedView.java
@@ -66,6 +66,7 @@
     private static final int PAGE_SNAP_ANIMATION_DURATION = 550;
     protected static final float NANOTIME_DIV = 1000000000.0f;
 
+    private static final float OVERSCROLL_ACCELERATE_FACTOR = 2;
     private static final float OVERSCROLL_DAMP_FACTOR = 0.14f;
     private static final int MINIMUM_SNAP_VELOCITY = 2200;
     private static final int MIN_FLING_VELOCITY = 250;
@@ -74,6 +75,7 @@
     // the velocity at which a fling gesture will cause us to snap to the next page
     protected int mSnapVelocity = 500;
 
+    protected float mDensity;
     protected float mSmoothingTime;
     protected float mTouchX;
 
@@ -229,6 +231,7 @@
         mTouchSlop = configuration.getScaledTouchSlop();
         mPagingTouchSlop = configuration.getScaledPagingTouchSlop();
         mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
+        mDensity = getResources().getDisplayMetrics().density;
     }
 
     public void setPageSwitchListener(PageSwitchListener pageSwitchListener) {
@@ -975,6 +978,19 @@
         }
     }
 
+    protected float getScrollProgress(int screenCenter, View v, int page) {
+        final int halfScreenSize = getMeasuredWidth() / 2;
+
+        int totalDistance = getScaledMeasuredWidth(v) + mPageSpacing;
+        int delta = screenCenter - (getChildOffset(page) -
+                getRelativeChildOffset(page) + halfScreenSize);
+
+        float scrollProgress = delta / (totalDistance * 1.0f);
+        scrollProgress = Math.min(scrollProgress, 1.0f);
+        scrollProgress = Math.max(scrollProgress, -1.0f);
+        return scrollProgress;
+    }
+
     // 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) {
@@ -982,7 +998,30 @@
         return f * f * f + 1.0f;
     }
 
-    protected void overScroll(float amount) {
+    protected void acceleratedOverScroll(float amount) {
+        int screenSize = getMeasuredWidth();
+
+        // We want to reach the max over scroll effect when the user has
+        // over scrolled half the size of the screen
+        float f = OVERSCROLL_ACCELERATE_FACTOR * (amount / screenSize);
+
+        if (f == 0) return;
+
+        // Clamp this factor, f, to -1 < f < 1
+        if (Math.abs(f) >= 1) {
+            f /= Math.abs(f);
+        }
+
+        int overScrollAmount = (int) Math.round(f * screenSize);
+        if (amount < 0) {
+            mScrollX = overScrollAmount;
+        } else {
+            mScrollX = mMaxScrollX + overScrollAmount;
+        }
+        invalidate();
+    }
+
+    protected void dampedOverScroll(float amount) {
         int screenSize = getMeasuredWidth();
 
         float f = (amount / screenSize);
@@ -1004,9 +1043,13 @@
         invalidate();
     }
 
+    protected void overScroll(float amount) {
+        dampedOverScroll(amount);
+    }
+
     protected float maxOverScroll() {
         // Using the formula in overScroll, assuming that f = 1.0 (which it should generally not
-        // exceed). Used to find out how much extra wallpaper we need for the overscroll effect
+        // exceed). Used to find out how much extra wallpaper we need for the over scroll effect
         float f = 1.0f;
         f = f / (Math.abs(f)) * (overScrollInfluenceCurve(Math.abs(f)));
         return OVERSCROLL_DAMP_FACTOR * f;
diff --git a/src/com/android/launcher2/Workspace.java b/src/com/android/launcher2/Workspace.java
index e8c5116..01efd4f 100644
--- a/src/com/android/launcher2/Workspace.java
+++ b/src/com/android/launcher2/Workspace.java
@@ -82,20 +82,13 @@
 
     // Y rotation to apply to the workspace screens
     private static final float WORKSPACE_ROTATION = 12.5f;
-
-    // These are extra scale factors to apply to the mini home screens
-    // so as to achieve the desired transform
-    private static final float EXTRA_SCALE_FACTOR_0 = 0.972f;
-    private static final float EXTRA_SCALE_FACTOR_1 = 1.0f;
-    private static final float EXTRA_SCALE_FACTOR_2 = 1.10f;
+    private static final float WORKSPACE_OVERSCROLL_ROTATION = 24f;
 
     private static final int CHILDREN_OUTLINE_FADE_OUT_DELAY = 0;
     private static final int CHILDREN_OUTLINE_FADE_OUT_DURATION = 375;
     private static final int CHILDREN_OUTLINE_FADE_IN_DURATION = 100;
 
     private static final int BACKGROUND_FADE_OUT_DURATION = 350;
-    private static final int BACKGROUND_FADE_IN_DURATION = 350;
-
     private static final int ADJACENT_SCREEN_DROP_DURATION = 300;
 
     // These animators are used to fade the children's outlines
@@ -762,18 +755,21 @@
         int scrollRange = getScrollRange();
         float scrollProgressOffset = 0;
 
-        // Account for overscroll: you only see the absolute edge of the wallpaper if
-        // you overscroll as far as you can in landscape mode. Only do this for static wallpapers
+        // Account for over scroll: you only see the absolute edge of the wallpaper if
+        // you over scroll as far as you can in landscape mode. Only do this for static wallpapers
         // because live wallpapers (and probably 3rd party wallpaper providers) rely on the offset
         // being even intervals from 0 to 1 (eg [0, 0.25, 0.5, 0.75, 1])
         if (isStaticWallpaper) {
-            int overscrollOffset = (int) (maxOverScroll() * display.getWidth());
-            scrollProgressOffset += overscrollOffset / (float) getScrollRange();
-            scrollRange += 2 * overscrollOffset;
+            int overScrollOffset = (int) (maxOverScroll() * display.getWidth());
+            scrollProgressOffset += overScrollOffset / (float) getScrollRange();
+            scrollRange += 2 * overScrollOffset;
         }
 
         // Again, we adjust the wallpaper offset to be consistent between values of mLayoutScale
-        float adjustedScrollX = mWallpaperScrollRatio * mScrollX;
+        boolean overScrollWallpaper = LauncherApplication.isScreenLarge();
+        float adjustedScrollX = overScrollWallpaper ?
+                mScrollX : Math.max(0, Math.min(mScrollX, mMaxScrollX));
+        adjustedScrollX *= mWallpaperScrollRatio;
         mLayoutScale = layoutScale;
 
         float scrollProgress =
@@ -1094,51 +1090,77 @@
         return Math.min(r / threshold, 1.0f);
     }
 
-    @Override
-    protected void screenScrolled(int screenCenter) {
-        super.screenScrolled(screenCenter);
-
-        // If the screen is not xlarge, then don't rotate the CellLayouts
-        // NOTE: If we don't update the side pages alpha, then we should not hide the side pages.
-        //       see unshrink().
-        if (!LauncherApplication.isScreenLarge()) return;
-
-        final int halfScreenSize = getMeasuredWidth() / 2;
-
+    private void screenScrolledLargeUI(int screenCenter) {
         for (int i = 0; i < getChildCount(); i++) {
             CellLayout cl = (CellLayout) getChildAt(i);
             if (cl != null) {
-                int totalDistance = getScaledMeasuredWidth(cl) + mPageSpacing;
-                int delta = screenCenter - (getChildOffset(i) -
-                        getRelativeChildOffset(i) + halfScreenSize);
-
-                float scrollProgress = delta / (totalDistance * 1.0f);
-                scrollProgress = Math.min(scrollProgress, 1.0f);
-                scrollProgress = Math.max(scrollProgress, -1.0f);
-
-                if (!isSmall()) {
-                    // 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 scrollProgress = getScrollProgress(screenCenter, cl, i);
                 float rotation = WORKSPACE_ROTATION * scrollProgress;
                 float translationX = getOffsetXForRotation(rotation, cl.getWidth(), cl.getHeight());
-                cl.setTranslationX(translationX);
 
+                // If the current page (i) is being over scrolled, 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)));
+                }
+
+                cl.setTranslationX(translationX);
                 cl.setRotationY(rotation);
             }
         }
     }
 
+    private void resetCellLayoutTransforms(CellLayout cl, boolean left) {
+        cl.setTranslationX(0);
+        cl.setRotationY(0);
+        cl.setOverScrollAmount(0, left);
+        cl.setPivotX(cl.getMeasuredWidth() / 2);
+        cl.setPivotY(cl.getMeasuredHeight() / 2);
+    }
+
+    private void screenScrolledStandardUI(int screenCenter) {
+        if (mScrollX < 0 || mScrollX > mMaxScrollX) {
+            int index = mScrollX < 0 ? 0 : getChildCount() - 1;
+            CellLayout cl = (CellLayout) getChildAt(index);
+            float scrollProgress = getScrollProgress(screenCenter, cl, index);
+            cl.setOverScrollAmount(Math.abs(scrollProgress), index == 0);
+            float translationX = index == 0 ? mScrollX : - (mMaxScrollX - mScrollX);
+            float rotation = - WORKSPACE_OVERSCROLL_ROTATION * scrollProgress;
+            cl.setCameraDistance(mDensity * 6500);
+            cl.setPivotX(cl.getMeasuredWidth() * (index == 0 ? 0.75f : 0.25f));
+            cl.setTranslationX(translationX);
+            cl.setRotationY(rotation);
+        } else {
+            resetCellLayoutTransforms((CellLayout) getChildAt(0), true);
+            resetCellLayoutTransforms((CellLayout) getChildAt(getChildCount() - 1), false);
+        }
+    }
+
+    @Override
+    protected void screenScrolled(int screenCenter) {
+        super.screenScrolled(screenCenter);
+        if (LauncherApplication.isScreenLarge()) {
+            screenScrolledLargeUI(screenCenter);
+        } else {
+            screenScrolledStandardUI(screenCenter);
+        }
+    }
+
+    @Override
+    protected void overScroll(float amount) {
+        if (LauncherApplication.isScreenLarge()) {
+            dampedOverScroll(amount);
+        } else {
+            acceleratedOverScroll(amount);
+        }
+    }
+
     protected void onAttachedToWindow() {
         super.onAttachedToWindow();
         mWindowToken = getWindowToken();