• 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.util.AttributeSet;
21 import android.view.MotionEvent;
22 import android.view.View;
23 import android.view.accessibility.AccessibilityNodeInfo;
24 
25 import androidx.annotation.Nullable;
26 import androidx.recyclerview.widget.RecyclerView;
27 
28 import com.android.launcher3.compat.AccessibilityManagerCompat;
29 import com.android.launcher3.views.RecyclerViewFastScroller;
30 
31 
32 /**
33  * A base {@link RecyclerView}, which does the following:
34  * <ul>
35  *   <li> NOT intercept a touch unless the scrolling velocity is below a predefined threshold.
36  *   <li> Enable fast scroller.
37  * </ul>
38  */
39 public abstract class FastScrollRecyclerView extends RecyclerView  {
40 
41     protected RecyclerViewFastScroller mScrollbar;
42 
FastScrollRecyclerView(Context context)43     public FastScrollRecyclerView(Context context) {
44         this(context, null);
45     }
46 
FastScrollRecyclerView(Context context, AttributeSet attrs)47     public FastScrollRecyclerView(Context context, AttributeSet attrs) {
48         this(context, attrs, 0);
49     }
50 
FastScrollRecyclerView(Context context, AttributeSet attrs, int defStyleAttr)51     public FastScrollRecyclerView(Context context, AttributeSet attrs, int defStyleAttr) {
52         super(context, attrs, defStyleAttr);
53     }
54 
bindFastScrollbar(RecyclerViewFastScroller scrollbar)55     public void bindFastScrollbar(RecyclerViewFastScroller scrollbar) {
56         mScrollbar = scrollbar;
57         mScrollbar.setRecyclerView(this);
58         onUpdateScrollbar(0);
59     }
60 
61     @Nullable
getScrollbar()62     public RecyclerViewFastScroller getScrollbar() {
63         return mScrollbar;
64     }
65 
getScrollBarTop()66     public int getScrollBarTop() {
67         return getPaddingTop();
68     }
69 
getScrollBarMarginBottom()70     public int getScrollBarMarginBottom() {
71         return getPaddingBottom();
72     }
73 
74     /**
75      * Returns the height of the fast scroll bar
76      */
getScrollbarTrackHeight()77     public int getScrollbarTrackHeight() {
78         return mScrollbar.getHeight() - getScrollBarTop() - getScrollBarMarginBottom();
79     }
80 
81     /**
82      * Returns the available scroll height:
83      *   AvailableScrollHeight = Total height of the all items - last page height
84      */
getAvailableScrollHeight()85     protected int getAvailableScrollHeight() {
86         // AvailableScrollHeight = Total height of the all items - first page height
87         int firstPageHeight = getMeasuredHeight() - getPaddingTop() - getPaddingBottom();
88         int availableScrollHeight = computeVerticalScrollRange() - firstPageHeight;
89         return Math.max(0, availableScrollHeight);
90     }
91 
92     /**
93      * Returns the available scroll bar height:
94      *   AvailableScrollBarHeight = Total height of the visible view - thumb height
95      */
getAvailableScrollBarHeight()96     protected int getAvailableScrollBarHeight() {
97         return getScrollbarTrackHeight() - mScrollbar.getThumbHeight();
98     }
99 
100     /**
101      * Updates the scrollbar thumb offset to match the visible scroll of the recycler view.  It does
102      * this by mapping the available scroll area of the recycler view to the available space for the
103      * scroll bar.
104      *
105      * @param scrollY the current scroll y
106      */
synchronizeScrollBarThumbOffsetToViewScroll(int scrollY, int availableScrollHeight)107     protected void synchronizeScrollBarThumbOffsetToViewScroll(int scrollY,
108             int availableScrollHeight) {
109         // Only show the scrollbar if there is height to be scrolled
110         if (availableScrollHeight <= 0) {
111             mScrollbar.setThumbOffsetY(-1);
112             return;
113         }
114 
115         // Calculate the current scroll position, the scrollY of the recycler view accounts for the
116         // view padding, while the scrollBarY is drawn right up to the background padding (ignoring
117         // padding)
118         int scrollBarY =
119                 (int) (((float) scrollY / availableScrollHeight) * getAvailableScrollBarHeight());
120 
121         // Calculate the position and size of the scroll bar
122         mScrollbar.setThumbOffsetY(scrollBarY);
123     }
124 
125     /**
126      * Returns whether the view itself will handle the touch event or not.
127      * @param ev MotionEvent in {@param eventSource}
128      */
shouldContainerScroll(MotionEvent ev, View eventSource)129     public boolean shouldContainerScroll(MotionEvent ev, View eventSource) {
130         float[] point = new float[2];
131         point[0] = ev.getX();
132         point[1] = ev.getY();
133         Utilities.mapCoordInSelfToDescendant(mScrollbar, eventSource, point);
134         // IF the MotionEvent is inside the thumb, container should not be pulled down.
135         if (mScrollbar.shouldBlockIntercept((int) point[0], (int) point[1])) {
136             return false;
137         }
138 
139         // IF scroller is at the very top OR there is no scroll bar because there is probably not
140         // enough items to scroll, THEN it's okay for the container to be pulled down.
141         return computeVerticalScrollOffset() == 0;
142     }
143 
144     /**
145      * @return whether fast scrolling is supported in the current state.
146      */
supportsFastScrolling()147     public boolean supportsFastScrolling() {
148         return true;
149     }
150 
151     /**
152      * Maps the touch (from 0..1) to the adapter position that should be visible.
153      * <p>Override in each subclass of this base class.
154      */
scrollToPositionAtProgress(float touchFraction)155     public abstract String scrollToPositionAtProgress(float touchFraction);
156 
157     /**
158      * Updates the bounds for the scrollbar.
159      * <p>Override in each subclass of this base class.
160      */
onUpdateScrollbar(int dy)161     public abstract void onUpdateScrollbar(int dy);
162 
163     /**
164      * <p>Override in each subclass of this base class.
165      */
onFastScrollCompleted()166     public void onFastScrollCompleted() {}
167 
168     @Override
onScrollStateChanged(int state)169     public void onScrollStateChanged(int state) {
170         super.onScrollStateChanged(state);
171 
172         if (state == SCROLL_STATE_IDLE) {
173             AccessibilityManagerCompat.sendScrollFinishedEventToTest(getContext());
174         }
175     }
176 
177     @Override
onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info)178     public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
179         super.onInitializeAccessibilityNodeInfo(info);
180         if (isLayoutSuppressed()) info.setScrollable(false);
181     }
182 
183     /**
184      * Scrolls this recycler view to the top.
185      */
scrollToTop()186     public void scrollToTop() {
187         if (mScrollbar != null) {
188             mScrollbar.reattachThumbToScroll();
189         }
190         scrollToPosition(0);
191     }
192 }
193