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