blob: 4e39ed3e5de77eb0c62628906943f64aa80f8f75 [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;
Sunny Goyal5d9fb0e2016-10-08 17:43:48 -070025import android.view.ViewGroup;
26
Winson Chung94804152015-05-08 13:06:44 -070027import com.android.launcher3.util.Thunk;
28
Winson Chungb1777442015-06-16 13:35:04 -070029
Winson Chung94804152015-05-08 13:06:44 -070030/**
Hyunyoung Songac5f6af2015-05-29 12:00:44 -070031 * A base {@link RecyclerView}, which does the following:
32 * <ul>
33 * <li> NOT intercept a touch unless the scrolling velocity is below a predefined threshold.
34 * <li> Enable fast scroller.
35 * </ul>
Winson Chung94804152015-05-08 13:06:44 -070036 */
Winson Chungb1777442015-06-16 13:35:04 -070037public abstract class BaseRecyclerView extends RecyclerView
Winson Chung94804152015-05-08 13:06:44 -070038 implements RecyclerView.OnItemTouchListener {
39
40 private static final int SCROLL_DELTA_THRESHOLD_DP = 4;
41
42 /** Keeps the last known scrolling delta/velocity along y-axis. */
43 @Thunk int mDy = 0;
44 private float mDeltaThreshold;
Winson Chung94804152015-05-08 13:06:44 -070045
Sunny Goyal5d9fb0e2016-10-08 17:43:48 -070046 protected final BaseRecyclerViewFastScrollBar mScrollbar;
Hyunyoung Songac5f6af2015-05-29 12:00:44 -070047
48 private int mDownX;
49 private int mDownY;
Hyunyoung Songac5f6af2015-05-29 12:00:44 -070050 private int mLastY;
Winson Chungef7f8742015-06-04 17:18:17 -070051 protected Rect mBackgroundPadding = new Rect();
Hyunyoung Songac5f6af2015-05-29 12:00:44 -070052
Winson Chung5f4e0fd2015-05-22 11:12:27 -070053 public BaseRecyclerView(Context context) {
Winson Chung94804152015-05-08 13:06:44 -070054 this(context, null);
55 }
56
Winson Chung5f4e0fd2015-05-22 11:12:27 -070057 public BaseRecyclerView(Context context, AttributeSet attrs) {
Winson Chung94804152015-05-08 13:06:44 -070058 this(context, attrs, 0);
59 }
60
Winson Chung5f4e0fd2015-05-22 11:12:27 -070061 public BaseRecyclerView(Context context, AttributeSet attrs, int defStyleAttr) {
Winson Chung94804152015-05-08 13:06:44 -070062 super(context, attrs, defStyleAttr);
63 mDeltaThreshold = getResources().getDisplayMetrics().density * SCROLL_DELTA_THRESHOLD_DP;
Winson Chungb1777442015-06-16 13:35:04 -070064 mScrollbar = new BaseRecyclerViewFastScrollBar(this, getResources());
Winson Chung94804152015-05-08 13:06:44 -070065
66 ScrollListener listener = new ScrollListener();
67 setOnScrollListener(listener);
68 }
69
70 private class ScrollListener extends OnScrollListener {
71 public ScrollListener() {
72 // Do nothing
73 }
74
75 @Override
76 public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
77 mDy = dy;
Winson Chungb1777442015-06-16 13:35:04 -070078
79 // TODO(winsonc): If we want to animate the section heads while scrolling, we can
80 // initiate that here if the recycler view scroll state is not
81 // RecyclerView.SCROLL_STATE_IDLE.
Winsond2eb49e2015-08-18 17:43:02 -070082
83 onUpdateScrollbar(dy);
Winson Chung94804152015-05-08 13:06:44 -070084 }
85 }
86
Winsond2eb49e2015-08-18 17:43:02 -070087 public void reset() {
88 mScrollbar.reattachThumbToScroll();
89 }
90
Winson Chung94804152015-05-08 13:06:44 -070091 @Override
92 protected void onFinishInflate() {
93 super.onFinishInflate();
94 addOnItemTouchListener(this);
95 }
96
Sunny Goyal5d9fb0e2016-10-08 17:43:48 -070097 @Override
98 protected void onAttachedToWindow() {
99 super.onAttachedToWindow();
100 mScrollbar.setPopupView(((ViewGroup) getParent()).findViewById(R.id.fast_scroller_popup));
101 }
102
Hyunyoung Songac5f6af2015-05-29 12:00:44 -0700103 /**
104 * We intercept the touch handling only to support fast scrolling when initiated from the
105 * scroll bar. Otherwise, we fall back to the default RecyclerView touch handling.
106 */
Winson Chung94804152015-05-08 13:06:44 -0700107 @Override
108 public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent ev) {
Hyunyoung Songac5f6af2015-05-29 12:00:44 -0700109 return handleTouchEvent(ev);
Winson Chung94804152015-05-08 13:06:44 -0700110 }
111
112 @Override
113 public void onTouchEvent(RecyclerView rv, MotionEvent ev) {
Hyunyoung Songac5f6af2015-05-29 12:00:44 -0700114 handleTouchEvent(ev);
115 }
116
117 /**
118 * Handles the touch event and determines whether to show the fast scroller (or updates it if
119 * it is already showing).
120 */
121 private boolean handleTouchEvent(MotionEvent ev) {
Hyunyoung Songac5f6af2015-05-29 12:00:44 -0700122 int action = ev.getAction();
123 int x = (int) ev.getX();
124 int y = (int) ev.getY();
125 switch (action) {
126 case MotionEvent.ACTION_DOWN:
127 // Keep track of the down positions
Hyunyoung Songec847282015-06-04 11:37:46 -0700128 mDownX = x;
Hyunyoung Songac5f6af2015-05-29 12:00:44 -0700129 mDownY = mLastY = y;
130 if (shouldStopScroll(ev)) {
131 stopScroll();
132 }
Winson Chungb1777442015-06-16 13:35:04 -0700133 mScrollbar.handleTouchEvent(ev, mDownX, mDownY, mLastY);
Hyunyoung Songac5f6af2015-05-29 12:00:44 -0700134 break;
135 case MotionEvent.ACTION_MOVE:
Winson Chungb1777442015-06-16 13:35:04 -0700136 mLastY = y;
137 mScrollbar.handleTouchEvent(ev, mDownX, mDownY, mLastY);
Hyunyoung Songac5f6af2015-05-29 12:00:44 -0700138 break;
139 case MotionEvent.ACTION_UP:
140 case MotionEvent.ACTION_CANCEL:
Winson Chungb1777442015-06-16 13:35:04 -0700141 onFastScrollCompleted();
142 mScrollbar.handleTouchEvent(ev, mDownX, mDownY, mLastY);
Hyunyoung Songac5f6af2015-05-29 12:00:44 -0700143 break;
144 }
Winsond2eb49e2015-08-18 17:43:02 -0700145 return mScrollbar.isDraggingThumb();
Winson Chung94804152015-05-08 13:06:44 -0700146 }
147
148 public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
149 // DO NOT REMOVE, NEEDED IMPLEMENTATION FOR M BUILDS
150 }
151
152 /**
Winson Chung94804152015-05-08 13:06:44 -0700153 * Returns whether this {@link MotionEvent} should trigger the scroll to be stopped.
154 */
155 protected boolean shouldStopScroll(MotionEvent ev) {
156 if (ev.getAction() == MotionEvent.ACTION_DOWN) {
157 if ((Math.abs(mDy) < mDeltaThreshold &&
158 getScrollState() != RecyclerView.SCROLL_STATE_IDLE)) {
159 // now the touch events are being passed to the {@link WidgetCell} until the
160 // touch sequence goes over the touch slop.
161 return true;
162 }
163 }
164 return false;
165 }
Hyunyoung Songac5f6af2015-05-29 12:00:44 -0700166
Winson Chungef7f8742015-06-04 17:18:17 -0700167 public void updateBackgroundPadding(Rect padding) {
168 mBackgroundPadding.set(padding);
169 }
170
Winson Chungb1777442015-06-16 13:35:04 -0700171 public Rect getBackgroundPadding() {
172 return mBackgroundPadding;
173 }
174
175 /**
Winsonc0880492015-08-21 11:16:27 -0700176 * Returns the visible height of the recycler view:
177 * VisibleHeight = View height - top padding - bottom padding
178 */
179 protected int getVisibleHeight() {
180 int visibleHeight = getHeight() - mBackgroundPadding.top - mBackgroundPadding.bottom;
181 return visibleHeight;
182 }
183
184 /**
Winson Chungb1777442015-06-16 13:35:04 -0700185 * Returns the available scroll height:
186 * AvailableScrollHeight = Total height of the all items - last page height
Winson Chungb1777442015-06-16 13:35:04 -0700187 */
Winsonb655b882016-07-11 18:59:18 -0700188 protected abstract int getAvailableScrollHeight();
Winson Chungb1777442015-06-16 13:35:04 -0700189
190 /**
191 * Returns the available scroll bar height:
192 * AvailableScrollBarHeight = Total height of the visible view - thumb height
193 */
194 protected int getAvailableScrollBarHeight() {
Winsonc0880492015-08-21 11:16:27 -0700195 int availableScrollBarHeight = getVisibleHeight() - mScrollbar.getThumbHeight();
Winson Chungb1777442015-06-16 13:35:04 -0700196 return availableScrollBarHeight;
197 }
198
199 /**
200 * Returns the track color (ignoring alpha), can be overridden by each subclass.
201 */
202 public int getFastScrollerTrackColor(int defaultTrackColor) {
203 return defaultTrackColor;
204 }
205
206 /**
207 * Returns the inactive thumb color, can be overridden by each subclass.
208 */
209 public int getFastScrollerThumbInactiveColor(int defaultInactiveThumbColor) {
210 return defaultInactiveThumbColor;
211 }
212
Winsonc0880492015-08-21 11:16:27 -0700213 /**
214 * Returns the scrollbar for this recycler view.
215 */
216 public BaseRecyclerViewFastScrollBar getScrollBar() {
217 return mScrollbar;
218 }
219
Hyunyoung Songac5f6af2015-05-29 12:00:44 -0700220 @Override
221 protected void dispatchDraw(Canvas canvas) {
222 super.dispatchDraw(canvas);
Winsond2eb49e2015-08-18 17:43:02 -0700223 onUpdateScrollbar(0);
Hyunyoung Songac5f6af2015-05-29 12:00:44 -0700224 mScrollbar.draw(canvas);
Hyunyoung Songac5f6af2015-05-29 12:00:44 -0700225 }
226
227 /**
Winson Chungb1777442015-06-16 13:35:04 -0700228 * Updates the scrollbar thumb offset to match the visible scroll of the recycler view. It does
229 * this by mapping the available scroll area of the recycler view to the available space for the
230 * scroll bar.
231 *
Winsonb655b882016-07-11 18:59:18 -0700232 * @param scrollY the current scroll y
Hyunyoung Songac5f6af2015-05-29 12:00:44 -0700233 */
Winsonb655b882016-07-11 18:59:18 -0700234 protected void synchronizeScrollBarThumbOffsetToViewScroll(int scrollY,
235 int availableScrollHeight) {
Winson Chungb1777442015-06-16 13:35:04 -0700236 // Only show the scrollbar if there is height to be scrolled
Winsond2eb49e2015-08-18 17:43:02 -0700237 int availableScrollBarHeight = getAvailableScrollBarHeight();
Winson Chungb1777442015-06-16 13:35:04 -0700238 if (availableScrollHeight <= 0) {
Sunny Goyal5d9fb0e2016-10-08 17:43:48 -0700239 mScrollbar.setThumbOffsetY(-1);
Winson Chungb1777442015-06-16 13:35:04 -0700240 return;
Hyunyoung Songac5f6af2015-05-29 12:00:44 -0700241 }
Hyunyoung Songac5f6af2015-05-29 12:00:44 -0700242
Winson Chungb1777442015-06-16 13:35:04 -0700243 // Calculate the current scroll position, the scrollY of the recycler view accounts for the
244 // view padding, while the scrollBarY is drawn right up to the background padding (ignoring
245 // padding)
Winson Chungb1777442015-06-16 13:35:04 -0700246 int scrollBarY = mBackgroundPadding.top +
247 (int) (((float) scrollY / availableScrollHeight) * availableScrollBarHeight);
Hyunyoung Songac5f6af2015-05-29 12:00:44 -0700248
Winson Chungb1777442015-06-16 13:35:04 -0700249 // Calculate the position and size of the scroll bar
Sunny Goyal5d9fb0e2016-10-08 17:43:48 -0700250 mScrollbar.setThumbOffsetY(scrollBarY);
Winson Chung4b9f9792015-06-12 18:02:52 -0700251 }
252
253 /**
Winsonc0880492015-08-21 11:16:27 -0700254 * @return whether fast scrolling is supported in the current state.
Winson646c2362015-09-03 11:46:11 -0700255 */
256 protected boolean supportsFastScrolling() {
257 return true;
258 }
259
260 /**
Hyunyoung Songac5f6af2015-05-29 12:00:44 -0700261 * Maps the touch (from 0..1) to the adapter position that should be visible.
262 * <p>Override in each subclass of this base class.
Winsonc0880492015-08-21 11:16:27 -0700263 *
264 * @return the scroll top of this recycler view.
Hyunyoung Songac5f6af2015-05-29 12:00:44 -0700265 */
Peter Schiller2e22b5d2016-07-12 12:45:10 -0700266 public abstract int getCurrentScrollY();
Winsonc0880492015-08-21 11:16:27 -0700267
268 /**
269 * Maps the touch (from 0..1) to the adapter position that should be visible.
270 * <p>Override in each subclass of this base class.
271 */
272 protected abstract String scrollToPositionAtProgress(float touchFraction);
Hyunyoung Songac5f6af2015-05-29 12:00:44 -0700273
274 /**
275 * Updates the bounds for the scrollbar.
276 * <p>Override in each subclass of this base class.
277 */
Winsonc0880492015-08-21 11:16:27 -0700278 protected abstract void onUpdateScrollbar(int dy);
Hyunyoung Songac5f6af2015-05-29 12:00:44 -0700279
280 /**
Winson Chungb1777442015-06-16 13:35:04 -0700281 * <p>Override in each subclass of this base class.
Hyunyoung Songac5f6af2015-05-29 12:00:44 -0700282 */
Winsonc0880492015-08-21 11:16:27 -0700283 protected void onFastScrollCompleted() {}
Winson Chung94804152015-05-08 13:06:44 -0700284}