Home | History | Annotate | Download | only in launcher3
      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.graphics.Canvas;
     21 import android.support.v7.widget.RecyclerView;
     22 import android.util.AttributeSet;
     23 import android.view.MotionEvent;
     24 import android.view.ViewGroup;
     25 
     26 import com.android.launcher3.util.Thunk;
     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         implements RecyclerView.OnItemTouchListener {
     38 
     39     private static final int SCROLL_DELTA_THRESHOLD_DP = 4;
     40 
     41     /** Keeps the last known scrolling delta/velocity along y-axis. */
     42     @Thunk int mDy = 0;
     43     private float mDeltaThreshold;
     44 
     45     protected final BaseRecyclerViewFastScrollBar mScrollbar;
     46 
     47     private int mDownX;
     48     private int mDownY;
     49     private int mLastY;
     50 
     51     public BaseRecyclerView(Context context) {
     52         this(context, null);
     53     }
     54 
     55     public BaseRecyclerView(Context context, AttributeSet attrs) {
     56         this(context, attrs, 0);
     57     }
     58 
     59     public BaseRecyclerView(Context context, AttributeSet attrs, int defStyleAttr) {
     60         super(context, attrs, defStyleAttr);
     61         mDeltaThreshold = getResources().getDisplayMetrics().density * SCROLL_DELTA_THRESHOLD_DP;
     62         mScrollbar = new BaseRecyclerViewFastScrollBar(this, getResources());
     63 
     64         ScrollListener listener = new ScrollListener();
     65         setOnScrollListener(listener);
     66     }
     67 
     68     private class ScrollListener extends OnScrollListener {
     69         public ScrollListener() {
     70             // Do nothing
     71         }
     72 
     73         @Override
     74         public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
     75             mDy = dy;
     76 
     77             // TODO(winsonc): If we want to animate the section heads while scrolling, we can
     78             //                initiate that here if the recycler view scroll state is not
     79             //                RecyclerView.SCROLL_STATE_IDLE.
     80 
     81             onUpdateScrollbar(dy);
     82         }
     83     }
     84 
     85     public void reset() {
     86         mScrollbar.reattachThumbToScroll();
     87     }
     88 
     89     @Override
     90     protected void onFinishInflate() {
     91         super.onFinishInflate();
     92         addOnItemTouchListener(this);
     93     }
     94 
     95     @Override
     96     protected void onAttachedToWindow() {
     97         super.onAttachedToWindow();
     98         mScrollbar.setPopupView(((ViewGroup) getParent()).findViewById(R.id.fast_scroller_popup));
     99     }
    100 
    101     /**
    102      * We intercept the touch handling only to support fast scrolling when initiated from the
    103      * scroll bar.  Otherwise, we fall back to the default RecyclerView touch handling.
    104      */
    105     @Override
    106     public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent ev) {
    107         return handleTouchEvent(ev);
    108     }
    109 
    110     @Override
    111     public void onTouchEvent(RecyclerView rv, MotionEvent ev) {
    112         handleTouchEvent(ev);
    113     }
    114 
    115     /**
    116      * Handles the touch event and determines whether to show the fast scroller (or updates it if
    117      * it is already showing).
    118      */
    119     private boolean handleTouchEvent(MotionEvent ev) {
    120         int action = ev.getAction();
    121         int x = (int) ev.getX();
    122         int y = (int) ev.getY();
    123         switch (action) {
    124             case MotionEvent.ACTION_DOWN:
    125                 // Keep track of the down positions
    126                 mDownX = x;
    127                 mDownY = mLastY = y;
    128                 if (shouldStopScroll(ev)) {
    129                     stopScroll();
    130                 }
    131                 mScrollbar.handleTouchEvent(ev, mDownX, mDownY, mLastY);
    132                 break;
    133             case MotionEvent.ACTION_MOVE:
    134                 mLastY = y;
    135                 mScrollbar.handleTouchEvent(ev, mDownX, mDownY, mLastY);
    136                 break;
    137             case MotionEvent.ACTION_UP:
    138             case MotionEvent.ACTION_CANCEL:
    139                 onFastScrollCompleted();
    140                 mScrollbar.handleTouchEvent(ev, mDownX, mDownY, mLastY);
    141                 break;
    142         }
    143         return mScrollbar.isDraggingThumb();
    144     }
    145 
    146     public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
    147         // DO NOT REMOVE, NEEDED IMPLEMENTATION FOR M BUILDS
    148     }
    149 
    150     /**
    151      * Returns whether this {@link MotionEvent} should trigger the scroll to be stopped.
    152      */
    153     protected boolean shouldStopScroll(MotionEvent ev) {
    154         if (ev.getAction() == MotionEvent.ACTION_DOWN) {
    155             if ((Math.abs(mDy) < mDeltaThreshold &&
    156                     getScrollState() != RecyclerView.SCROLL_STATE_IDLE)) {
    157                 // now the touch events are being passed to the {@link WidgetCell} until the
    158                 // touch sequence goes over the touch slop.
    159                 return true;
    160             }
    161         }
    162         return false;
    163     }
    164 
    165     /**
    166      * Returns the height of the fast scroll bar
    167      */
    168     protected int getScrollbarTrackHeight() {
    169         return getHeight();
    170     }
    171 
    172     /**
    173      * Returns the available scroll height:
    174      *   AvailableScrollHeight = Total height of the all items - last page height
    175      */
    176     protected abstract int getAvailableScrollHeight();
    177 
    178     /**
    179      * Returns the available scroll bar height:
    180      *   AvailableScrollBarHeight = Total height of the visible view - thumb height
    181      */
    182     protected int getAvailableScrollBarHeight() {
    183         int availableScrollBarHeight = getScrollbarTrackHeight() - mScrollbar.getThumbHeight();
    184         return availableScrollBarHeight;
    185     }
    186 
    187     /**
    188      * Returns the track color (ignoring alpha), can be overridden by each subclass.
    189      */
    190     public int getFastScrollerTrackColor(int defaultTrackColor) {
    191         return defaultTrackColor;
    192     }
    193 
    194     /**
    195      * Returns the scrollbar for this recycler view.
    196      */
    197     public BaseRecyclerViewFastScrollBar getScrollBar() {
    198         return mScrollbar;
    199     }
    200 
    201     @Override
    202     protected void dispatchDraw(Canvas canvas) {
    203         super.dispatchDraw(canvas);
    204         onUpdateScrollbar(0);
    205         mScrollbar.draw(canvas);
    206     }
    207 
    208     /**
    209      * Updates the scrollbar thumb offset to match the visible scroll of the recycler view.  It does
    210      * this by mapping the available scroll area of the recycler view to the available space for the
    211      * scroll bar.
    212      *
    213      * @param scrollY the current scroll y
    214      */
    215     protected void synchronizeScrollBarThumbOffsetToViewScroll(int scrollY,
    216             int availableScrollHeight) {
    217         // Only show the scrollbar if there is height to be scrolled
    218         if (availableScrollHeight <= 0) {
    219             mScrollbar.setThumbOffsetY(-1);
    220             return;
    221         }
    222 
    223         // Calculate the current scroll position, the scrollY of the recycler view accounts for the
    224         // view padding, while the scrollBarY is drawn right up to the background padding (ignoring
    225         // padding)
    226         int scrollBarY =
    227                 (int) (((float) scrollY / availableScrollHeight) * getAvailableScrollBarHeight());
    228 
    229         // Calculate the position and size of the scroll bar
    230         mScrollbar.setThumbOffsetY(scrollBarY);
    231     }
    232 
    233     /**
    234      * @return whether fast scrolling is supported in the current state.
    235      */
    236     protected boolean supportsFastScrolling() {
    237         return true;
    238     }
    239 
    240     /**
    241      * Maps the touch (from 0..1) to the adapter position that should be visible.
    242      * <p>Override in each subclass of this base class.
    243      *
    244      * @return the scroll top of this recycler view.
    245      */
    246     public abstract int getCurrentScrollY();
    247 
    248     /**
    249      * Maps the touch (from 0..1) to the adapter position that should be visible.
    250      * <p>Override in each subclass of this base class.
    251      */
    252     protected abstract String scrollToPositionAtProgress(float touchFraction);
    253 
    254     /**
    255      * Updates the bounds for the scrollbar.
    256      * <p>Override in each subclass of this base class.
    257      */
    258     protected abstract void onUpdateScrollbar(int dy);
    259 
    260     /**
    261      * <p>Override in each subclass of this base class.
    262      */
    263     protected void onFastScrollCompleted() {}
    264 }