summaryrefslogtreecommitdiffstats
path: root/src/com/android/launcher3/BaseRecyclerView.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/android/launcher3/BaseRecyclerView.java')
-rw-r--r--src/com/android/launcher3/BaseRecyclerView.java272
1 files changed, 265 insertions, 7 deletions
diff --git a/src/com/android/launcher3/BaseRecyclerView.java b/src/com/android/launcher3/BaseRecyclerView.java
index b63ef788a..8d418f984 100644
--- a/src/com/android/launcher3/BaseRecyclerView.java
+++ b/src/com/android/launcher3/BaseRecyclerView.java
@@ -16,15 +16,28 @@
package com.android.launcher3;
+import android.animation.ObjectAnimator;
import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;
import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewConfiguration;
+
import com.android.launcher3.util.Thunk;
/**
- * A base {@link RecyclerView}, which will NOT intercept a touch sequence unless the scrolling
- * velocity is below a predefined threshold.
+ * A base {@link RecyclerView}, which does the following:
+ * <ul>
+ * <li> NOT intercept a touch unless the scrolling velocity is below a predefined threshold.
+ * <li> Enable fast scroller.
+ * </ul>
*/
public class BaseRecyclerView extends RecyclerView
implements RecyclerView.OnItemTouchListener {
@@ -35,6 +48,53 @@ public class BaseRecyclerView extends RecyclerView
@Thunk int mDy = 0;
private float mDeltaThreshold;
+ //
+ // Keeps track of variables required for the second function of this class: fast scroller.
+ //
+
+ private static final float FAST_SCROLL_OVERLAY_Y_OFFSET_FACTOR = 1.5f;
+
+ /**
+ * The current scroll state of the recycler view. We use this in updateVerticalScrollbarBounds()
+ * and scrollToPositionAtProgress() to determine the scroll position of the recycler view so
+ * that we can calculate what the scroll bar looks like, and where to jump to from the fast
+ * scroller.
+ */
+ public static class ScrollPositionState {
+ // The index of the first visible row
+ public int rowIndex;
+ // The offset of the first visible row
+ public int rowTopOffset;
+ // The height of a given row (they are currently all the same height)
+ public int rowHeight;
+ }
+ // Should be maintained inside overriden method #updateVerticalScrollbarBounds
+ public ScrollPositionState scrollPosState = new ScrollPositionState();
+ public Rect verticalScrollbarBounds = new Rect();
+
+ private boolean mDraggingFastScroller;
+
+ private Drawable mScrollbar;
+ private Drawable mFastScrollerBg;
+ private Rect mTmpFastScrollerInvalidateRect = new Rect();
+ private Rect mFastScrollerBounds = new Rect();
+
+ private String mFastScrollSectionName;
+ private Paint mFastScrollTextPaint;
+ private Rect mFastScrollTextBounds = new Rect();
+ private float mFastScrollAlpha;
+
+ private int mDownX;
+ private int mDownY;
+ private int mLastX;
+ private int mLastY;
+ private int mScrollbarWidth;
+ private int mScrollbarMinHeight;
+ private int mScrollbarInset;
+ private Rect mBackgroundPadding = new Rect();
+
+
+
public BaseRecyclerView(Context context) {
this(context, null);
}
@@ -49,6 +109,24 @@ public class BaseRecyclerView extends RecyclerView
ScrollListener listener = new ScrollListener();
setOnScrollListener(listener);
+
+ Resources res = context.getResources();
+ int fastScrollerSize = res.getDimensionPixelSize(R.dimen.all_apps_fast_scroll_popup_size);
+ mScrollbar = res.getDrawable(R.drawable.all_apps_scrollbar_thumb);
+ mFastScrollerBg = res.getDrawable(R.drawable.all_apps_fastscroll_bg);
+ mFastScrollerBg.setBounds(0, 0, fastScrollerSize, fastScrollerSize);
+ mFastScrollTextPaint = new Paint();
+ mFastScrollTextPaint.setColor(Color.WHITE);
+ mFastScrollTextPaint.setAntiAlias(true);
+ mFastScrollTextPaint.setTextSize(res.getDimensionPixelSize(
+ R.dimen.all_apps_fast_scroll_text_size));
+ mScrollbarWidth = res.getDimensionPixelSize(R.dimen.all_apps_fast_scroll_bar_width);
+ mScrollbarMinHeight =
+ res.getDimensionPixelSize(R.dimen.all_apps_fast_scroll_bar_min_height);
+ mScrollbarInset =
+ res.getDimensionPixelSize(R.dimen.all_apps_fast_scroll_scrubber_touch_inset);
+ setFastScrollerAlpha(mFastScrollAlpha);
+ setOverScrollMode(View.OVER_SCROLL_NEVER);
}
private class ScrollListener extends OnScrollListener {
@@ -68,17 +146,74 @@ public class BaseRecyclerView extends RecyclerView
addOnItemTouchListener(this);
}
+ /**
+ * 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.
+ */
@Override
public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent ev) {
- if (shouldStopScroll(ev)) {
- stopScroll();
- }
- return false;
+ return handleTouchEvent(ev);
}
@Override
public void onTouchEvent(RecyclerView rv, MotionEvent ev) {
- // Do nothing.
+ handleTouchEvent(ev);
+ }
+
+ /**
+ * Handles the touch event and determines whether to show the fast scroller (or updates it if
+ * it is already showing).
+ */
+ private boolean handleTouchEvent(MotionEvent ev) {
+ ViewConfiguration config = ViewConfiguration.get(getContext());
+
+ int action = ev.getAction();
+ int x = (int) ev.getX();
+ int y = (int) ev.getY();
+ switch (action) {
+ case MotionEvent.ACTION_DOWN:
+ // Keep track of the down positions
+ mDownX = mLastX = x;
+ mDownY = mLastY = y;
+ if (shouldStopScroll(ev)) {
+ stopScroll();
+ }
+ break;
+ case MotionEvent.ACTION_MOVE:
+ // Check if we are scrolling
+ if (!mDraggingFastScroller && isPointNearScrollbar(mDownX, mDownY) &&
+ Math.abs(y - mDownY) > config.getScaledTouchSlop()) {
+ getParent().requestDisallowInterceptTouchEvent(true);
+ mDraggingFastScroller = true;
+ animateFastScrollerVisibility(true);
+ }
+ if (mDraggingFastScroller) {
+ mLastX = x;
+ mLastY = y;
+
+ // Scroll to the right position, and update the section name
+ int top = getPaddingTop() + (mFastScrollerBg.getBounds().height() / 2);
+ int bottom = getHeight() - getPaddingBottom() -
+ (mFastScrollerBg.getBounds().height() / 2);
+ float boundedY = (float) Math.max(top, Math.min(bottom, y));
+ mFastScrollSectionName = scrollToPositionAtProgress((boundedY - top) /
+ (bottom - top));
+
+ // Combine the old and new fast scroller bounds to create the full invalidate
+ // rect
+ mTmpFastScrollerInvalidateRect.set(mFastScrollerBounds);
+ updateFastScrollerBounds();
+ mTmpFastScrollerInvalidateRect.union(mFastScrollerBounds);
+ invalidateFastScroller(mTmpFastScrollerInvalidateRect);
+ }
+ break;
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_CANCEL:
+ mDraggingFastScroller = false;
+ animateFastScrollerVisibility(false);
+ break;
+ }
+ return mDraggingFastScroller;
}
public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
@@ -99,4 +234,127 @@ public class BaseRecyclerView extends RecyclerView
}
return false;
}
+
+ @Override
+ protected void dispatchDraw(Canvas canvas) {
+ super.dispatchDraw(canvas);
+ drawVerticalScrubber(canvas);
+ drawFastScrollerPopup(canvas);
+ }
+
+ /**
+ * Draws the vertical scrollbar.
+ */
+ private void drawVerticalScrubber(Canvas canvas) {
+ updateVerticalScrollbarBounds();
+
+ // Draw the scroll bar
+ int restoreCount = canvas.save(Canvas.MATRIX_SAVE_FLAG);
+ canvas.translate(verticalScrollbarBounds.left, verticalScrollbarBounds.top);
+ mScrollbar.setBounds(0, 0, mScrollbarWidth, verticalScrollbarBounds.height());
+ mScrollbar.draw(canvas);
+ canvas.restoreToCount(restoreCount);
+ }
+
+ /**
+ * Draws the fast scroller popup.
+ */
+ private void drawFastScrollerPopup(Canvas canvas) {
+ if (mFastScrollAlpha > 0f && mFastScrollSectionName != null && !mFastScrollSectionName.isEmpty()) {
+ // Draw the fast scroller popup
+ int restoreCount = canvas.save(Canvas.MATRIX_SAVE_FLAG);
+ canvas.translate(mFastScrollerBounds.left, mFastScrollerBounds.top);
+ mFastScrollerBg.setAlpha((int) (mFastScrollAlpha * 255));
+ mFastScrollerBg.draw(canvas);
+ mFastScrollTextPaint.setAlpha((int) (mFastScrollAlpha * 255));
+ mFastScrollTextPaint.getTextBounds(mFastScrollSectionName, 0,
+ mFastScrollSectionName.length(), mFastScrollTextBounds);
+ float textWidth = mFastScrollTextPaint.measureText(mFastScrollSectionName);
+ canvas.drawText(mFastScrollSectionName,
+ (mFastScrollerBounds.width() - textWidth) / 2,
+ mFastScrollerBounds.height() -
+ (mFastScrollerBounds.height() - mFastScrollTextBounds.height()) / 2,
+ mFastScrollTextPaint);
+ canvas.restoreToCount(restoreCount);
+ }
+ }
+
+ /**
+ * Returns the scroll bar width.
+ */
+ public int getScrollbarWidth() {
+ return mScrollbarWidth;
+ }
+
+ /**
+ * Sets the fast scroller alpha.
+ */
+ public void setFastScrollerAlpha(float alpha) {
+ mFastScrollAlpha = alpha;
+ invalidateFastScroller(mFastScrollerBounds);
+ }
+
+ /**
+ * Maps the touch (from 0..1) to the adapter position that should be visible.
+ * <p>Override in each subclass of this base class.
+ */
+ public String scrollToPositionAtProgress(float touchFraction) {
+ return null;
+ }
+
+ /**
+ * Updates the bounds for the scrollbar.
+ * <p>Override in each subclass of this base class.
+ */
+ public void updateVerticalScrollbarBounds() {};
+
+ /**
+ * Animates the visibility of the fast scroller popup.
+ */
+ private void animateFastScrollerVisibility(boolean visible) {
+ ObjectAnimator anim = ObjectAnimator.ofFloat(this, "fastScrollerAlpha", visible ? 1f : 0f);
+ anim.setDuration(visible ? 200 : 150);
+ anim.start();
+ }
+
+ /**
+ * Invalidates the fast scroller popup.
+ */
+ protected void invalidateFastScroller(Rect bounds) {
+ invalidate(bounds.left, bounds.top, bounds.right, bounds.bottom);
+ }
+
+ /**
+ * Returns whether a given point is near the scrollbar.
+ */
+ private boolean isPointNearScrollbar(int x, int y) {
+ // Check if we are scrolling
+ updateVerticalScrollbarBounds();
+ verticalScrollbarBounds.inset(mScrollbarInset, mScrollbarInset);
+ return verticalScrollbarBounds.contains(x, y);
+ }
+
+ /**
+ * Updates the bounds for the fast scroller.
+ */
+ private void updateFastScrollerBounds() {
+ if (mFastScrollAlpha > 0f && !mFastScrollSectionName.isEmpty()) {
+ int x;
+ int y;
+
+ // Calculate the position for the fast scroller popup
+ Rect bgBounds = mFastScrollerBg.getBounds();
+ if (Utilities.isRtl(getResources())) {
+ x = mBackgroundPadding.left + getScrollBarSize();
+ } else {
+ x = getWidth() - getPaddingRight() - getScrollBarSize() - bgBounds.width();
+ }
+ y = mLastY - (int) (FAST_SCROLL_OVERLAY_Y_OFFSET_FACTOR * bgBounds.height());
+ y = Math.max(getPaddingTop(), Math.min(y, getHeight() - getPaddingBottom() -
+ bgBounds.height()));
+ mFastScrollerBounds.set(x, y, x + bgBounds.width(), y + bgBounds.height());
+ } else {
+ mFastScrollerBounds.setEmpty();
+ }
+ }
} \ No newline at end of file