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}