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