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 17package com.android.launcher3; 18 19import android.content.Context; 20import android.graphics.Canvas; 21import android.support.v7.widget.RecyclerView; 22import android.util.AttributeSet; 23import android.view.MotionEvent; 24import android.view.ViewGroup; 25import android.widget.TextView; 26 27import com.android.launcher3.views.RecyclerViewFastScroller; 28 29 30/** 31 * A base {@link RecyclerView}, which does the following: 32 * <ul> 33 * <li> NOT intercept a touch unless the scrolling velocity is below a predefined threshold. 34 * <li> Enable fast scroller. 35 * </ul> 36 */ 37public abstract class BaseRecyclerView extends RecyclerView 38 implements RecyclerView.OnItemTouchListener { 39 40 protected RecyclerViewFastScroller mScrollbar; 41 42 public BaseRecyclerView(Context context) { 43 this(context, null); 44 } 45 46 public BaseRecyclerView(Context context, AttributeSet attrs) { 47 this(context, attrs, 0); 48 } 49 50 public BaseRecyclerView(Context context, AttributeSet attrs, int defStyleAttr) { 51 super(context, attrs, defStyleAttr); 52 } 53 54 @Override 55 protected void onFinishInflate() { 56 super.onFinishInflate(); 57 addOnItemTouchListener(this); 58 } 59 60 @Override 61 protected void onAttachedToWindow() { 62 super.onAttachedToWindow(); 63 ViewGroup parent = (ViewGroup) getParent(); 64 mScrollbar = parent.findViewById(R.id.fast_scroller); 65 mScrollbar.setRecyclerView(this, (TextView) parent.findViewById(R.id.fast_scroller_popup)); 66 } 67 68 /** 69 * We intercept the touch handling only to support fast scrolling when initiated from the 70 * scroll bar. Otherwise, we fall back to the default RecyclerView touch handling. 71 */ 72 @Override 73 public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent ev) { 74 return handleTouchEvent(ev); 75 } 76 77 @Override 78 public void onTouchEvent(RecyclerView rv, MotionEvent ev) { 79 handleTouchEvent(ev); 80 } 81 82 /** 83 * Handles the touch event and determines whether to show the fast scroller (or updates it if 84 * it is already showing). 85 */ 86 private boolean handleTouchEvent(MotionEvent ev) { 87 // Move to mScrollbar's coordinate system. 88 int left = getLeft() - mScrollbar.getLeft(); 89 int top = getTop() - mScrollbar.getTop(); 90 ev.offsetLocation(left, top); 91 try { 92 return mScrollbar.handleTouchEvent(ev); 93 } finally { 94 ev.offsetLocation(-left, -top); 95 } 96 } 97 98 public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) { 99 // DO NOT REMOVE, NEEDED IMPLEMENTATION FOR M BUILDS 100 } 101 102 /** 103 * Returns the height of the fast scroll bar 104 */ 105 public int getScrollbarTrackHeight() { 106 return getHeight() - getPaddingTop() - getPaddingBottom(); 107 } 108 109 /** 110 * Returns the available scroll height: 111 * AvailableScrollHeight = Total height of the all items - last page height 112 */ 113 protected abstract int getAvailableScrollHeight(); 114 115 /** 116 * Returns the available scroll bar height: 117 * AvailableScrollBarHeight = Total height of the visible view - thumb height 118 */ 119 protected int getAvailableScrollBarHeight() { 120 int availableScrollBarHeight = getScrollbarTrackHeight() - mScrollbar.getThumbHeight(); 121 return availableScrollBarHeight; 122 } 123 124 /** 125 * Returns the scrollbar for this recycler view. 126 */ 127 public RecyclerViewFastScroller getScrollBar() { 128 return mScrollbar; 129 } 130 131 @Override 132 protected void dispatchDraw(Canvas canvas) { 133 onUpdateScrollbar(0); 134 super.dispatchDraw(canvas); 135 } 136 137 /** 138 * Updates the scrollbar thumb offset to match the visible scroll of the recycler view. It does 139 * this by mapping the available scroll area of the recycler view to the available space for the 140 * scroll bar. 141 * 142 * @param scrollY the current scroll y 143 */ 144 protected void synchronizeScrollBarThumbOffsetToViewScroll(int scrollY, 145 int availableScrollHeight) { 146 // Only show the scrollbar if there is height to be scrolled 147 if (availableScrollHeight <= 0) { 148 mScrollbar.setThumbOffsetY(-1); 149 return; 150 } 151 152 // Calculate the current scroll position, the scrollY of the recycler view accounts for the 153 // view padding, while the scrollBarY is drawn right up to the background padding (ignoring 154 // padding) 155 int scrollBarY = 156 (int) (((float) scrollY / availableScrollHeight) * getAvailableScrollBarHeight()); 157 158 // Calculate the position and size of the scroll bar 159 mScrollbar.setThumbOffsetY(scrollBarY); 160 } 161 162 /** 163 * @return whether fast scrolling is supported in the current state. 164 */ 165 public boolean supportsFastScrolling() { 166 return true; 167 } 168 169 /** 170 * Maps the touch (from 0..1) to the adapter position that should be visible. 171 * <p>Override in each subclass of this base class. 172 * 173 * @return the scroll top of this recycler view. 174 */ 175 public abstract int getCurrentScrollY(); 176 177 /** 178 * Maps the touch (from 0..1) to the adapter position that should be visible. 179 * <p>Override in each subclass of this base class. 180 */ 181 public abstract String scrollToPositionAtProgress(float touchFraction); 182 183 /** 184 * Updates the bounds for the scrollbar. 185 * <p>Override in each subclass of this base class. 186 */ 187 public abstract void onUpdateScrollbar(int dy); 188 189 /** 190 * <p>Override in each subclass of this base class. 191 */ 192 public void onFastScrollCompleted() {} 193}