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