blob: 514cc07511929bb83439181d531010b92acc94c4 [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;
Winson Chung94804152015-05-08 13:06:44 -070021import android.support.v7.widget.RecyclerView;
22import android.util.AttributeSet;
23import android.view.MotionEvent;
Sunny Goyal5d9fb0e2016-10-08 17:43:48 -070024import android.view.ViewGroup;
25
Winson Chung94804152015-05-08 13:06:44 -070026import com.android.launcher3.util.Thunk;
27
Winson Chungb1777442015-06-16 13:35:04 -070028
Winson Chung94804152015-05-08 13:06:44 -070029/**
Hyunyoung Songac5f6af2015-05-29 12:00:44 -070030 * A base {@link RecyclerView}, which does the following:
31 * <ul>
32 * <li> NOT intercept a touch unless the scrolling velocity is below a predefined threshold.
33 * <li> Enable fast scroller.
34 * </ul>
Winson Chung94804152015-05-08 13:06:44 -070035 */
Winson Chungb1777442015-06-16 13:35:04 -070036public abstract class BaseRecyclerView extends RecyclerView
Winson Chung94804152015-05-08 13:06:44 -070037 implements RecyclerView.OnItemTouchListener {
38
39 private static final int SCROLL_DELTA_THRESHOLD_DP = 4;
40
41 /** Keeps the last known scrolling delta/velocity along y-axis. */
42 @Thunk int mDy = 0;
43 private float mDeltaThreshold;
Winson Chung94804152015-05-08 13:06:44 -070044
Sunny Goyal5d9fb0e2016-10-08 17:43:48 -070045 protected final BaseRecyclerViewFastScrollBar mScrollbar;
Hyunyoung Songac5f6af2015-05-29 12:00:44 -070046
47 private int mDownX;
48 private int mDownY;
Hyunyoung Songac5f6af2015-05-29 12:00:44 -070049 private int mLastY;
Hyunyoung Songac5f6af2015-05-29 12:00:44 -070050
Sunny Goyal9e4c3592017-06-07 14:05:08 -070051 private boolean mScrollBarVisible = true;
52
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
Winson Chung94804152015-05-08 13:06:44 -070087 @Override
88 protected void onFinishInflate() {
89 super.onFinishInflate();
90 addOnItemTouchListener(this);
91 }
92
Sunny Goyal5d9fb0e2016-10-08 17:43:48 -070093 @Override
94 protected void onAttachedToWindow() {
95 super.onAttachedToWindow();
96 mScrollbar.setPopupView(((ViewGroup) getParent()).findViewById(R.id.fast_scroller_popup));
97 }
98
Hyunyoung Songac5f6af2015-05-29 12:00:44 -070099 /**
100 * We intercept the touch handling only to support fast scrolling when initiated from the
101 * scroll bar. Otherwise, we fall back to the default RecyclerView touch handling.
102 */
Winson Chung94804152015-05-08 13:06:44 -0700103 @Override
104 public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent ev) {
Hyunyoung Songac5f6af2015-05-29 12:00:44 -0700105 return handleTouchEvent(ev);
Winson Chung94804152015-05-08 13:06:44 -0700106 }
107
108 @Override
109 public void onTouchEvent(RecyclerView rv, MotionEvent ev) {
Hyunyoung Songac5f6af2015-05-29 12:00:44 -0700110 handleTouchEvent(ev);
111 }
112
113 /**
114 * Handles the touch event and determines whether to show the fast scroller (or updates it if
115 * it is already showing).
116 */
117 private boolean handleTouchEvent(MotionEvent ev) {
Sunny Goyaldc19a072017-05-12 08:17:35 -0700118 ev.offsetLocation(0, -getPaddingTop());
Hyunyoung Songac5f6af2015-05-29 12:00:44 -0700119 int action = ev.getAction();
120 int x = (int) ev.getX();
121 int y = (int) ev.getY();
122 switch (action) {
123 case MotionEvent.ACTION_DOWN:
124 // Keep track of the down positions
Hyunyoung Songec847282015-06-04 11:37:46 -0700125 mDownX = x;
Hyunyoung Songac5f6af2015-05-29 12:00:44 -0700126 mDownY = mLastY = y;
127 if (shouldStopScroll(ev)) {
128 stopScroll();
129 }
Winson Chungb1777442015-06-16 13:35:04 -0700130 mScrollbar.handleTouchEvent(ev, mDownX, mDownY, mLastY);
Hyunyoung Songac5f6af2015-05-29 12:00:44 -0700131 break;
132 case MotionEvent.ACTION_MOVE:
Winson Chungb1777442015-06-16 13:35:04 -0700133 mLastY = y;
134 mScrollbar.handleTouchEvent(ev, mDownX, mDownY, mLastY);
Hyunyoung Songac5f6af2015-05-29 12:00:44 -0700135 break;
136 case MotionEvent.ACTION_UP:
137 case MotionEvent.ACTION_CANCEL:
Winson Chungb1777442015-06-16 13:35:04 -0700138 onFastScrollCompleted();
139 mScrollbar.handleTouchEvent(ev, mDownX, mDownY, mLastY);
Hyunyoung Songac5f6af2015-05-29 12:00:44 -0700140 break;
141 }
Sunny Goyaldc19a072017-05-12 08:17:35 -0700142 ev.offsetLocation(0, getPaddingTop());
Winsond2eb49e2015-08-18 17:43:02 -0700143 return mScrollbar.isDraggingThumb();
Winson Chung94804152015-05-08 13:06:44 -0700144 }
145
146 public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
147 // DO NOT REMOVE, NEEDED IMPLEMENTATION FOR M BUILDS
148 }
149
150 /**
Winson Chung94804152015-05-08 13:06:44 -0700151 * Returns whether this {@link MotionEvent} should trigger the scroll to be stopped.
152 */
153 protected boolean shouldStopScroll(MotionEvent ev) {
154 if (ev.getAction() == MotionEvent.ACTION_DOWN) {
155 if ((Math.abs(mDy) < mDeltaThreshold &&
156 getScrollState() != RecyclerView.SCROLL_STATE_IDLE)) {
157 // now the touch events are being passed to the {@link WidgetCell} until the
158 // touch sequence goes over the touch slop.
159 return true;
160 }
161 }
162 return false;
163 }
Hyunyoung Songac5f6af2015-05-29 12:00:44 -0700164
Winson Chungb1777442015-06-16 13:35:04 -0700165 /**
Sunny Goyal00e10682016-10-18 14:23:46 +0100166 * Returns the height of the fast scroll bar
Winsonc0880492015-08-21 11:16:27 -0700167 */
Sunny Goyal00e10682016-10-18 14:23:46 +0100168 protected int getScrollbarTrackHeight() {
Sunny Goyaldc19a072017-05-12 08:17:35 -0700169 return getHeight() - getPaddingTop() - getPaddingBottom();
Winsonc0880492015-08-21 11:16:27 -0700170 }
171
172 /**
Winson Chungb1777442015-06-16 13:35:04 -0700173 * Returns the available scroll height:
174 * AvailableScrollHeight = Total height of the all items - last page height
Winson Chungb1777442015-06-16 13:35:04 -0700175 */
Winsonb655b882016-07-11 18:59:18 -0700176 protected abstract int getAvailableScrollHeight();
Winson Chungb1777442015-06-16 13:35:04 -0700177
178 /**
179 * Returns the available scroll bar height:
180 * AvailableScrollBarHeight = Total height of the visible view - thumb height
181 */
182 protected int getAvailableScrollBarHeight() {
Sunny Goyal00e10682016-10-18 14:23:46 +0100183 int availableScrollBarHeight = getScrollbarTrackHeight() - mScrollbar.getThumbHeight();
Winson Chungb1777442015-06-16 13:35:04 -0700184 return availableScrollBarHeight;
185 }
186
187 /**
188 * Returns the track color (ignoring alpha), can be overridden by each subclass.
189 */
190 public int getFastScrollerTrackColor(int defaultTrackColor) {
191 return defaultTrackColor;
192 }
193
194 /**
Winsonc0880492015-08-21 11:16:27 -0700195 * Returns the scrollbar for this recycler view.
196 */
197 public BaseRecyclerViewFastScrollBar getScrollBar() {
198 return mScrollbar;
199 }
200
Hyunyoung Songac5f6af2015-05-29 12:00:44 -0700201 @Override
202 protected void dispatchDraw(Canvas canvas) {
203 super.dispatchDraw(canvas);
Sunny Goyal9e4c3592017-06-07 14:05:08 -0700204 if (mScrollBarVisible) {
205 onUpdateScrollbar(0);
206 mScrollbar.draw(canvas);
207 }
208 }
209
210 /**
211 * Sets the scrollbar visibility. The call does not refresh the UI, its the responsibility
212 * of the caller to call {@link #invalidate()}.
213 */
214 public void setScrollBarVisible(boolean visible) {
215 mScrollBarVisible = visible;
Hyunyoung Songac5f6af2015-05-29 12:00:44 -0700216 }
217
218 /**
Winson Chungb1777442015-06-16 13:35:04 -0700219 * Updates the scrollbar thumb offset to match the visible scroll of the recycler view. It does
220 * this by mapping the available scroll area of the recycler view to the available space for the
221 * scroll bar.
222 *
Winsonb655b882016-07-11 18:59:18 -0700223 * @param scrollY the current scroll y
Hyunyoung Songac5f6af2015-05-29 12:00:44 -0700224 */
Winsonb655b882016-07-11 18:59:18 -0700225 protected void synchronizeScrollBarThumbOffsetToViewScroll(int scrollY,
226 int availableScrollHeight) {
Winson Chungb1777442015-06-16 13:35:04 -0700227 // Only show the scrollbar if there is height to be scrolled
228 if (availableScrollHeight <= 0) {
Sunny Goyal5d9fb0e2016-10-08 17:43:48 -0700229 mScrollbar.setThumbOffsetY(-1);
Winson Chungb1777442015-06-16 13:35:04 -0700230 return;
Hyunyoung Songac5f6af2015-05-29 12:00:44 -0700231 }
Hyunyoung Songac5f6af2015-05-29 12:00:44 -0700232
Winson Chungb1777442015-06-16 13:35:04 -0700233 // Calculate the current scroll position, the scrollY of the recycler view accounts for the
234 // view padding, while the scrollBarY is drawn right up to the background padding (ignoring
235 // padding)
Sunny Goyal00e10682016-10-18 14:23:46 +0100236 int scrollBarY =
237 (int) (((float) scrollY / availableScrollHeight) * getAvailableScrollBarHeight());
Hyunyoung Songac5f6af2015-05-29 12:00:44 -0700238
Winson Chungb1777442015-06-16 13:35:04 -0700239 // Calculate the position and size of the scroll bar
Sunny Goyal5d9fb0e2016-10-08 17:43:48 -0700240 mScrollbar.setThumbOffsetY(scrollBarY);
Winson Chung4b9f9792015-06-12 18:02:52 -0700241 }
242
243 /**
Winsonc0880492015-08-21 11:16:27 -0700244 * @return whether fast scrolling is supported in the current state.
Winson646c2362015-09-03 11:46:11 -0700245 */
246 protected boolean supportsFastScrolling() {
247 return true;
248 }
249
250 /**
Hyunyoung Songac5f6af2015-05-29 12:00:44 -0700251 * Maps the touch (from 0..1) to the adapter position that should be visible.
252 * <p>Override in each subclass of this base class.
Winsonc0880492015-08-21 11:16:27 -0700253 *
254 * @return the scroll top of this recycler view.
Hyunyoung Songac5f6af2015-05-29 12:00:44 -0700255 */
Peter Schiller2e22b5d2016-07-12 12:45:10 -0700256 public abstract int getCurrentScrollY();
Winsonc0880492015-08-21 11:16:27 -0700257
258 /**
259 * Maps the touch (from 0..1) to the adapter position that should be visible.
260 * <p>Override in each subclass of this base class.
261 */
262 protected abstract String scrollToPositionAtProgress(float touchFraction);
Hyunyoung Songac5f6af2015-05-29 12:00:44 -0700263
264 /**
265 * Updates the bounds for the scrollbar.
266 * <p>Override in each subclass of this base class.
267 */
Winsonc0880492015-08-21 11:16:27 -0700268 protected abstract void onUpdateScrollbar(int dy);
Hyunyoung Songac5f6af2015-05-29 12:00:44 -0700269
270 /**
Winson Chungb1777442015-06-16 13:35:04 -0700271 * <p>Override in each subclass of this base class.
Hyunyoung Songac5f6af2015-05-29 12:00:44 -0700272 */
Winsonc0880492015-08-21 11:16:27 -0700273 protected void onFastScrollCompleted() {}
Winson Chung94804152015-05-08 13:06:44 -0700274}