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