blob: 8bd5eba0007f8008531c60c0f905abaca0833492 [file] [log] [blame]
Winson Chung94804152015-05-08 13:06:44 -07001/*
2 * Copyright (C) 2015 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.launcher3;
18
19import android.content.Context;
Hyunyoung Songac5f6af2015-05-29 12:00:44 -070020import android.graphics.Canvas;
Hyunyoung Songac5f6af2015-05-29 12:00:44 -070021import android.graphics.Rect;
Winson Chung94804152015-05-08 13:06:44 -070022import android.support.v7.widget.RecyclerView;
23import android.util.AttributeSet;
24import android.view.MotionEvent;
25import com.android.launcher3.util.Thunk;
26
Winson Chungb1777442015-06-16 13:35:04 -070027
Winson Chung94804152015-05-08 13:06:44 -070028/**
Hyunyoung Songac5f6af2015-05-29 12:00:44 -070029 * A base {@link RecyclerView}, which does the following:
30 * <ul>
31 * <li> NOT intercept a touch unless the scrolling velocity is below a predefined threshold.
32 * <li> Enable fast scroller.
33 * </ul>
Winson Chung94804152015-05-08 13:06:44 -070034 */
Winson Chungb1777442015-06-16 13:35:04 -070035public abstract class BaseRecyclerView extends RecyclerView
Winson Chung94804152015-05-08 13:06:44 -070036 implements RecyclerView.OnItemTouchListener {
37
38 private static final int SCROLL_DELTA_THRESHOLD_DP = 4;
39
40 /** Keeps the last known scrolling delta/velocity along y-axis. */
41 @Thunk int mDy = 0;
42 private float mDeltaThreshold;
Winson Chung94804152015-05-08 13:06:44 -070043
Winson Chungb1777442015-06-16 13:35:04 -070044 protected BaseRecyclerViewFastScrollBar mScrollbar;
Hyunyoung Songac5f6af2015-05-29 12:00:44 -070045
46 private int mDownX;
47 private int mDownY;
Hyunyoung Songac5f6af2015-05-29 12:00:44 -070048 private int mLastY;
Winson Chungef7f8742015-06-04 17:18:17 -070049 protected Rect mBackgroundPadding = new Rect();
Hyunyoung Songac5f6af2015-05-29 12:00:44 -070050
Winson Chung5f4e0fd2015-05-22 11:12:27 -070051 public BaseRecyclerView(Context context) {
Winson Chung94804152015-05-08 13:06:44 -070052 this(context, null);
53 }
54
Winson Chung5f4e0fd2015-05-22 11:12:27 -070055 public BaseRecyclerView(Context context, AttributeSet attrs) {
Winson Chung94804152015-05-08 13:06:44 -070056 this(context, attrs, 0);
57 }
58
Winson Chung5f4e0fd2015-05-22 11:12:27 -070059 public BaseRecyclerView(Context context, AttributeSet attrs, int defStyleAttr) {
Winson Chung94804152015-05-08 13:06:44 -070060 super(context, attrs, defStyleAttr);
61 mDeltaThreshold = getResources().getDisplayMetrics().density * SCROLL_DELTA_THRESHOLD_DP;
Winson Chungb1777442015-06-16 13:35:04 -070062 mScrollbar = new BaseRecyclerViewFastScrollBar(this, getResources());
Winson Chung94804152015-05-08 13:06:44 -070063
64 ScrollListener listener = new ScrollListener();
65 setOnScrollListener(listener);
66 }
67
68 private class ScrollListener extends OnScrollListener {
69 public ScrollListener() {
70 // Do nothing
71 }
72
73 @Override
74 public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
75 mDy = dy;
Winson Chungb1777442015-06-16 13:35:04 -070076
77 // TODO(winsonc): If we want to animate the section heads while scrolling, we can
78 // initiate that here if the recycler view scroll state is not
79 // RecyclerView.SCROLL_STATE_IDLE.
Winsond2eb49e2015-08-18 17:43:02 -070080
81 onUpdateScrollbar(dy);
Winson Chung94804152015-05-08 13:06:44 -070082 }
83 }
84
Winsond2eb49e2015-08-18 17:43:02 -070085 public void reset() {
86 mScrollbar.reattachThumbToScroll();
87 }
88
Winson Chung94804152015-05-08 13:06:44 -070089 @Override
90 protected void onFinishInflate() {
91 super.onFinishInflate();
92 addOnItemTouchListener(this);
93 }
94
Hyunyoung Songac5f6af2015-05-29 12:00:44 -070095 /**
96 * We intercept the touch handling only to support fast scrolling when initiated from the
97 * scroll bar. Otherwise, we fall back to the default RecyclerView touch handling.
98 */
Winson Chung94804152015-05-08 13:06:44 -070099 @Override
100 public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent ev) {
Hyunyoung Songac5f6af2015-05-29 12:00:44 -0700101 return handleTouchEvent(ev);
Winson Chung94804152015-05-08 13:06:44 -0700102 }
103
104 @Override
105 public void onTouchEvent(RecyclerView rv, MotionEvent ev) {
Hyunyoung Songac5f6af2015-05-29 12:00:44 -0700106 handleTouchEvent(ev);
107 }
108
109 /**
110 * Handles the touch event and determines whether to show the fast scroller (or updates it if
111 * it is already showing).
112 */
113 private boolean handleTouchEvent(MotionEvent ev) {
Hyunyoung Songac5f6af2015-05-29 12:00:44 -0700114 int action = ev.getAction();
115 int x = (int) ev.getX();
116 int y = (int) ev.getY();
117 switch (action) {
118 case MotionEvent.ACTION_DOWN:
119 // Keep track of the down positions
Hyunyoung Songec847282015-06-04 11:37:46 -0700120 mDownX = x;
Hyunyoung Songac5f6af2015-05-29 12:00:44 -0700121 mDownY = mLastY = y;
122 if (shouldStopScroll(ev)) {
123 stopScroll();
124 }
Winson Chungb1777442015-06-16 13:35:04 -0700125 mScrollbar.handleTouchEvent(ev, mDownX, mDownY, mLastY);
Hyunyoung Songac5f6af2015-05-29 12:00:44 -0700126 break;
127 case MotionEvent.ACTION_MOVE:
Winson Chungb1777442015-06-16 13:35:04 -0700128 mLastY = y;
129 mScrollbar.handleTouchEvent(ev, mDownX, mDownY, mLastY);
Hyunyoung Songac5f6af2015-05-29 12:00:44 -0700130 break;
131 case MotionEvent.ACTION_UP:
132 case MotionEvent.ACTION_CANCEL:
Winson Chungb1777442015-06-16 13:35:04 -0700133 onFastScrollCompleted();
134 mScrollbar.handleTouchEvent(ev, mDownX, mDownY, mLastY);
Hyunyoung Songac5f6af2015-05-29 12:00:44 -0700135 break;
136 }
Winsond2eb49e2015-08-18 17:43:02 -0700137 return mScrollbar.isDraggingThumb();
Winson Chung94804152015-05-08 13:06:44 -0700138 }
139
140 public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
141 // DO NOT REMOVE, NEEDED IMPLEMENTATION FOR M BUILDS
142 }
143
144 /**
Winson Chung94804152015-05-08 13:06:44 -0700145 * Returns whether this {@link MotionEvent} should trigger the scroll to be stopped.
146 */
147 protected boolean shouldStopScroll(MotionEvent ev) {
148 if (ev.getAction() == MotionEvent.ACTION_DOWN) {
149 if ((Math.abs(mDy) < mDeltaThreshold &&
150 getScrollState() != RecyclerView.SCROLL_STATE_IDLE)) {
151 // now the touch events are being passed to the {@link WidgetCell} until the
152 // touch sequence goes over the touch slop.
153 return true;
154 }
155 }
156 return false;
157 }
Hyunyoung Songac5f6af2015-05-29 12:00:44 -0700158
Winson Chungef7f8742015-06-04 17:18:17 -0700159 public void updateBackgroundPadding(Rect padding) {
160 mBackgroundPadding.set(padding);
161 }
162
Winson Chungb1777442015-06-16 13:35:04 -0700163 public Rect getBackgroundPadding() {
164 return mBackgroundPadding;
165 }
166
167 /**
168 * Returns the scroll bar width when the user is scrolling.
169 */
170 public int getMaxScrollbarWidth() {
171 return mScrollbar.getThumbMaxWidth();
172 }
173
174 /**
Winsonc0880492015-08-21 11:16:27 -0700175 * Returns the visible height of the recycler view:
176 * VisibleHeight = View height - top padding - bottom padding
177 */
178 protected int getVisibleHeight() {
179 int visibleHeight = getHeight() - mBackgroundPadding.top - mBackgroundPadding.bottom;
180 return visibleHeight;
181 }
182
183 /**
Winson Chungb1777442015-06-16 13:35:04 -0700184 * Returns the available scroll height:
185 * AvailableScrollHeight = Total height of the all items - last page height
Winson Chungb1777442015-06-16 13:35:04 -0700186 */
Winsonb655b882016-07-11 18:59:18 -0700187 protected abstract int getAvailableScrollHeight();
Winson Chungb1777442015-06-16 13:35:04 -0700188
189 /**
190 * Returns the available scroll bar height:
191 * AvailableScrollBarHeight = Total height of the visible view - thumb height
192 */
193 protected int getAvailableScrollBarHeight() {
Winsonc0880492015-08-21 11:16:27 -0700194 int availableScrollBarHeight = getVisibleHeight() - mScrollbar.getThumbHeight();
Winson Chungb1777442015-06-16 13:35:04 -0700195 return availableScrollBarHeight;
196 }
197
198 /**
199 * Returns the track color (ignoring alpha), can be overridden by each subclass.
200 */
201 public int getFastScrollerTrackColor(int defaultTrackColor) {
202 return defaultTrackColor;
203 }
204
205 /**
206 * Returns the inactive thumb color, can be overridden by each subclass.
207 */
208 public int getFastScrollerThumbInactiveColor(int defaultInactiveThumbColor) {
209 return defaultInactiveThumbColor;
210 }
211
Winsonc0880492015-08-21 11:16:27 -0700212 /**
213 * Returns the scrollbar for this recycler view.
214 */
215 public BaseRecyclerViewFastScrollBar getScrollBar() {
216 return mScrollbar;
217 }
218
Hyunyoung Songac5f6af2015-05-29 12:00:44 -0700219 @Override
220 protected void dispatchDraw(Canvas canvas) {
221 super.dispatchDraw(canvas);
Winsond2eb49e2015-08-18 17:43:02 -0700222 onUpdateScrollbar(0);
Hyunyoung Songac5f6af2015-05-29 12:00:44 -0700223 mScrollbar.draw(canvas);
Hyunyoung Songac5f6af2015-05-29 12:00:44 -0700224 }
225
226 /**
Winson Chungb1777442015-06-16 13:35:04 -0700227 * Updates the scrollbar thumb offset to match the visible scroll of the recycler view. It does
228 * this by mapping the available scroll area of the recycler view to the available space for the
229 * scroll bar.
230 *
Winsonb655b882016-07-11 18:59:18 -0700231 * @param scrollY the current scroll y
Hyunyoung Songac5f6af2015-05-29 12:00:44 -0700232 */
Winsonb655b882016-07-11 18:59:18 -0700233 protected void synchronizeScrollBarThumbOffsetToViewScroll(int scrollY,
234 int availableScrollHeight) {
Winson Chungb1777442015-06-16 13:35:04 -0700235 // Only show the scrollbar if there is height to be scrolled
Winsond2eb49e2015-08-18 17:43:02 -0700236 int availableScrollBarHeight = getAvailableScrollBarHeight();
Winson Chungb1777442015-06-16 13:35:04 -0700237 if (availableScrollHeight <= 0) {
Winsond2eb49e2015-08-18 17:43:02 -0700238 mScrollbar.setThumbOffset(-1, -1);
Winson Chungb1777442015-06-16 13:35:04 -0700239 return;
Hyunyoung Songac5f6af2015-05-29 12:00:44 -0700240 }
Hyunyoung Songac5f6af2015-05-29 12:00:44 -0700241
Winson Chungb1777442015-06-16 13:35:04 -0700242 // Calculate the current scroll position, the scrollY of the recycler view accounts for the
243 // view padding, while the scrollBarY is drawn right up to the background padding (ignoring
244 // padding)
Winson Chungb1777442015-06-16 13:35:04 -0700245 int scrollBarY = mBackgroundPadding.top +
246 (int) (((float) scrollY / availableScrollHeight) * availableScrollBarHeight);
Hyunyoung Songac5f6af2015-05-29 12:00:44 -0700247
Winson Chungb1777442015-06-16 13:35:04 -0700248 // Calculate the position and size of the scroll bar
249 int scrollBarX;
250 if (Utilities.isRtl(getResources())) {
251 scrollBarX = mBackgroundPadding.left;
252 } else {
Winsond2eb49e2015-08-18 17:43:02 -0700253 scrollBarX = getWidth() - mBackgroundPadding.right - mScrollbar.getThumbWidth();
Winson Chungb1777442015-06-16 13:35:04 -0700254 }
Winsond2eb49e2015-08-18 17:43:02 -0700255 mScrollbar.setThumbOffset(scrollBarX, scrollBarY);
Winson Chung4b9f9792015-06-12 18:02:52 -0700256 }
257
258 /**
Winsonc0880492015-08-21 11:16:27 -0700259 * @return whether fast scrolling is supported in the current state.
Winson646c2362015-09-03 11:46:11 -0700260 */
261 protected boolean supportsFastScrolling() {
262 return true;
263 }
264
265 /**
Hyunyoung Songac5f6af2015-05-29 12:00:44 -0700266 * Maps the touch (from 0..1) to the adapter position that should be visible.
267 * <p>Override in each subclass of this base class.
Winsonc0880492015-08-21 11:16:27 -0700268 *
269 * @return the scroll top of this recycler view.
Hyunyoung Songac5f6af2015-05-29 12:00:44 -0700270 */
Winsonb655b882016-07-11 18:59:18 -0700271 protected abstract int getCurrentScrollY();
Winsonc0880492015-08-21 11:16:27 -0700272
273 /**
274 * Maps the touch (from 0..1) to the adapter position that should be visible.
275 * <p>Override in each subclass of this base class.
276 */
277 protected abstract String scrollToPositionAtProgress(float touchFraction);
Hyunyoung Songac5f6af2015-05-29 12:00:44 -0700278
279 /**
280 * Updates the bounds for the scrollbar.
281 * <p>Override in each subclass of this base class.
282 */
Winsonc0880492015-08-21 11:16:27 -0700283 protected abstract void onUpdateScrollbar(int dy);
Hyunyoung Songac5f6af2015-05-29 12:00:44 -0700284
285 /**
Winson Chungb1777442015-06-16 13:35:04 -0700286 * <p>Override in each subclass of this base class.
Hyunyoung Songac5f6af2015-05-29 12:00:44 -0700287 */
Winsonc0880492015-08-21 11:16:27 -0700288 protected void onFastScrollCompleted() {}
Winson Chung94804152015-05-08 13:06:44 -0700289}