AllAppsRecyclerView.java revision 2605900854a64eef49380332de17935d8204597d
193f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung/*
293f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung * Copyright (C) 2015 The Android Open Source Project
393f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung *
493f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung * Licensed under the Apache License, Version 2.0 (the "License");
593f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung * you may not use this file except in compliance with the License.
693f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung * You may obtain a copy of the License at
793f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung *
893f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung *      http://www.apache.org/licenses/LICENSE-2.0
993f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung *
1093f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung * Unless required by applicable law or agreed to in writing, software
1193f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung * distributed under the License is distributed on an "AS IS" BASIS,
1293f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1393f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung * See the License for the specific language governing permissions and
1493f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung * limitations under the License.
1593f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung */
165f4e0fdd2e4edeb9211e2dcd1c99497f175731f8Winson Chungpackage com.android.launcher3.allapps;
1793f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung
1893f98eaf1800024cb2f28379bdd997f3debae63aWinson Chungimport android.content.Context;
19243fdd7cdf262b341b4f66177af27eec4f5cde45Winson Chungimport android.graphics.Canvas;
208f1eff7b6cc8621888ee46605c32e601f80a890bWinson Chungimport android.os.Bundle;
2124cf70092b8b0281df071891573642f56e34f9e5Winson Chungimport android.support.v7.widget.LinearLayoutManager;
2293f98eaf1800024cb2f28379bdd997f3debae63aWinson Chungimport android.support.v7.widget.RecyclerView;
2393f98eaf1800024cb2f28379bdd997f3debae63aWinson Chungimport android.util.AttributeSet;
24f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chungimport android.view.View;
25b713ad4adb7d7dbc926ee2ab793ee6ef38cd2520Sunny Goyal
265f4e0fdd2e4edeb9211e2dcd1c99497f175731f8Winson Chungimport com.android.launcher3.BaseRecyclerView;
27b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chungimport com.android.launcher3.BaseRecyclerViewFastScrollBar;
285f4e0fdd2e4edeb9211e2dcd1c99497f175731f8Winson Chungimport com.android.launcher3.DeviceProfile;
298f1eff7b6cc8621888ee46605c32e601f80a890bWinson Chungimport com.android.launcher3.Stats;
301ae7a5018b48dba562bc18821f0f1e778192ee85Winson Chungimport com.android.launcher3.Utilities;
31b713ad4adb7d7dbc926ee2ab793ee6ef38cd2520Sunny Goyalimport com.android.launcher3.util.Thunk;
3293f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung
3393f98eaf1800024cb2f28379bdd997f3debae63aWinson Chungimport java.util.List;
3493f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung
3593f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung/**
36ac5f6af1488ec1cf0b73aa0848a675764c2f652bHyunyoung Song * A RecyclerView with custom fast scroll support for the all apps view.
3793f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung */
388f1eff7b6cc8621888ee46605c32e601f80a890bWinson Chungpublic class AllAppsRecyclerView extends BaseRecyclerView
398f1eff7b6cc8621888ee46605c32e601f80a890bWinson Chung        implements Stats.LaunchSourceProvider {
4093f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung
41b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung    private static final int FAST_SCROLL_MODE_JUMP_TO_FIRST_ICON = 0;
42b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung    private static final int FAST_SCROLL_MODE_FREE_SCROLL = 1;
43b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung
44b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung    private static final int FAST_SCROLL_BAR_MODE_DISTRIBUTE_BY_ROW = 0;
45b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung    private static final int FAST_SCROLL_BAR_MODE_DISTRIBUTE_BY_SECTIONS = 1;
46b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung
4793f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung    private AlphabeticalAppsList mApps;
4893f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung    private int mNumAppsPerRow;
49b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung
50b713ad4adb7d7dbc926ee2ab793ee6ef38cd2520Sunny Goyal    @Thunk BaseRecyclerViewFastScrollBar.FastScrollFocusableView mLastFastScrollFocusedView;
51b713ad4adb7d7dbc926ee2ab793ee6ef38cd2520Sunny Goyal    @Thunk int mPrevFastScrollFocusedPosition;
52b713ad4adb7d7dbc926ee2ab793ee6ef38cd2520Sunny Goyal    @Thunk int mFastScrollFrameIndex;
53b713ad4adb7d7dbc926ee2ab793ee6ef38cd2520Sunny Goyal    @Thunk final int[] mFastScrollFrames = new int[10];
54b713ad4adb7d7dbc926ee2ab793ee6ef38cd2520Sunny Goyal
55b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung    private final int mFastScrollMode = FAST_SCROLL_MODE_JUMP_TO_FIRST_ICON;
56b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung    private final int mScrollBarMode = FAST_SCROLL_BAR_MODE_DISTRIBUTE_BY_ROW;
5793f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung
58b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung    private ScrollPositionState mScrollPosState = new ScrollPositionState();
592e6da1539bc7286336b3c24d96ab76434939ce4dAdam Cohen
605f4e0fdd2e4edeb9211e2dcd1c99497f175731f8Winson Chung    public AllAppsRecyclerView(Context context) {
6193f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung        this(context, null);
6293f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung    }
6393f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung
645f4e0fdd2e4edeb9211e2dcd1c99497f175731f8Winson Chung    public AllAppsRecyclerView(Context context, AttributeSet attrs) {
6593f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung        this(context, attrs, 0);
6693f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung    }
6793f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung
685f4e0fdd2e4edeb9211e2dcd1c99497f175731f8Winson Chung    public AllAppsRecyclerView(Context context, AttributeSet attrs, int defStyleAttr) {
6993f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung        this(context, attrs, defStyleAttr, 0);
7093f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung    }
7193f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung
725f4e0fdd2e4edeb9211e2dcd1c99497f175731f8Winson Chung    public AllAppsRecyclerView(Context context, AttributeSet attrs, int defStyleAttr,
7393f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung            int defStyleRes) {
7493f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung        super(context, attrs, defStyleAttr);
75d2eb49e4c3bb37d35e72c36d8a308262b690075fWinson        mScrollbar.setDetachThumbOnFastScroll();
7693f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung    }
7793f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung
7893f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung    /**
7993f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung     * Sets the list of apps in this view, used to determine the fastscroll position.
8093f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung     */
8193f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung    public void setApps(AlphabeticalAppsList apps) {
8293f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung        mApps = apps;
8393f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung    }
8493f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung
8593f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung    /**
8693f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung     * Sets the number of apps per row in this recycler view.
8793f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung     */
881cddad6c6bff0b271e8ebde44013a5a09f273436Winson Chung    public void setNumAppsPerRow(DeviceProfile grid, int numAppsPerRow) {
89208ed75cfdb02e571273d73d056d8ed7f6f756ebWinson Chung        mNumAppsPerRow = numAppsPerRow;
90da25da3d557177febb9c7afb78e04b1ef4a45f17Winson Chung
91da25da3d557177febb9c7afb78e04b1ef4a45f17Winson Chung        RecyclerView.RecycledViewPool pool = getRecycledViewPool();
92da25da3d557177febb9c7afb78e04b1ef4a45f17Winson Chung        int approxRows = (int) Math.ceil(grid.availableHeightPx / grid.allAppsIconSizePx);
935f4e0fdd2e4edeb9211e2dcd1c99497f175731f8Winson Chung        pool.setMaxRecycledViews(AllAppsGridAdapter.EMPTY_SEARCH_VIEW_TYPE, 1);
94bedf9232eb67a420f0372d3ca135ca13194e603bWinson Chung        pool.setMaxRecycledViews(AllAppsGridAdapter.SEARCH_MARKET_DIVIDER_VIEW_TYPE, 1);
95bedf9232eb67a420f0372d3ca135ca13194e603bWinson Chung        pool.setMaxRecycledViews(AllAppsGridAdapter.SEARCH_MARKET_VIEW_TYPE, 1);
965f4e0fdd2e4edeb9211e2dcd1c99497f175731f8Winson Chung        pool.setMaxRecycledViews(AllAppsGridAdapter.ICON_VIEW_TYPE, approxRows * mNumAppsPerRow);
971ae7a5018b48dba562bc18821f0f1e778192ee85Winson Chung        pool.setMaxRecycledViews(AllAppsGridAdapter.PREDICTION_ICON_VIEW_TYPE, mNumAppsPerRow);
985f4e0fdd2e4edeb9211e2dcd1c99497f175731f8Winson Chung        pool.setMaxRecycledViews(AllAppsGridAdapter.SECTION_BREAK_VIEW_TYPE, approxRows);
9993f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung    }
10093f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung
10193f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung    /**
102ed0c1cc739d3b3d82c301299db8abc3146de49bbWinson Chung     * Scrolls this recycler view to the top.
103ed0c1cc739d3b3d82c301299db8abc3146de49bbWinson Chung     */
104ed0c1cc739d3b3d82c301299db8abc3146de49bbWinson Chung    public void scrollToTop() {
1052605900854a64eef49380332de17935d8204597dWinson        // Ensure we reattach the scrollbar if it was previously detached while fast-scrolling
1062605900854a64eef49380332de17935d8204597dWinson        if (mScrollbar.isThumbDetached()) {
1072605900854a64eef49380332de17935d8204597dWinson            mScrollbar.reattachThumbToScroll();
1082605900854a64eef49380332de17935d8204597dWinson        }
109ed0c1cc739d3b3d82c301299db8abc3146de49bbWinson Chung        scrollToPosition(0);
11032f14071cc5808737c88da7b5f3047a8222f8843Winson Chung    }
11132f14071cc5808737c88da7b5f3047a8222f8843Winson Chung
11232f14071cc5808737c88da7b5f3047a8222f8843Winson Chung    /**
113243fdd7cdf262b341b4f66177af27eec4f5cde45Winson Chung     * We need to override the draw to ensure that we don't draw the overscroll effect beyond the
114243fdd7cdf262b341b4f66177af27eec4f5cde45Winson Chung     * background bounds.
115243fdd7cdf262b341b4f66177af27eec4f5cde45Winson Chung     */
116243fdd7cdf262b341b4f66177af27eec4f5cde45Winson Chung    @Override
117243fdd7cdf262b341b4f66177af27eec4f5cde45Winson Chung    protected void dispatchDraw(Canvas canvas) {
118243fdd7cdf262b341b4f66177af27eec4f5cde45Winson Chung        canvas.clipRect(mBackgroundPadding.left, mBackgroundPadding.top,
119243fdd7cdf262b341b4f66177af27eec4f5cde45Winson Chung                getWidth() - mBackgroundPadding.right,
120243fdd7cdf262b341b4f66177af27eec4f5cde45Winson Chung                getHeight() - mBackgroundPadding.bottom);
121243fdd7cdf262b341b4f66177af27eec4f5cde45Winson Chung        super.dispatchDraw(canvas);
122243fdd7cdf262b341b4f66177af27eec4f5cde45Winson Chung    }
123243fdd7cdf262b341b4f66177af27eec4f5cde45Winson Chung
12493f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung    @Override
12593f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung    protected void onFinishInflate() {
126ebe1734a67dff9ab46f3c6cce328a86b714ce620Hyunyoung Song        super.onFinishInflate();
127ef7f874a889b609bd34e692b9c9a1f8cefd1ea95Winson Chung
128ef7f874a889b609bd34e692b9c9a1f8cefd1ea95Winson Chung        // Bind event handlers
12993f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung        addOnItemTouchListener(this);
13093f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung    }
13193f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung
1328f1eff7b6cc8621888ee46605c32e601f80a890bWinson Chung    @Override
1338f1eff7b6cc8621888ee46605c32e601f80a890bWinson Chung    public void fillInLaunchSourceData(Bundle sourceData) {
1348f1eff7b6cc8621888ee46605c32e601f80a890bWinson Chung        sourceData.putString(Stats.SOURCE_EXTRA_CONTAINER, Stats.CONTAINER_ALL_APPS);
1358f1eff7b6cc8621888ee46605c32e601f80a890bWinson Chung        if (mApps.hasFilter()) {
1368f1eff7b6cc8621888ee46605c32e601f80a890bWinson Chung            sourceData.putString(Stats.SOURCE_EXTRA_SUB_CONTAINER,
1378f1eff7b6cc8621888ee46605c32e601f80a890bWinson Chung                    Stats.SUB_CONTAINER_ALL_APPS_SEARCH);
1388f1eff7b6cc8621888ee46605c32e601f80a890bWinson Chung        } else {
1398f1eff7b6cc8621888ee46605c32e601f80a890bWinson Chung            sourceData.putString(Stats.SOURCE_EXTRA_SUB_CONTAINER,
1408f1eff7b6cc8621888ee46605c32e601f80a890bWinson Chung                    Stats.SUB_CONTAINER_ALL_APPS_A_Z);
1418f1eff7b6cc8621888ee46605c32e601f80a890bWinson Chung        }
1428f1eff7b6cc8621888ee46605c32e601f80a890bWinson Chung    }
1438f1eff7b6cc8621888ee46605c32e601f80a890bWinson Chung
14493f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung    /**
14599d96ba6c8e3258f7d99a33d49da2aeb0da5d862Winson Chung     * Maps the touch (from 0..1) to the adapter position that should be visible.
14693f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung     */
147ac5f6af1488ec1cf0b73aa0848a675764c2f652bHyunyoung Song    @Override
148ac5f6af1488ec1cf0b73aa0848a675764c2f652bHyunyoung Song    public String scrollToPositionAtProgress(float touchFraction) {
149b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung        int rowCount = mApps.getNumAppRows();
150b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung        if (rowCount == 0) {
1519121fbffafe0b90a23768e5ebc5fffab2a2175c2Winson Chung            return "";
1529121fbffafe0b90a23768e5ebc5fffab2a2175c2Winson Chung        }
15393f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung
154208ed75cfdb02e571273d73d056d8ed7f6f756ebWinson Chung        // Stop the scroller if it is scrolling
155208ed75cfdb02e571273d73d056d8ed7f6f756ebWinson Chung        stopScroll();
156208ed75cfdb02e571273d73d056d8ed7f6f756ebWinson Chung
157b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung        // Find the fastscroll section that maps to this touch fraction
158b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung        List<AlphabeticalAppsList.FastScrollSectionInfo> fastScrollSections =
159b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung                mApps.getFastScrollerSections();
160b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung        AlphabeticalAppsList.FastScrollSectionInfo lastInfo = fastScrollSections.get(0);
161b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung        if (mScrollBarMode == FAST_SCROLL_BAR_MODE_DISTRIBUTE_BY_ROW) {
162b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung            for (int i = 1; i < fastScrollSections.size(); i++) {
163b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung                AlphabeticalAppsList.FastScrollSectionInfo info = fastScrollSections.get(i);
164b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung                if (info.touchFraction > touchFraction) {
165b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung                    break;
166b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung                }
167b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung                lastInfo = info;
168208ed75cfdb02e571273d73d056d8ed7f6f756ebWinson Chung            }
169b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung        } else if (mScrollBarMode == FAST_SCROLL_BAR_MODE_DISTRIBUTE_BY_SECTIONS){
170b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung            lastInfo = fastScrollSections.get((int) (touchFraction * (fastScrollSections.size() - 1)));
171b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung        } else {
172b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung            throw new RuntimeException("Unexpected scroll bar mode");
173208ed75cfdb02e571273d73d056d8ed7f6f756ebWinson Chung        }
174208ed75cfdb02e571273d73d056d8ed7f6f756ebWinson Chung
175b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung        // Map the touch position back to the scroll of the recycler view
176d2eb49e4c3bb37d35e72c36d8a308262b690075fWinson        getCurScrollState(mScrollPosState);
177d2eb49e4c3bb37d35e72c36d8a308262b690075fWinson        int availableScrollHeight = getAvailableScrollHeight(rowCount, mScrollPosState.rowHeight);
178b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung        LinearLayoutManager layoutManager = (LinearLayoutManager) getLayoutManager();
179b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung        if (mFastScrollMode == FAST_SCROLL_MODE_FREE_SCROLL) {
180b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung            layoutManager.scrollToPositionWithOffset(0, (int) -(availableScrollHeight * touchFraction));
18193f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung        }
18293f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung
183b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung        if (mPrevFastScrollFocusedPosition != lastInfo.fastScrollToItem.position) {
184b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung            mPrevFastScrollFocusedPosition = lastInfo.fastScrollToItem.position;
185208ed75cfdb02e571273d73d056d8ed7f6f756ebWinson Chung
186b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung            // Reset the last focused view
187b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung            if (mLastFastScrollFocusedView != null) {
188b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung                mLastFastScrollFocusedView.setFastScrollFocused(false, true);
189b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung                mLastFastScrollFocusedView = null;
190b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung            }
191f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung
192b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung            if (mFastScrollMode == FAST_SCROLL_MODE_JUMP_TO_FIRST_ICON) {
193b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung                smoothSnapToPosition(mPrevFastScrollFocusedPosition, mScrollPosState);
194b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung            } else if (mFastScrollMode == FAST_SCROLL_MODE_FREE_SCROLL) {
195b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung                final ViewHolder vh = findViewHolderForPosition(mPrevFastScrollFocusedPosition);
196b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung                if (vh != null &&
197b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung                        vh.itemView instanceof BaseRecyclerViewFastScrollBar.FastScrollFocusableView) {
198b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung                    mLastFastScrollFocusedView =
199b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung                            (BaseRecyclerViewFastScrollBar.FastScrollFocusableView) vh.itemView;
200b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung                    mLastFastScrollFocusedView.setFastScrollFocused(true, true);
201b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung                }
202b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung            } else {
203b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung                throw new RuntimeException("Unexpected fast scroll mode");
204ac5f6af1488ec1cf0b73aa0848a675764c2f652bHyunyoung Song            }
205ac5f6af1488ec1cf0b73aa0848a675764c2f652bHyunyoung Song        }
206b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung        return lastInfo.sectionName;
207ac5f6af1488ec1cf0b73aa0848a675764c2f652bHyunyoung Song    }
208ac5f6af1488ec1cf0b73aa0848a675764c2f652bHyunyoung Song
209b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung    @Override
210b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung    public void onFastScrollCompleted() {
211b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung        super.onFastScrollCompleted();
212b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung        // Reset and clean up the last focused view
213b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung        if (mLastFastScrollFocusedView != null) {
214b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung            mLastFastScrollFocusedView.setFastScrollFocused(false, true);
215b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung            mLastFastScrollFocusedView = null;
216ac5f6af1488ec1cf0b73aa0848a675764c2f652bHyunyoung Song        }
217b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung        mPrevFastScrollFocusedPosition = -1;
218ac5f6af1488ec1cf0b73aa0848a675764c2f652bHyunyoung Song    }
219ac5f6af1488ec1cf0b73aa0848a675764c2f652bHyunyoung Song
220f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung    /**
221515f2897e876ba52ed6371d22e6d0f7d1016aaf3Winson Chung     * Updates the bounds for the scrollbar.
222f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung     */
223ac5f6af1488ec1cf0b73aa0848a675764c2f652bHyunyoung Song    @Override
224d2eb49e4c3bb37d35e72c36d8a308262b690075fWinson    public void onUpdateScrollbar(int dy) {
22583f59abc9c566da5deb98afe7ea35cfb061f2920Winson Chung        List<AlphabeticalAppsList.AdapterItem> items = mApps.getAdapterItems();
22683f59abc9c566da5deb98afe7ea35cfb061f2920Winson Chung
227ef7f874a889b609bd34e692b9c9a1f8cefd1ea95Winson Chung        // Skip early if there are no items or we haven't been measured
228ef7f874a889b609bd34e692b9c9a1f8cefd1ea95Winson Chung        if (items.isEmpty() || mNumAppsPerRow == 0) {
229d2eb49e4c3bb37d35e72c36d8a308262b690075fWinson            mScrollbar.setThumbOffset(-1, -1);
230f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung            return;
231f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung        }
232f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung
233f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung        // Find the index and height of the first visible row (all rows have the same height)
234b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung        int rowCount = mApps.getNumAppRows();
235d2eb49e4c3bb37d35e72c36d8a308262b690075fWinson        getCurScrollState(mScrollPosState);
236b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung        if (mScrollPosState.rowIndex < 0) {
237d2eb49e4c3bb37d35e72c36d8a308262b690075fWinson            mScrollbar.setThumbOffset(-1, -1);
238b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung            return;
239b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung        }
240b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung
241d2eb49e4c3bb37d35e72c36d8a308262b690075fWinson        // Only show the scrollbar if there is height to be scrolled
242d2eb49e4c3bb37d35e72c36d8a308262b690075fWinson        int availableScrollBarHeight = getAvailableScrollBarHeight();
243d2eb49e4c3bb37d35e72c36d8a308262b690075fWinson        int availableScrollHeight = getAvailableScrollHeight(mApps.getNumAppRows(), mScrollPosState.rowHeight);
244d2eb49e4c3bb37d35e72c36d8a308262b690075fWinson        if (availableScrollHeight <= 0) {
245d2eb49e4c3bb37d35e72c36d8a308262b690075fWinson            mScrollbar.setThumbOffset(-1, -1);
246d2eb49e4c3bb37d35e72c36d8a308262b690075fWinson            return;
247d2eb49e4c3bb37d35e72c36d8a308262b690075fWinson        }
248d2eb49e4c3bb37d35e72c36d8a308262b690075fWinson
249d2eb49e4c3bb37d35e72c36d8a308262b690075fWinson        // Calculate the current scroll position, the scrollY of the recycler view accounts for the
250d2eb49e4c3bb37d35e72c36d8a308262b690075fWinson        // view padding, while the scrollBarY is drawn right up to the background padding (ignoring
251d2eb49e4c3bb37d35e72c36d8a308262b690075fWinson        // padding)
252d2eb49e4c3bb37d35e72c36d8a308262b690075fWinson        int scrollY = getPaddingTop() +
253d2eb49e4c3bb37d35e72c36d8a308262b690075fWinson                (mScrollPosState.rowIndex * mScrollPosState.rowHeight) - mScrollPosState.rowTopOffset;
254d2eb49e4c3bb37d35e72c36d8a308262b690075fWinson        int scrollBarY = mBackgroundPadding.top +
255d2eb49e4c3bb37d35e72c36d8a308262b690075fWinson                (int) (((float) scrollY / availableScrollHeight) * availableScrollBarHeight);
256d2eb49e4c3bb37d35e72c36d8a308262b690075fWinson
257d2eb49e4c3bb37d35e72c36d8a308262b690075fWinson        if (mScrollbar.isThumbDetached()) {
258d2eb49e4c3bb37d35e72c36d8a308262b690075fWinson            int scrollBarX;
259d2eb49e4c3bb37d35e72c36d8a308262b690075fWinson            if (Utilities.isRtl(getResources())) {
260d2eb49e4c3bb37d35e72c36d8a308262b690075fWinson                scrollBarX = mBackgroundPadding.left;
261d2eb49e4c3bb37d35e72c36d8a308262b690075fWinson            } else {
262d2eb49e4c3bb37d35e72c36d8a308262b690075fWinson                scrollBarX = getWidth() - mBackgroundPadding.right - mScrollbar.getThumbWidth();
263d2eb49e4c3bb37d35e72c36d8a308262b690075fWinson            }
264d2eb49e4c3bb37d35e72c36d8a308262b690075fWinson
265d2eb49e4c3bb37d35e72c36d8a308262b690075fWinson            if (mScrollbar.isDraggingThumb()) {
266d2eb49e4c3bb37d35e72c36d8a308262b690075fWinson                // If the thumb is detached, then just update the thumb to the current
267d2eb49e4c3bb37d35e72c36d8a308262b690075fWinson                // touch position
268d2eb49e4c3bb37d35e72c36d8a308262b690075fWinson                mScrollbar.setThumbOffset(scrollBarX, (int) mScrollbar.getLastTouchY());
269d2eb49e4c3bb37d35e72c36d8a308262b690075fWinson            } else {
270d2eb49e4c3bb37d35e72c36d8a308262b690075fWinson                int thumbScrollY = mScrollbar.getThumbOffset().y;
271d2eb49e4c3bb37d35e72c36d8a308262b690075fWinson                int diffScrollY = scrollBarY - thumbScrollY;
272d2eb49e4c3bb37d35e72c36d8a308262b690075fWinson                if (diffScrollY * dy > 0f) {
273d2eb49e4c3bb37d35e72c36d8a308262b690075fWinson                    // User is scrolling in the same direction the thumb needs to catch up to the
27451a7d965443373e3a9f4fce1c0895669ed4110bdWinson                    // current scroll position.  We do this by mapping the difference in movement
27551a7d965443373e3a9f4fce1c0895669ed4110bdWinson                    // from the original scroll bar position to the difference in movement necessary
27651a7d965443373e3a9f4fce1c0895669ed4110bdWinson                    // in the detached thumb position to ensure that both speed towards the same
27751a7d965443373e3a9f4fce1c0895669ed4110bdWinson                    // position at either end of the list.
27851a7d965443373e3a9f4fce1c0895669ed4110bdWinson                    if (dy < 0) {
27951a7d965443373e3a9f4fce1c0895669ed4110bdWinson                        int offset = (int) ((dy * thumbScrollY) / (float) scrollBarY);
28051a7d965443373e3a9f4fce1c0895669ed4110bdWinson                        thumbScrollY += Math.max(offset, diffScrollY);
28151a7d965443373e3a9f4fce1c0895669ed4110bdWinson                    } else {
28251a7d965443373e3a9f4fce1c0895669ed4110bdWinson                        int offset = (int) ((dy * (availableScrollBarHeight - thumbScrollY)) /
28351a7d965443373e3a9f4fce1c0895669ed4110bdWinson                                (float) (availableScrollBarHeight - scrollBarY));
28451a7d965443373e3a9f4fce1c0895669ed4110bdWinson                        thumbScrollY += Math.min(offset, diffScrollY);
28551a7d965443373e3a9f4fce1c0895669ed4110bdWinson                    }
286d2eb49e4c3bb37d35e72c36d8a308262b690075fWinson                    thumbScrollY = Math.max(0, Math.min(availableScrollBarHeight, thumbScrollY));
287d2eb49e4c3bb37d35e72c36d8a308262b690075fWinson                    mScrollbar.setThumbOffset(scrollBarX, thumbScrollY);
288d2eb49e4c3bb37d35e72c36d8a308262b690075fWinson                    if (scrollBarY == thumbScrollY) {
289d2eb49e4c3bb37d35e72c36d8a308262b690075fWinson                        mScrollbar.reattachThumbToScroll();
290d2eb49e4c3bb37d35e72c36d8a308262b690075fWinson                    }
291d2eb49e4c3bb37d35e72c36d8a308262b690075fWinson                } else {
292d2eb49e4c3bb37d35e72c36d8a308262b690075fWinson                    // User is scrolling in an opposite direction to the direction that the thumb
293d2eb49e4c3bb37d35e72c36d8a308262b690075fWinson                    // needs to catch up to the scroll position.  Do nothing except for updating
294d2eb49e4c3bb37d35e72c36d8a308262b690075fWinson                    // the scroll bar x to match the thumb width.
295d2eb49e4c3bb37d35e72c36d8a308262b690075fWinson                    mScrollbar.setThumbOffset(scrollBarX, thumbScrollY);
296d2eb49e4c3bb37d35e72c36d8a308262b690075fWinson                }
297d2eb49e4c3bb37d35e72c36d8a308262b690075fWinson            }
298d2eb49e4c3bb37d35e72c36d8a308262b690075fWinson        } else {
299d2eb49e4c3bb37d35e72c36d8a308262b690075fWinson            synchronizeScrollBarThumbOffsetToViewScroll(mScrollPosState, rowCount);
300d2eb49e4c3bb37d35e72c36d8a308262b690075fWinson        }
301b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung    }
302f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung
303b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung    /**
304b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung     * This runnable runs a single frame of the smooth scroll animation and posts the next frame
305b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung     * if necessary.
306b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung     */
307b713ad4adb7d7dbc926ee2ab793ee6ef38cd2520Sunny Goyal    @Thunk Runnable mSmoothSnapNextFrameRunnable = new Runnable() {
308b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung        @Override
309b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung        public void run() {
310b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung            if (mFastScrollFrameIndex < mFastScrollFrames.length) {
311b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung                scrollBy(0, mFastScrollFrames[mFastScrollFrameIndex]);
312b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung                mFastScrollFrameIndex++;
313b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung                postOnAnimation(mSmoothSnapNextFrameRunnable);
314b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung            } else {
315b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung                // Animation completed, set the fast scroll state on the target view
316b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung                final ViewHolder vh = findViewHolderForPosition(mPrevFastScrollFocusedPosition);
317b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung                if (vh != null &&
318b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung                        vh.itemView instanceof BaseRecyclerViewFastScrollBar.FastScrollFocusableView &&
319b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung                        mLastFastScrollFocusedView != vh.itemView) {
320b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung                    mLastFastScrollFocusedView =
321b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung                            (BaseRecyclerViewFastScrollBar.FastScrollFocusableView) vh.itemView;
322b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung                    mLastFastScrollFocusedView.setFastScrollFocused(true, true);
323b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung                }
324f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung            }
325f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung        }
326b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung    };
327b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung
328b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung    /**
329b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung     * Smoothly snaps to a given position.  We do this manually by calculating the keyframes
330b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung     * ourselves and animating the scroll on the recycler view.
331b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung     */
332b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung    private void smoothSnapToPosition(final int position, ScrollPositionState scrollPosState) {
333b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung        removeCallbacks(mSmoothSnapNextFrameRunnable);
334b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung
335b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung        // Calculate the full animation from the current scroll position to the final scroll
336b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung        // position, and then run the animation for the duration.
3371ae7a5018b48dba562bc18821f0f1e778192ee85Winson Chung        int curScrollY = getPaddingTop() +
338b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung                (scrollPosState.rowIndex * scrollPosState.rowHeight) - scrollPosState.rowTopOffset;
339b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung        int newScrollY = getScrollAtPosition(position, scrollPosState.rowHeight);
340b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung        int numFrames = mFastScrollFrames.length;
341b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung        for (int i = 0; i < numFrames; i++) {
342b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung            // TODO(winsonc): We can interpolate this as well.
343b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung            mFastScrollFrames[i] = (newScrollY - curScrollY) / numFrames;
344b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung        }
345b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung        mFastScrollFrameIndex = 0;
346b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung        postOnAnimation(mSmoothSnapNextFrameRunnable);
347f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung    }
348ed0c1cc739d3b3d82c301299db8abc3146de49bbWinson Chung
349ed0c1cc739d3b3d82c301299db8abc3146de49bbWinson Chung    /**
3501ae7a5018b48dba562bc18821f0f1e778192ee85Winson Chung     * Returns the current scroll state of the apps rows.
351ed0c1cc739d3b3d82c301299db8abc3146de49bbWinson Chung     */
352d2eb49e4c3bb37d35e72c36d8a308262b690075fWinson    protected void getCurScrollState(ScrollPositionState stateOut) {
353ed0c1cc739d3b3d82c301299db8abc3146de49bbWinson Chung        stateOut.rowIndex = -1;
354ed0c1cc739d3b3d82c301299db8abc3146de49bbWinson Chung        stateOut.rowTopOffset = -1;
355ed0c1cc739d3b3d82c301299db8abc3146de49bbWinson Chung        stateOut.rowHeight = -1;
356532e6ea2e6e4b5f968038b52db9bb998cb9fe84aWinson Chung
357ef7f874a889b609bd34e692b9c9a1f8cefd1ea95Winson Chung        // Return early if there are no items or we haven't been measured
358d2eb49e4c3bb37d35e72c36d8a308262b690075fWinson        List<AlphabeticalAppsList.AdapterItem> items = mApps.getAdapterItems();
359ef7f874a889b609bd34e692b9c9a1f8cefd1ea95Winson Chung        if (items.isEmpty() || mNumAppsPerRow == 0) {
360532e6ea2e6e4b5f968038b52db9bb998cb9fe84aWinson Chung            return;
361532e6ea2e6e4b5f968038b52db9bb998cb9fe84aWinson Chung        }
362532e6ea2e6e4b5f968038b52db9bb998cb9fe84aWinson Chung
363ed0c1cc739d3b3d82c301299db8abc3146de49bbWinson Chung        int childCount = getChildCount();
364ed0c1cc739d3b3d82c301299db8abc3146de49bbWinson Chung        for (int i = 0; i < childCount; i++) {
365ed0c1cc739d3b3d82c301299db8abc3146de49bbWinson Chung            View child = getChildAt(i);
366ed0c1cc739d3b3d82c301299db8abc3146de49bbWinson Chung            int position = getChildPosition(child);
367ed0c1cc739d3b3d82c301299db8abc3146de49bbWinson Chung            if (position != NO_POSITION) {
368ed0c1cc739d3b3d82c301299db8abc3146de49bbWinson Chung                AlphabeticalAppsList.AdapterItem item = items.get(position);
3691ae7a5018b48dba562bc18821f0f1e778192ee85Winson Chung                if (item.viewType == AllAppsGridAdapter.ICON_VIEW_TYPE ||
3701ae7a5018b48dba562bc18821f0f1e778192ee85Winson Chung                        item.viewType == AllAppsGridAdapter.PREDICTION_ICON_VIEW_TYPE) {
371b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung                    stateOut.rowIndex = item.rowIndex;
372ed0c1cc739d3b3d82c301299db8abc3146de49bbWinson Chung                    stateOut.rowTopOffset = getLayoutManager().getDecoratedTop(child);
373ed0c1cc739d3b3d82c301299db8abc3146de49bbWinson Chung                    stateOut.rowHeight = child.getHeight();
374ed0c1cc739d3b3d82c301299db8abc3146de49bbWinson Chung                    break;
375ed0c1cc739d3b3d82c301299db8abc3146de49bbWinson Chung                }
376ed0c1cc739d3b3d82c301299db8abc3146de49bbWinson Chung            }
377ed0c1cc739d3b3d82c301299db8abc3146de49bbWinson Chung        }
378ed0c1cc739d3b3d82c301299db8abc3146de49bbWinson Chung    }
379b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung
380b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung    /**
381b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung     * Returns the scrollY for the given position in the adapter.
382b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung     */
383b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung    private int getScrollAtPosition(int position, int rowHeight) {
384b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung        AlphabeticalAppsList.AdapterItem item = mApps.getAdapterItems().get(position);
3851ae7a5018b48dba562bc18821f0f1e778192ee85Winson Chung        if (item.viewType == AllAppsGridAdapter.ICON_VIEW_TYPE ||
3861ae7a5018b48dba562bc18821f0f1e778192ee85Winson Chung                item.viewType == AllAppsGridAdapter.PREDICTION_ICON_VIEW_TYPE) {
3871ae7a5018b48dba562bc18821f0f1e778192ee85Winson Chung            int offset = item.rowIndex > 0 ? getPaddingTop() : 0;
3881ae7a5018b48dba562bc18821f0f1e778192ee85Winson Chung            return offset + item.rowIndex * rowHeight;
389b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung        } else {
390b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung            return 0;
391b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung        }
392b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung    }
39393f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung}
394