• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
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 
17 package com.android.launcher3;
18 
19 import android.content.Context;
20 import android.graphics.Canvas;
21 import android.graphics.Rect;
22 import android.support.v7.widget.RecyclerView;
23 import android.util.AttributeSet;
24 import android.view.MotionEvent;
25 import com.android.launcher3.util.Thunk;
26 
27 
28 /**
29  * 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>
34  */
35 public abstract class BaseRecyclerView extends RecyclerView
36         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;
43 
44     protected BaseRecyclerViewFastScrollBar mScrollbar;
45 
46     private int mDownX;
47     private int mDownY;
48     private int mLastY;
49     protected Rect mBackgroundPadding = new Rect();
50 
BaseRecyclerView(Context context)51     public BaseRecyclerView(Context context) {
52         this(context, null);
53     }
54 
BaseRecyclerView(Context context, AttributeSet attrs)55     public BaseRecyclerView(Context context, AttributeSet attrs) {
56         this(context, attrs, 0);
57     }
58 
BaseRecyclerView(Context context, AttributeSet attrs, int defStyleAttr)59     public BaseRecyclerView(Context context, AttributeSet attrs, int defStyleAttr) {
60         super(context, attrs, defStyleAttr);
61         mDeltaThreshold = getResources().getDisplayMetrics().density * SCROLL_DELTA_THRESHOLD_DP;
62         mScrollbar = new BaseRecyclerViewFastScrollBar(this, getResources());
63 
64         ScrollListener listener = new ScrollListener();
65         setOnScrollListener(listener);
66     }
67 
68     private class ScrollListener extends OnScrollListener {
ScrollListener()69         public ScrollListener() {
70             // Do nothing
71         }
72 
73         @Override
onScrolled(RecyclerView recyclerView, int dx, int dy)74         public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
75             mDy = dy;
76 
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.
80 
81             onUpdateScrollbar(dy);
82         }
83     }
84 
reset()85     public void reset() {
86         mScrollbar.reattachThumbToScroll();
87     }
88 
89     @Override
onFinishInflate()90     protected void onFinishInflate() {
91         super.onFinishInflate();
92         addOnItemTouchListener(this);
93     }
94 
95     /**
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      */
99     @Override
onInterceptTouchEvent(RecyclerView rv, MotionEvent ev)100     public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent ev) {
101         return handleTouchEvent(ev);
102     }
103 
104     @Override
onTouchEvent(RecyclerView rv, MotionEvent ev)105     public void onTouchEvent(RecyclerView rv, MotionEvent ev) {
106         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      */
handleTouchEvent(MotionEvent ev)113     private boolean handleTouchEvent(MotionEvent ev) {
114         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
120                 mDownX = x;
121                 mDownY = mLastY = y;
122                 if (shouldStopScroll(ev)) {
123                     stopScroll();
124                 }
125                 mScrollbar.handleTouchEvent(ev, mDownX, mDownY, mLastY);
126                 break;
127             case MotionEvent.ACTION_MOVE:
128                 mLastY = y;
129                 mScrollbar.handleTouchEvent(ev, mDownX, mDownY, mLastY);
130                 break;
131             case MotionEvent.ACTION_UP:
132             case MotionEvent.ACTION_CANCEL:
133                 onFastScrollCompleted();
134                 mScrollbar.handleTouchEvent(ev, mDownX, mDownY, mLastY);
135                 break;
136         }
137         return mScrollbar.isDraggingThumb();
138     }
139 
onRequestDisallowInterceptTouchEvent(boolean disallowIntercept)140     public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
141         // DO NOT REMOVE, NEEDED IMPLEMENTATION FOR M BUILDS
142     }
143 
144     /**
145      * Returns whether this {@link MotionEvent} should trigger the scroll to be stopped.
146      */
shouldStopScroll(MotionEvent ev)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     }
158 
updateBackgroundPadding(Rect padding)159     public void updateBackgroundPadding(Rect padding) {
160         mBackgroundPadding.set(padding);
161     }
162 
getBackgroundPadding()163     public Rect getBackgroundPadding() {
164         return mBackgroundPadding;
165     }
166 
167     /**
168      * Returns the scroll bar width when the user is scrolling.
169      */
getMaxScrollbarWidth()170     public int getMaxScrollbarWidth() {
171         return mScrollbar.getThumbMaxWidth();
172     }
173 
174     /**
175      * Returns the visible height of the recycler view:
176      *   VisibleHeight = View height - top padding - bottom padding
177      */
getVisibleHeight()178     protected int getVisibleHeight() {
179         int visibleHeight = getHeight() - mBackgroundPadding.top - mBackgroundPadding.bottom;
180         return visibleHeight;
181     }
182 
183     /**
184      * Returns the available scroll height:
185      *   AvailableScrollHeight = Total height of the all items - last page height
186      */
getAvailableScrollHeight()187     protected abstract int getAvailableScrollHeight();
188 
189     /**
190      * Returns the available scroll bar height:
191      *   AvailableScrollBarHeight = Total height of the visible view - thumb height
192      */
getAvailableScrollBarHeight()193     protected int getAvailableScrollBarHeight() {
194         int availableScrollBarHeight = getVisibleHeight() - mScrollbar.getThumbHeight();
195         return availableScrollBarHeight;
196     }
197 
198     /**
199      * Returns the track color (ignoring alpha), can be overridden by each subclass.
200      */
getFastScrollerTrackColor(int defaultTrackColor)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      */
getFastScrollerThumbInactiveColor(int defaultInactiveThumbColor)208     public int getFastScrollerThumbInactiveColor(int defaultInactiveThumbColor) {
209         return defaultInactiveThumbColor;
210     }
211 
212     /**
213      * Returns the scrollbar for this recycler view.
214      */
getScrollBar()215     public BaseRecyclerViewFastScrollBar getScrollBar() {
216         return mScrollbar;
217     }
218 
219     @Override
dispatchDraw(Canvas canvas)220     protected void dispatchDraw(Canvas canvas) {
221         super.dispatchDraw(canvas);
222         onUpdateScrollbar(0);
223         mScrollbar.draw(canvas);
224     }
225 
226     /**
227      * 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      *
231      * @param scrollY the current scroll y
232      */
synchronizeScrollBarThumbOffsetToViewScroll(int scrollY, int availableScrollHeight)233     protected void synchronizeScrollBarThumbOffsetToViewScroll(int scrollY,
234             int availableScrollHeight) {
235         // Only show the scrollbar if there is height to be scrolled
236         int availableScrollBarHeight = getAvailableScrollBarHeight();
237         if (availableScrollHeight <= 0) {
238             mScrollbar.setThumbOffset(-1, -1);
239             return;
240         }
241 
242         // 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)
245         int scrollBarY = mBackgroundPadding.top +
246                 (int) (((float) scrollY / availableScrollHeight) * availableScrollBarHeight);
247 
248         // Calculate the position and size of the scroll bar
249         mScrollbar.setThumbOffset(getScrollBarX(), scrollBarY);
250     }
251 
252     /**
253      * @return the x position for the scrollbar thumb
254      */
getScrollBarX()255     protected int getScrollBarX() {
256         if (Utilities.isRtl(getResources())) {
257             return mBackgroundPadding.left;
258         } else {
259             return getWidth() - mBackgroundPadding.right - mScrollbar.getThumbWidth();
260         }
261     }
262 
263     /**
264      * @return whether fast scrolling is supported in the current state.
265      */
supportsFastScrolling()266     protected boolean supportsFastScrolling() {
267         return true;
268     }
269 
270     /**
271      * Maps the touch (from 0..1) to the adapter position that should be visible.
272      * <p>Override in each subclass of this base class.
273      *
274      * @return the scroll top of this recycler view.
275      */
getCurrentScrollY()276     public abstract int getCurrentScrollY();
277 
278     /**
279      * Maps the touch (from 0..1) to the adapter position that should be visible.
280      * <p>Override in each subclass of this base class.
281      */
scrollToPositionAtProgress(float touchFraction)282     protected abstract String scrollToPositionAtProgress(float touchFraction);
283 
284     /**
285      * Updates the bounds for the scrollbar.
286      * <p>Override in each subclass of this base class.
287      */
onUpdateScrollbar(int dy)288     protected abstract void onUpdateScrollbar(int dy);
289 
290     /**
291      * <p>Override in each subclass of this base class.
292      */
onFastScrollCompleted()293     protected void onFastScrollCompleted() {}
294 }