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