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.support.v7.widget.RecyclerView;
21import android.util.AttributeSet;
22import android.view.MotionEvent;
23import android.view.View;
24import android.view.ViewGroup;
25
26import 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 */
36public 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}