diff options
Diffstat (limited to 'src/com/android/launcher3/BaseRecyclerView.java')
-rw-r--r-- | src/com/android/launcher3/BaseRecyclerView.java | 285 |
1 files changed, 285 insertions, 0 deletions
diff --git a/src/com/android/launcher3/BaseRecyclerView.java b/src/com/android/launcher3/BaseRecyclerView.java new file mode 100644 index 000000000..0fae427e8 --- /dev/null +++ b/src/com/android/launcher3/BaseRecyclerView.java @@ -0,0 +1,285 @@ +/* + * 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.content.Context; +import android.graphics.Canvas; +import android.graphics.Rect; +import android.support.v7.widget.RecyclerView; +import android.util.AttributeSet; +import android.view.MotionEvent; +import com.android.launcher3.util.Thunk; + + +/** + * 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 abstract class BaseRecyclerView extends RecyclerView + implements RecyclerView.OnItemTouchListener { + + private static final int SCROLL_DELTA_THRESHOLD_DP = 4; + + /** Keeps the last known scrolling delta/velocity along y-axis. */ + @Thunk int mDy = 0; + private float mDeltaThreshold; + + /** + * The current scroll state of the recycler view. We use this in onUpdateScrollbar() + * 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; + } + + protected BaseRecyclerViewFastScrollBar mScrollbar; + + private int mDownX; + private int mDownY; + private int mLastY; + protected Rect mBackgroundPadding = new Rect(); + + public BaseRecyclerView(Context context) { + this(context, null); + } + + public BaseRecyclerView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public BaseRecyclerView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + mDeltaThreshold = getResources().getDisplayMetrics().density * SCROLL_DELTA_THRESHOLD_DP; + mScrollbar = new BaseRecyclerViewFastScrollBar(this, getResources()); + + ScrollListener listener = new ScrollListener(); + setOnScrollListener(listener); + } + + private class ScrollListener extends OnScrollListener { + public ScrollListener() { + // Do nothing + } + + @Override + public void onScrolled(RecyclerView recyclerView, int dx, int dy) { + mDy = dy; + + // TODO(winsonc): If we want to animate the section heads while scrolling, we can + // initiate that here if the recycler view scroll state is not + // RecyclerView.SCROLL_STATE_IDLE. + } + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + 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) { + return handleTouchEvent(ev); + } + + @Override + public void onTouchEvent(RecyclerView rv, MotionEvent ev) { + 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) { + 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 = x; + mDownY = mLastY = y; + if (shouldStopScroll(ev)) { + stopScroll(); + } + mScrollbar.handleTouchEvent(ev, mDownX, mDownY, mLastY); + break; + case MotionEvent.ACTION_MOVE: + mLastY = y; + mScrollbar.handleTouchEvent(ev, mDownX, mDownY, mLastY); + break; + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_CANCEL: + onFastScrollCompleted(); + mScrollbar.handleTouchEvent(ev, mDownX, mDownY, mLastY); + break; + } + return mScrollbar.isDragging(); + } + + public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) { + // DO NOT REMOVE, NEEDED IMPLEMENTATION FOR M BUILDS + } + + /** + * Returns whether this {@link MotionEvent} should trigger the scroll to be stopped. + */ + protected boolean shouldStopScroll(MotionEvent ev) { + if (ev.getAction() == MotionEvent.ACTION_DOWN) { + if ((Math.abs(mDy) < mDeltaThreshold && + getScrollState() != RecyclerView.SCROLL_STATE_IDLE)) { + // now the touch events are being passed to the {@link WidgetCell} until the + // touch sequence goes over the touch slop. + return true; + } + } + return false; + } + + public void updateBackgroundPadding(Rect padding) { + mBackgroundPadding.set(padding); + } + + public Rect getBackgroundPadding() { + return mBackgroundPadding; + } + + /** + * Returns the scroll bar width when the user is scrolling. + */ + public int getMaxScrollbarWidth() { + return mScrollbar.getThumbMaxWidth(); + } + + /** + * Returns the available scroll height: + * AvailableScrollHeight = Total height of the all items - last page height + * + * This assumes that all rows are the same height. + * + * @param yOffset the offset from the top of the recycler view to start tracking. + */ + protected int getAvailableScrollHeight(int rowCount, int rowHeight, int yOffset) { + int visibleHeight = getHeight() - mBackgroundPadding.top - mBackgroundPadding.bottom; + int scrollHeight = getPaddingTop() + yOffset + rowCount * rowHeight + getPaddingBottom(); + int availableScrollHeight = scrollHeight - visibleHeight; + return availableScrollHeight; + } + + /** + * Returns the available scroll bar height: + * AvailableScrollBarHeight = Total height of the visible view - thumb height + */ + protected int getAvailableScrollBarHeight() { + int visibleHeight = getHeight() - mBackgroundPadding.top - mBackgroundPadding.bottom; + int availableScrollBarHeight = visibleHeight - mScrollbar.getThumbHeight(); + return availableScrollBarHeight; + } + + /** + * Returns the track color (ignoring alpha), can be overridden by each subclass. + */ + public int getFastScrollerTrackColor(int defaultTrackColor) { + return defaultTrackColor; + } + + /** + * Returns the inactive thumb color, can be overridden by each subclass. + */ + public int getFastScrollerThumbInactiveColor(int defaultInactiveThumbColor) { + return defaultInactiveThumbColor; + } + + @Override + protected void dispatchDraw(Canvas canvas) { + super.dispatchDraw(canvas); + onUpdateScrollbar(); + mScrollbar.draw(canvas); + } + + /** + * Updates the scrollbar thumb offset to match the visible scroll of the recycler view. It does + * this by mapping the available scroll area of the recycler view to the available space for the + * scroll bar. + * + * @param scrollPosState the current scroll position + * @param rowCount the number of rows, used to calculate the total scroll height (assumes that + * all rows are the same height) + * @param yOffset the offset to start tracking in the recycler view (only used for all apps) + */ + protected void synchronizeScrollBarThumbOffsetToViewScroll(ScrollPositionState scrollPosState, + int rowCount, int yOffset) { + int availableScrollHeight = getAvailableScrollHeight(rowCount, scrollPosState.rowHeight, + yOffset); + int availableScrollBarHeight = getAvailableScrollBarHeight(); + + // Only show the scrollbar if there is height to be scrolled + if (availableScrollHeight <= 0) { + mScrollbar.setScrollbarThumbOffset(-1, -1); + return; + } + + // 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 scrollY = getPaddingTop() + yOffset + + (scrollPosState.rowIndex * scrollPosState.rowHeight) - scrollPosState.rowTopOffset; + int scrollBarY = mBackgroundPadding.top + + (int) (((float) scrollY / availableScrollHeight) * availableScrollBarHeight); + + // Calculate the position and size of the scroll bar + int scrollBarX; + if (Utilities.isRtl(getResources())) { + scrollBarX = mBackgroundPadding.left; + } else { + scrollBarX = getWidth() - mBackgroundPadding.right - mScrollbar.getWidth(); + } + mScrollbar.setScrollbarThumbOffset(scrollBarX, scrollBarY); + } + + /** + * Maps the touch (from 0..1) to the adapter position that should be visible. + * <p>Override in each subclass of this base class. + */ + public abstract String scrollToPositionAtProgress(float touchFraction); + + /** + * Updates the bounds for the scrollbar. + * <p>Override in each subclass of this base class. + */ + public abstract void onUpdateScrollbar(); + + /** + * <p>Override in each subclass of this base class. + */ + public void onFastScrollCompleted() {} +}
\ No newline at end of file |