Simplifying fast scroller logic

> Using a separate view for drawing the popup. This allows us to use elevation
  property instead of drawing the shadow as bitmap.
> During the thumb animation, invalidating the full track width, instead of
  invalidating the track and thumb separately.
> The thumb path is calculated at 0,0 and drawn using canvas.translate().
   This avoids recalculating the path on every scroll.

Change-Id: I48741e5b4432df0d939016db284d7aaf52cc2aa6
diff --git a/src/com/android/launcher3/BaseRecyclerView.java b/src/com/android/launcher3/BaseRecyclerView.java
index 45bc940..9bdbe25 100644
--- a/src/com/android/launcher3/BaseRecyclerView.java
+++ b/src/com/android/launcher3/BaseRecyclerView.java
@@ -22,6 +22,8 @@
 import android.support.v7.widget.RecyclerView;
 import android.util.AttributeSet;
 import android.view.MotionEvent;
+import android.view.ViewGroup;
+
 import com.android.launcher3.util.Thunk;
 
 
@@ -41,7 +43,7 @@
     @Thunk int mDy = 0;
     private float mDeltaThreshold;
 
-    protected BaseRecyclerViewFastScrollBar mScrollbar;
+    protected final BaseRecyclerViewFastScrollBar mScrollbar;
 
     private int mDownX;
     private int mDownY;
@@ -92,6 +94,12 @@
         addOnItemTouchListener(this);
     }
 
+    @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        mScrollbar.setPopupView(((ViewGroup) getParent()).findViewById(R.id.fast_scroller_popup));
+    }
+
     /**
      * We intercept the touch handling only to support fast scrolling when initiated from the
      * scroll bar.  Otherwise, we fall back to the default RecyclerView touch handling.
@@ -235,7 +243,7 @@
         // Only show the scrollbar if there is height to be scrolled
         int availableScrollBarHeight = getAvailableScrollBarHeight();
         if (availableScrollHeight <= 0) {
-            mScrollbar.setThumbOffset(-1, -1);
+            mScrollbar.setThumbOffsetY(-1);
             return;
         }
 
@@ -246,18 +254,7 @@
                 (int) (((float) scrollY / availableScrollHeight) * availableScrollBarHeight);
 
         // Calculate the position and size of the scroll bar
-        mScrollbar.setThumbOffset(getScrollBarX(), scrollBarY);
-    }
-
-    /**
-     * @return the x position for the scrollbar thumb
-     */
-    protected int getScrollBarX() {
-        if (Utilities.isRtl(getResources())) {
-            return mBackgroundPadding.left;
-        } else {
-            return getWidth() - mBackgroundPadding.right - mScrollbar.getThumbWidth();
-        }
+        mScrollbar.setThumbOffsetY(scrollBarY);
     }
 
     /**
diff --git a/src/com/android/launcher3/BaseRecyclerViewFastScrollBar.java b/src/com/android/launcher3/BaseRecyclerViewFastScrollBar.java
index 3d71632..770166d 100644
--- a/src/com/android/launcher3/BaseRecyclerViewFastScrollBar.java
+++ b/src/com/android/launcher3/BaseRecyclerViewFastScrollBar.java
@@ -15,21 +15,18 @@
  */
 package com.android.launcher3;
 
-import android.animation.AnimatorSet;
-import android.animation.ArgbEvaluator;
 import android.animation.ObjectAnimator;
-import android.animation.ValueAnimator;
 import android.content.res.Resources;
 import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.Paint;
 import android.graphics.Path;
-import android.graphics.Point;
 import android.graphics.Rect;
+import android.util.Property;
 import android.view.MotionEvent;
+import android.view.View;
 import android.view.ViewConfiguration;
-
-import com.android.launcher3.util.Thunk;
+import android.widget.TextView;
 
 /**
  * The track and scrollbar that shows when you scroll the list.
@@ -40,29 +37,46 @@
         void setFastScrollFocusState(final FastBitmapDrawable.State focusState, boolean animated);
     }
 
+    private static final Property<BaseRecyclerViewFastScrollBar, Integer> TRACK_WIDTH =
+            new Property<BaseRecyclerViewFastScrollBar, Integer>(Integer.class, "width") {
+
+                @Override
+                public Integer get(BaseRecyclerViewFastScrollBar scrollBar) {
+                    return scrollBar.mWidth;
+                }
+
+                @Override
+                public void set(BaseRecyclerViewFastScrollBar scrollBar, Integer value) {
+                    scrollBar.setTrackWidth(value);
+                }
+            };
+
     private final static int MAX_TRACK_ALPHA = 30;
     private final static int SCROLL_BAR_VIS_DURATION = 150;
+    private static final float FAST_SCROLL_OVERLAY_Y_OFFSET_FACTOR = 1.5f;
 
-    @Thunk BaseRecyclerView mRv;
-    private BaseRecyclerViewFastScrollPopup mPopup;
+    private final Rect mTmpRect = new Rect();
+    private final BaseRecyclerView mRv;
 
-    private AnimatorSet mScrollbarAnimator;
+    private final boolean mIsRtl;
 
-    private int mThumbInactiveColor;
-    private int mThumbActiveColor;
-    @Thunk Point mThumbOffset = new Point(-1, -1);
-    @Thunk Paint mThumbPaint;
-    private int mThumbMinWidth;
-    private int mThumbMaxWidth;
-    @Thunk int mThumbWidth;
-    @Thunk int mThumbHeight;
-    private int mThumbCurvature;
-    private Path mThumbPath = new Path();
-    private Paint mTrackPaint;
-    private int mTrackWidth;
-    private float mLastTouchY;
     // The inset is the buffer around which a point will still register as a click on the scrollbar
-    private int mTouchInset;
+    private final int mTouchInset;
+
+    private final int mMinWidth;
+    private final int mMaxWidth;
+
+    // Current width of the track
+    private int mWidth;
+    private ObjectAnimator mWidthAnimator;
+
+    private final Path mThumbPath = new Path();
+    private final Paint mThumbPaint;
+    private final int mThumbHeight;
+
+    private final Paint mTrackPaint;
+
+    private float mLastTouchY;
     private boolean mIsDragging;
     private boolean mIsThumbDetached;
     private boolean mCanThumbDetach;
@@ -70,27 +84,35 @@
 
     // This is the offset from the top of the scrollbar when the user first starts touching.  To
     // prevent jumping, this offset is applied as the user scrolls.
-    private int mTouchOffset;
+    private int mTouchOffsetY;
+    private int mThumbOffsetY;
 
-    private Rect mInvalidateRect = new Rect();
-    private Rect mTmpRect = new Rect();
+    // Fast scroller popup
+    private TextView mPopupView;
+    private boolean mPopupVisible;
+    private String mPopupSectionName;
 
     public BaseRecyclerViewFastScrollBar(BaseRecyclerView rv, Resources res) {
         mRv = rv;
-        mPopup = new BaseRecyclerViewFastScrollPopup(rv, res);
         mTrackPaint = new Paint();
         mTrackPaint.setColor(rv.getFastScrollerTrackColor(Color.BLACK));
         mTrackPaint.setAlpha(MAX_TRACK_ALPHA);
-        mThumbActiveColor = mThumbInactiveColor = Utilities.getColorAccent(rv.getContext());
+
         mThumbPaint = new Paint();
         mThumbPaint.setAntiAlias(true);
-        mThumbPaint.setColor(mThumbInactiveColor);
+        mThumbPaint.setColor(Utilities.getColorAccent(rv.getContext()));
         mThumbPaint.setStyle(Paint.Style.FILL);
-        mThumbWidth = mThumbMinWidth = res.getDimensionPixelSize(R.dimen.container_fastscroll_thumb_min_width);
-        mThumbMaxWidth = res.getDimensionPixelSize(R.dimen.container_fastscroll_thumb_max_width);
+
+        mWidth = mMinWidth = res.getDimensionPixelSize(R.dimen.container_fastscroll_thumb_min_width);
+        mMaxWidth = res.getDimensionPixelSize(R.dimen.container_fastscroll_thumb_max_width);
         mThumbHeight = res.getDimensionPixelSize(R.dimen.container_fastscroll_thumb_height);
-        mThumbCurvature = mThumbMaxWidth - mThumbMinWidth;
         mTouchInset = res.getDimensionPixelSize(R.dimen.container_fastscroll_thumb_touch_inset);
+        mIsRtl = Utilities.isRtl(res);
+        updateThumbPath();
+    }
+
+    public void setPopupView(View popup) {
+        mPopupView = (TextView) popup;
     }
 
     public void setDetachThumbOnFastScroll() {
@@ -101,51 +123,54 @@
         mIsThumbDetached = false;
     }
 
-    public void setThumbOffset(int x, int y) {
-        if (mThumbOffset.x == x && mThumbOffset.y == y) {
+    private int getDrawLeft() {
+        return mIsRtl ? 0 : (mRv.getWidth() - mMaxWidth);
+    }
+
+    public void setThumbOffsetY(int y) {
+        if (mThumbOffsetY == y) {
             return;
         }
-        mInvalidateRect.set(mThumbOffset.x - mThumbCurvature, mThumbOffset.y,
-                mThumbOffset.x + mThumbWidth, mThumbOffset.y + mThumbHeight);
-        mThumbOffset.set(x, y);
+
+        // Invalidate the previous and new thumb area
+        int drawLeft = getDrawLeft();
+        mTmpRect.set(drawLeft, mThumbOffsetY, drawLeft + mMaxWidth, mThumbOffsetY + mThumbHeight);
+        mThumbOffsetY = y;
+        mTmpRect.union(drawLeft, mThumbOffsetY, drawLeft + mMaxWidth, mThumbOffsetY + mThumbHeight);
+        mRv.invalidate(mTmpRect);
+    }
+
+    public int getThumbOffsetY() {
+        return mThumbOffsetY;
+    }
+
+    private void setTrackWidth(int width) {
+        if (mWidth == width) {
+            return;
+        }
+        int left = getDrawLeft();
+        // Invalidate the whole scroll bar area.
+        mRv.invalidate(left, 0, left + mMaxWidth, mRv.getVisibleHeight());
+
+        mWidth = width;
         updateThumbPath();
-        mInvalidateRect.union(mThumbOffset.x - mThumbCurvature, mThumbOffset.y,
-                mThumbOffset.x + mThumbWidth, mThumbOffset.y + mThumbHeight);
-        mRv.invalidate(mInvalidateRect);
     }
 
-    public Point getThumbOffset() {
-        return mThumbOffset;
-    }
+    /**
+     * Updates the path for the thumb drawable.
+     */
+    private void updateThumbPath() {
+        int smallWidth = mIsRtl ? mWidth : -mWidth;
+        int largeWidth = mIsRtl ? mMaxWidth : -mMaxWidth;
 
-    // Setter/getter for the thumb bar width for animations
-    public void setThumbWidth(int width) {
-        mInvalidateRect.set(mThumbOffset.x - mThumbCurvature, mThumbOffset.y,
-                mThumbOffset.x + mThumbWidth, mThumbOffset.y + mThumbHeight);
-        mThumbWidth = width;
-        updateThumbPath();
-        mInvalidateRect.union(mThumbOffset.x - mThumbCurvature, mThumbOffset.y,
-                mThumbOffset.x + mThumbWidth, mThumbOffset.y + mThumbHeight);
-        mRv.invalidate(mInvalidateRect);
-    }
-
-    public int getThumbWidth() {
-        return mThumbWidth;
-    }
-
-    // Setter/getter for the track bar width for animations
-    public void setTrackWidth(int width) {
-        mInvalidateRect.set(mThumbOffset.x - mThumbCurvature, 0, mThumbOffset.x + mThumbWidth,
-                mRv.getVisibleHeight());
-        mTrackWidth = width;
-        updateThumbPath();
-        mInvalidateRect.union(mThumbOffset.x - mThumbCurvature, 0, mThumbOffset.x + mThumbWidth,
-                mRv.getVisibleHeight());
-        mRv.invalidate(mInvalidateRect);
-    }
-
-    public int getTrackWidth() {
-        return mTrackWidth;
+        mThumbPath.reset();
+        mThumbPath.moveTo(0, 0);
+        mThumbPath.lineTo(0, mThumbHeight);             // Left edge
+        mThumbPath.lineTo(smallWidth, mThumbHeight);    // bottom edge
+        mThumbPath.cubicTo(smallWidth, mThumbHeight,    // right edge
+                largeWidth, mThumbHeight / 2,
+                smallWidth, 0);
+        mThumbPath.close();
     }
 
     public int getThumbHeight() {
@@ -153,7 +178,7 @@
     }
 
     public int getThumbMaxWidth() {
-        return mThumbMaxWidth;
+        return mMaxWidth;
     }
 
     public boolean isDraggingThumb() {
@@ -176,7 +201,7 @@
         switch (action) {
             case MotionEvent.ACTION_DOWN:
                 if (isNearThumb(downX, downY)) {
-                    mTouchOffset = downY - mThumbOffset.y;
+                    mTouchOffsetY = downY - mThumbOffsetY;
                 }
                 break;
             case MotionEvent.ACTION_MOVE:
@@ -191,32 +216,35 @@
                     if (mCanThumbDetach) {
                         mIsThumbDetached = true;
                     }
-                    mTouchOffset += (lastY - downY);
-                    mPopup.animateVisibility(true);
+                    mTouchOffsetY += (lastY - downY);
+                    animatePopupVisibility(true);
                     showActiveScrollbar(true);
                 }
                 if (mIsDragging) {
                     // Update the fastscroller section name at this touch position
                     int top = mRv.getBackgroundPadding().top;
                     int bottom = top + mRv.getVisibleHeight() - mThumbHeight;
-                    float boundedY = (float) Math.max(top, Math.min(bottom, y - mTouchOffset));
+                    float boundedY = (float) Math.max(top, Math.min(bottom, y - mTouchOffsetY));
                     String sectionName = mRv.scrollToPositionAtProgress((boundedY - top) /
                             (bottom - top));
-                    mPopup.setSectionName(sectionName);
-                    mPopup.animateVisibility(!sectionName.isEmpty());
-                    mRv.invalidate(mPopup.updateFastScrollerBounds(lastY));
+                    if (!sectionName.equals(mPopupSectionName)) {
+                        mPopupSectionName = sectionName;
+                        mPopupView.setText(sectionName);
+                    }
+                    animatePopupVisibility(!sectionName.isEmpty());
+                    updatePopupY(lastY);
                     mLastTouchY = boundedY;
-                    setThumbOffset(mRv.getScrollBarX(), (int) mLastTouchY);
+                    setThumbOffsetY((int) mLastTouchY);
                 }
                 break;
             case MotionEvent.ACTION_UP:
             case MotionEvent.ACTION_CANCEL:
-                mTouchOffset = 0;
+                mTouchOffsetY = 0;
                 mLastTouchY = 0;
                 mIgnoreDragGesture = false;
                 if (mIsDragging) {
                     mIsDragging = false;
-                    mPopup.animateVisibility(false);
+                    animatePopupVisibility(false);
                     showActiveScrollbar(false);
                 }
                 break;
@@ -224,74 +252,58 @@
     }
 
     public void draw(Canvas canvas) {
-        if (mThumbOffset.x < 0 || mThumbOffset.y < 0) {
+        if (mThumbOffsetY < 0) {
             return;
         }
-
-        // Draw the scroll bar track and thumb
-        if (mTrackPaint.getAlpha() > 0) {
-            canvas.drawRect(mThumbOffset.x, 0, mThumbOffset.x + mThumbWidth,
-                    mRv.getVisibleHeight(), mTrackPaint);
+        int saveCount = canvas.save(Canvas.MATRIX_SAVE_FLAG);
+        if (!mIsRtl) {
+            canvas.translate(mRv.getWidth(), 0);
         }
-        canvas.drawPath(mThumbPath, mThumbPaint);
+        // Draw the track
+        int thumbWidth = mIsRtl ? mWidth : -mWidth;
+        canvas.drawRect(0, 0, thumbWidth, mRv.getVisibleHeight(), mTrackPaint);
 
-        // Draw the popup
-        mPopup.draw(canvas);
+        canvas.translate(0, mThumbOffsetY);
+        canvas.drawPath(mThumbPath, mThumbPaint);
+        canvas.restoreToCount(saveCount);
     }
 
     /**
-     * Animates the width and color of the scrollbar.
+     * Animates the width of the scrollbar.
      */
     private void showActiveScrollbar(boolean isScrolling) {
-        if (mScrollbarAnimator != null) {
-            mScrollbarAnimator.cancel();
+        if (mWidthAnimator != null) {
+            mWidthAnimator.cancel();
         }
 
-        mScrollbarAnimator = new AnimatorSet();
-        ObjectAnimator trackWidthAnim = ObjectAnimator.ofInt(this, "trackWidth",
-                isScrolling ? mThumbMaxWidth : mThumbMinWidth);
-        ObjectAnimator thumbWidthAnim = ObjectAnimator.ofInt(this, "thumbWidth",
-                isScrolling ? mThumbMaxWidth : mThumbMinWidth);
-        mScrollbarAnimator.playTogether(trackWidthAnim, thumbWidthAnim);
-        if (mThumbActiveColor != mThumbInactiveColor) {
-            ValueAnimator colorAnimation = ValueAnimator.ofObject(new ArgbEvaluator(),
-                    mThumbPaint.getColor(), isScrolling ? mThumbActiveColor : mThumbInactiveColor);
-            colorAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
-                @Override
-                public void onAnimationUpdate(ValueAnimator animator) {
-                    mThumbPaint.setColor((Integer) animator.getAnimatedValue());
-                    mRv.invalidate(mThumbOffset.x, mThumbOffset.y, mThumbOffset.x + mThumbWidth,
-                            mThumbOffset.y + mThumbHeight);
-                }
-            });
-            mScrollbarAnimator.play(colorAnimation);
-        }
-        mScrollbarAnimator.setDuration(SCROLL_BAR_VIS_DURATION);
-        mScrollbarAnimator.start();
-    }
-
-    /**
-     * Updates the path for the thumb drawable.
-     */
-    private void updateThumbPath() {
-        mThumbCurvature = mThumbMaxWidth - mThumbWidth;
-        mThumbPath.reset();
-        mThumbPath.moveTo(mThumbOffset.x + mThumbWidth, mThumbOffset.y);                    // tr
-        mThumbPath.lineTo(mThumbOffset.x + mThumbWidth, mThumbOffset.y + mThumbHeight);     // br
-        mThumbPath.lineTo(mThumbOffset.x, mThumbOffset.y + mThumbHeight);                   // bl
-        mThumbPath.cubicTo(mThumbOffset.x, mThumbOffset.y + mThumbHeight,
-                mThumbOffset.x - mThumbCurvature, mThumbOffset.y + mThumbHeight / 2,
-                mThumbOffset.x, mThumbOffset.y);                                            // bl2tl
-        mThumbPath.close();
+        mWidthAnimator = ObjectAnimator.ofInt(this, TRACK_WIDTH,
+                isScrolling ? mMaxWidth : mMinWidth);
+        mWidthAnimator.setDuration(SCROLL_BAR_VIS_DURATION);
+        mWidthAnimator.start();
     }
 
     /**
      * Returns whether the specified points are near the scroll bar bounds.
      */
     public boolean isNearThumb(int x, int y) {
-        mTmpRect.set(mThumbOffset.x, mThumbOffset.y, mThumbOffset.x + mThumbWidth,
-                mThumbOffset.y + mThumbHeight);
+        int left = getDrawLeft();
+        mTmpRect.set(left, mThumbOffsetY, left + mMaxWidth, mThumbOffsetY + mThumbHeight);
         mTmpRect.inset(mTouchInset, mTouchInset);
         return mTmpRect.contains(x, y);
     }
+
+    private void animatePopupVisibility(boolean visible) {
+        if (mPopupVisible != visible) {
+            mPopupVisible = visible;
+            mPopupView.animate().cancel();
+            mPopupView.animate().alpha(visible ? 1f : 0f).setDuration(visible ? 200 : 150).start();
+        }
+    }
+
+    private void updatePopupY(int lastTouchY) {
+        int height = mPopupView.getHeight();
+        float top = lastTouchY - (FAST_SCROLL_OVERLAY_Y_OFFSET_FACTOR * height);
+        top = Math.max(mMaxWidth, Math.min(top, mRv.getVisibleHeight() - mMaxWidth - height));
+        mPopupView.setTranslationY(top);
+    }
 }
diff --git a/src/com/android/launcher3/BaseRecyclerViewFastScrollPopup.java b/src/com/android/launcher3/BaseRecyclerViewFastScrollPopup.java
deleted file mode 100644
index b9b044d..0000000
--- a/src/com/android/launcher3/BaseRecyclerViewFastScrollPopup.java
+++ /dev/null
@@ -1,198 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.launcher3;
-
-import android.animation.Animator;
-import android.animation.ObjectAnimator;
-import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Paint;
-import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
-
-import com.android.launcher3.graphics.HolographicOutlineHelper;
-
-/**
- * The fast scroller popup that shows the section name the list will jump to.
- */
-public class BaseRecyclerViewFastScrollPopup {
-
-    private static final float FAST_SCROLL_OVERLAY_Y_OFFSET_FACTOR = 1.5f;
-
-    private static final int SHADOW_INSET = 3;
-    private static final int SHADOW_SHIFT_Y = 2;
-    private static final float SHADOW_ALPHA_MULTIPLIER = 0.67f;
-
-    private Resources mRes;
-    private BaseRecyclerView mRv;
-
-    private Bitmap mShadow;
-    private Paint mShadowPaint;
-
-    private Drawable mBg;
-    // The absolute bounds of the fast scroller bg
-    private Rect mBgBounds = new Rect();
-    private int mBgOriginalSize;
-    private Rect mInvalidateRect = new Rect();
-    private Rect mTmpRect = new Rect();
-
-    private String mSectionName;
-    private Paint mTextPaint;
-    private Rect mTextBounds = new Rect();
-    private float mAlpha;
-
-    private Animator mAlphaAnimator;
-    private boolean mVisible;
-
-    public BaseRecyclerViewFastScrollPopup(BaseRecyclerView rv, Resources res) {
-        mRes = res;
-        mRv = rv;
-
-        mBgOriginalSize = res.getDimensionPixelSize(R.dimen.container_fastscroll_popup_size);
-        mBg = rv.getContext().getDrawable(R.drawable.container_fastscroll_popup_bg);
-        mBg.setBounds(0, 0, mBgOriginalSize, mBgOriginalSize);
-
-        mTextPaint = new Paint();
-        mTextPaint.setColor(Color.WHITE);
-        mTextPaint.setAntiAlias(true);
-        mTextPaint.setTextSize(res.getDimensionPixelSize(R.dimen.container_fastscroll_popup_text_size));
-
-        mShadowPaint = new Paint();
-        mShadowPaint.setAntiAlias(true);
-        mShadowPaint.setFilterBitmap(true);
-        mShadowPaint.setDither(true);
-    }
-
-    /**
-     * Sets the section name.
-     */
-    public void setSectionName(String sectionName) {
-        if (!sectionName.equals(mSectionName)) {
-            mSectionName = sectionName;
-            mTextPaint.getTextBounds(sectionName, 0, sectionName.length(), mTextBounds);
-            // Update the width to use measureText since that is more accurate
-            mTextBounds.right = (int) (mTextBounds.left + mTextPaint.measureText(sectionName));
-        }
-    }
-
-    /**
-     * Updates the bounds for the fast scroller.
-     *
-     * @return the invalidation rect for this update.
-     */
-    public Rect updateFastScrollerBounds(int lastTouchY) {
-        mInvalidateRect.set(mBgBounds);
-
-        if (isVisible()) {
-            // Calculate the dimensions and position of the fast scroller popup
-            int edgePadding = mRv.getMaxScrollbarWidth();
-            int bgPadding = (mBgOriginalSize - mTextBounds.height()) / 2;
-            int bgHeight = mBgOriginalSize;
-            int bgWidth = Math.max(mBgOriginalSize, mTextBounds.width() + (2 * bgPadding));
-            if (Utilities.isRtl(mRes)) {
-                mBgBounds.left = mRv.getBackgroundPadding().left + (2 * mRv.getMaxScrollbarWidth());
-                mBgBounds.right = mBgBounds.left + bgWidth;
-            } else {
-                mBgBounds.right = mRv.getWidth() - mRv.getBackgroundPadding().right -
-                        (2 * mRv.getMaxScrollbarWidth());
-                mBgBounds.left = mBgBounds.right - bgWidth;
-            }
-            mBgBounds.top = lastTouchY - (int) (FAST_SCROLL_OVERLAY_Y_OFFSET_FACTOR * bgHeight);
-            mBgBounds.top = Math.max(edgePadding,
-                    Math.min(mBgBounds.top, mRv.getVisibleHeight() - edgePadding - bgHeight));
-            mBgBounds.bottom = mBgBounds.top + bgHeight;
-
-            // Generate a bitmap for a shadow matching these bounds
-            mShadow = HolographicOutlineHelper.getInstance(
-                    mRv.getContext()).createMediumDropShadow(mBg, false /* shouldCache */);
-        } else {
-            mShadow = null;
-            mBgBounds.setEmpty();
-        }
-
-        // Combine the old and new fast scroller bounds to create the full invalidate rect
-        mInvalidateRect.union(mBgBounds);
-        return mInvalidateRect;
-    }
-
-    /**
-     * Animates the visibility of the fast scroller popup.
-     */
-    public void animateVisibility(boolean visible) {
-        if (mVisible != visible) {
-            mVisible = visible;
-            if (mAlphaAnimator != null) {
-                mAlphaAnimator.cancel();
-            }
-            mAlphaAnimator = ObjectAnimator.ofFloat(this, "alpha", visible ? 1f : 0f);
-            mAlphaAnimator.setDuration(visible ? 200 : 150);
-            mAlphaAnimator.start();
-        }
-    }
-
-    // Setter/getter for the popup alpha for animations
-    public void setAlpha(float alpha) {
-        mAlpha = alpha;
-        mRv.invalidate(mBgBounds);
-    }
-
-    public float getAlpha() {
-        return mAlpha;
-    }
-
-    public int getHeight() {
-        return mBgOriginalSize;
-    }
-
-    public void draw(Canvas c) {
-        if (isVisible()) {
-            // Determine the alpha and prepare the canvas
-            final int alpha = (int) (mAlpha * 255);
-            int restoreCount = c.save(Canvas.MATRIX_SAVE_FLAG);
-            c.translate(mBgBounds.left, mBgBounds.top);
-            mTmpRect.set(mBgBounds);
-            mTmpRect.offsetTo(0, 0);
-
-            // Expand the rect (with a negative inset), translate it, and draw the shadow
-            if (mShadow != null) {
-                mTmpRect.inset(-SHADOW_INSET * 2, -SHADOW_INSET * 2);
-                mTmpRect.offset(0, SHADOW_SHIFT_Y);
-                mShadowPaint.setAlpha((int) (alpha * SHADOW_ALPHA_MULTIPLIER));
-                c.drawBitmap(mShadow, null, mTmpRect, mShadowPaint);
-                mTmpRect.inset(SHADOW_INSET * 2, SHADOW_INSET * 2);
-                mTmpRect.offset(0, -SHADOW_SHIFT_Y);
-            }
-
-            // Draw the background
-            mBg.setBounds(mTmpRect);
-            mBg.setAlpha(alpha);
-            mBg.draw(c);
-
-            // Draw the text
-            mTextPaint.setAlpha(alpha);
-            c.drawText(mSectionName, (mBgBounds.width() - mTextBounds.width()) / 2,
-                    mBgBounds.height() - (mBgBounds.height() / 2) - mTextBounds.exactCenterY(),
-                    mTextPaint);
-            c.restoreToCount(restoreCount);
-        }
-    }
-
-    public boolean isVisible() {
-        return (mAlpha > 0f) && (mSectionName != null);
-    }
-}
diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java
index 689ee38..5c9e3f7 100644
--- a/src/com/android/launcher3/allapps/AllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java
@@ -272,7 +272,7 @@
 
         // IF scroller is at the very top OR there is no scroll bar because there is probably not
         // enough items to scroll, THEN it's okay for the container to be pulled down.
-        if (mAppsRecyclerView.getScrollBar().getThumbOffset().y <= 0) {
+        if (mAppsRecyclerView.getScrollBar().getThumbOffsetY() <= 0) {
             return true;
         }
         return false;
diff --git a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
index 0173847..5a9ea6b 100644
--- a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
@@ -299,14 +299,14 @@
 
         // Skip early if there are no items or we haven't been measured
         if (items.isEmpty() || mNumAppsPerRow == 0) {
-            mScrollbar.setThumbOffset(-1, -1);
+            mScrollbar.setThumbOffsetY(-1);
             return;
         }
 
         // Skip early if, there no child laid out in the container.
         int scrollY = getCurrentScrollY();
         if (scrollY < 0) {
-            mScrollbar.setThumbOffset(-1, -1);
+            mScrollbar.setThumbOffsetY(-1);
             return;
         }
 
@@ -314,7 +314,7 @@
         int availableScrollBarHeight = getAvailableScrollBarHeight();
         int availableScrollHeight = getAvailableScrollHeight();
         if (availableScrollHeight <= 0) {
-            mScrollbar.setThumbOffset(-1, -1);
+            mScrollbar.setThumbOffsetY(-1);
             return;
         }
 
@@ -323,11 +323,10 @@
                 // Calculate the current scroll position, the scrollY of the recycler view accounts
                 // for the view padding, while the scrollBarY is drawn right up to the background
                 // padding (ignoring padding)
-                int scrollBarX = getScrollBarX();
                 int scrollBarY = mBackgroundPadding.top +
                         (int) (((float) scrollY / availableScrollHeight) * availableScrollBarHeight);
 
-                int thumbScrollY = mScrollbar.getThumbOffset().y;
+                int thumbScrollY = mScrollbar.getThumbOffsetY();
                 int diffScrollY = scrollBarY - thumbScrollY;
                 if (diffScrollY * dy > 0f) {
                     // User is scrolling in the same direction the thumb needs to catch up to the
@@ -344,7 +343,7 @@
                         thumbScrollY += Math.min(offset, diffScrollY);
                     }
                     thumbScrollY = Math.max(0, Math.min(availableScrollBarHeight, thumbScrollY));
-                    mScrollbar.setThumbOffset(scrollBarX, thumbScrollY);
+                    mScrollbar.setThumbOffsetY(thumbScrollY);
                     if (scrollBarY == thumbScrollY) {
                         mScrollbar.reattachThumbToScroll();
                     }
@@ -352,7 +351,7 @@
                     // User is scrolling in an opposite direction to the direction that the thumb
                     // needs to catch up to the scroll position.  Do nothing except for updating
                     // the scroll bar x to match the thumb width.
-                    mScrollbar.setThumbOffset(scrollBarX, thumbScrollY);
+                    mScrollbar.setThumbOffsetY(thumbScrollY);
                 }
             }
         } else {
diff --git a/src/com/android/launcher3/graphics/HolographicOutlineHelper.java b/src/com/android/launcher3/graphics/HolographicOutlineHelper.java
index 0d70bde..9c39721 100644
--- a/src/com/android/launcher3/graphics/HolographicOutlineHelper.java
+++ b/src/com/android/launcher3/graphics/HolographicOutlineHelper.java
@@ -158,18 +158,13 @@
     }
 
     public Bitmap createMediumDropShadow(BubbleTextView view) {
-        return createMediumDropShadow(view.getIcon(), view.getScaleX(), view.getScaleY(), true);
-    }
-
-    public Bitmap createMediumDropShadow(Drawable drawable, boolean shouldCache) {
-        return createMediumDropShadow(drawable, 1f, 1f, shouldCache);
-    }
-
-    Bitmap createMediumDropShadow(Drawable drawable, float scaleX, float scaleY,
-                boolean shouldCache) {
+        Drawable drawable = view.getIcon();
         if (drawable == null) {
             return null;
         }
+
+        float scaleX = view.getScaleX();
+        float scaleY = view.getScaleY();
         Rect rect = drawable.getBounds();
 
         int bitmapWidth = (int) (rect.width() * scaleX);
@@ -179,14 +174,11 @@
         }
 
         int key = (bitmapWidth << 16) | bitmapHeight;
-        Bitmap cache = shouldCache ? mBitmapCache.get(key) : null;
+        Bitmap cache = mBitmapCache.get(key);
         if (cache == null) {
             cache = Bitmap.createBitmap(bitmapWidth, bitmapHeight, Bitmap.Config.ALPHA_8);
             mCanvas.setBitmap(cache);
-
-            if (shouldCache) {
-                mBitmapCache.put(key, cache);
-            }
+            mBitmapCache.put(key, cache);
         } else {
             mCanvas.setBitmap(cache);
             mCanvas.drawColor(Color.BLACK, PorterDuff.Mode.CLEAR);
@@ -206,7 +198,7 @@
         int resultWidth = bitmapWidth + extraSize;
         int resultHeight = bitmapHeight + extraSize;
         key = (resultWidth << 16) | resultHeight;
-        Bitmap result = shouldCache ? mBitmapCache.get(key) : null;
+        Bitmap result = mBitmapCache.get(key);
         if (result == null) {
             result = Bitmap.createBitmap(resultWidth, resultHeight, Bitmap.Config.ALPHA_8);
             mCanvas.setBitmap(result);
diff --git a/src/com/android/launcher3/widget/WidgetsRecyclerView.java b/src/com/android/launcher3/widget/WidgetsRecyclerView.java
index 2560661..c33978e 100644
--- a/src/com/android/launcher3/widget/WidgetsRecyclerView.java
+++ b/src/com/android/launcher3/widget/WidgetsRecyclerView.java
@@ -121,7 +121,7 @@
         // Skip early if, there no child laid out in the container.
         int scrollY = getCurrentScrollY();
         if (scrollY < 0) {
-            mScrollbar.setThumbOffset(-1, -1);
+            mScrollbar.setThumbOffsetY(-1);
             return;
         }