AllAppsRecyclerView.java revision b713ad4adb7d7dbc926ee2ab793ee6ef38cd2520
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; 24243fdd7cdf262b341b4f66177af27eec4f5cde45Winson Chungimport android.view.ContextThemeWrapper; 25f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chungimport android.view.View; 26b713ad4adb7d7dbc926ee2ab793ee6ef38cd2520Sunny Goyal 275f4e0fdd2e4edeb9211e2dcd1c99497f175731f8Winson Chungimport com.android.launcher3.BaseRecyclerView; 28b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chungimport com.android.launcher3.BaseRecyclerViewFastScrollBar; 295f4e0fdd2e4edeb9211e2dcd1c99497f175731f8Winson Chungimport com.android.launcher3.DeviceProfile; 305f4e0fdd2e4edeb9211e2dcd1c99497f175731f8Winson Chungimport com.android.launcher3.Launcher; 318f1eff7b6cc8621888ee46605c32e601f80a890bWinson Chungimport com.android.launcher3.Stats; 32b713ad4adb7d7dbc926ee2ab793ee6ef38cd2520Sunny Goyalimport com.android.launcher3.util.Thunk; 3393f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung 3493f98eaf1800024cb2f28379bdd997f3debae63aWinson Chungimport java.util.List; 3593f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung 3693f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung/** 37ac5f6af1488ec1cf0b73aa0848a675764c2f652bHyunyoung Song * A RecyclerView with custom fast scroll support for the all apps view. 3893f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung */ 398f1eff7b6cc8621888ee46605c32e601f80a890bWinson Chungpublic class AllAppsRecyclerView extends BaseRecyclerView 408f1eff7b6cc8621888ee46605c32e601f80a890bWinson Chung implements Stats.LaunchSourceProvider { 4193f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung 42b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung private static final int FAST_SCROLL_MODE_JUMP_TO_FIRST_ICON = 0; 43b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung private static final int FAST_SCROLL_MODE_FREE_SCROLL = 1; 44b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung 45b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung private static final int FAST_SCROLL_BAR_MODE_DISTRIBUTE_BY_ROW = 0; 46b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung private static final int FAST_SCROLL_BAR_MODE_DISTRIBUTE_BY_SECTIONS = 1; 47b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung 4893f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung private AlphabeticalAppsList mApps; 4993f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung private int mNumAppsPerRow; 50208ed75cfdb02e571273d73d056d8ed7f6f756ebWinson Chung private int mPredictionBarHeight; 51b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung 52b713ad4adb7d7dbc926ee2ab793ee6ef38cd2520Sunny Goyal @Thunk BaseRecyclerViewFastScrollBar.FastScrollFocusableView mLastFastScrollFocusedView; 53b713ad4adb7d7dbc926ee2ab793ee6ef38cd2520Sunny Goyal @Thunk int mPrevFastScrollFocusedPosition; 54b713ad4adb7d7dbc926ee2ab793ee6ef38cd2520Sunny Goyal @Thunk int mFastScrollFrameIndex; 55b713ad4adb7d7dbc926ee2ab793ee6ef38cd2520Sunny Goyal @Thunk final int[] mFastScrollFrames = new int[10]; 56b713ad4adb7d7dbc926ee2ab793ee6ef38cd2520Sunny Goyal 57b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung private final int mFastScrollMode = FAST_SCROLL_MODE_JUMP_TO_FIRST_ICON; 58b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung private final int mScrollBarMode = FAST_SCROLL_BAR_MODE_DISTRIBUTE_BY_ROW; 5993f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung 602e6da1539bc7286336b3c24d96ab76434939ce4dAdam Cohen private Launcher mLauncher; 61b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung private ScrollPositionState mScrollPosState = new ScrollPositionState(); 622e6da1539bc7286336b3c24d96ab76434939ce4dAdam Cohen 635f4e0fdd2e4edeb9211e2dcd1c99497f175731f8Winson Chung public AllAppsRecyclerView(Context context) { 6493f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung this(context, null); 6593f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung } 6693f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung 675f4e0fdd2e4edeb9211e2dcd1c99497f175731f8Winson Chung public AllAppsRecyclerView(Context context, AttributeSet attrs) { 6893f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung this(context, attrs, 0); 6993f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung } 7093f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung 715f4e0fdd2e4edeb9211e2dcd1c99497f175731f8Winson Chung public AllAppsRecyclerView(Context context, AttributeSet attrs, int defStyleAttr) { 7293f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung this(context, attrs, defStyleAttr, 0); 7393f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung } 7493f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung 755f4e0fdd2e4edeb9211e2dcd1c99497f175731f8Winson Chung public AllAppsRecyclerView(Context context, AttributeSet attrs, int defStyleAttr, 7693f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung int defStyleRes) { 7793f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung super(context, attrs, defStyleAttr); 78243fdd7cdf262b341b4f66177af27eec4f5cde45Winson Chung // We have a theme on this view, so we need to coerce the base activity context from that 79243fdd7cdf262b341b4f66177af27eec4f5cde45Winson Chung ContextThemeWrapper ctx = (ContextThemeWrapper) context; 80243fdd7cdf262b341b4f66177af27eec4f5cde45Winson Chung mLauncher = (Launcher) ctx.getBaseContext(); 8193f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung } 8293f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung 8393f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung /** 8493f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung * Sets the list of apps in this view, used to determine the fastscroll position. 8593f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung */ 8693f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung public void setApps(AlphabeticalAppsList apps) { 8793f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung mApps = apps; 8893f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung } 8993f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung 9093f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung /** 9193f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung * Sets the number of apps per row in this recycler view. 9293f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung */ 93b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung public void setNumAppsPerRow(int numAppsPerRow) { 94208ed75cfdb02e571273d73d056d8ed7f6f756ebWinson Chung mNumAppsPerRow = numAppsPerRow; 95da25da3d557177febb9c7afb78e04b1ef4a45f17Winson Chung 962e6da1539bc7286336b3c24d96ab76434939ce4dAdam Cohen DeviceProfile grid = mLauncher.getDeviceProfile(); 97da25da3d557177febb9c7afb78e04b1ef4a45f17Winson Chung RecyclerView.RecycledViewPool pool = getRecycledViewPool(); 98da25da3d557177febb9c7afb78e04b1ef4a45f17Winson Chung int approxRows = (int) Math.ceil(grid.availableHeightPx / grid.allAppsIconSizePx); 995f4e0fdd2e4edeb9211e2dcd1c99497f175731f8Winson Chung pool.setMaxRecycledViews(AllAppsGridAdapter.PREDICTION_BAR_SPACER_TYPE, 1); 1005f4e0fdd2e4edeb9211e2dcd1c99497f175731f8Winson Chung pool.setMaxRecycledViews(AllAppsGridAdapter.EMPTY_SEARCH_VIEW_TYPE, 1); 1015f4e0fdd2e4edeb9211e2dcd1c99497f175731f8Winson Chung pool.setMaxRecycledViews(AllAppsGridAdapter.ICON_VIEW_TYPE, approxRows * mNumAppsPerRow); 1025f4e0fdd2e4edeb9211e2dcd1c99497f175731f8Winson Chung pool.setMaxRecycledViews(AllAppsGridAdapter.SECTION_BREAK_VIEW_TYPE, approxRows); 10393f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung } 10493f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung 10593f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung /** 106208ed75cfdb02e571273d73d056d8ed7f6f756ebWinson Chung * Sets the prediction bar height. 107208ed75cfdb02e571273d73d056d8ed7f6f756ebWinson Chung */ 108208ed75cfdb02e571273d73d056d8ed7f6f756ebWinson Chung public void setPredictionBarHeight(int height) { 109208ed75cfdb02e571273d73d056d8ed7f6f756ebWinson Chung mPredictionBarHeight = height; 110208ed75cfdb02e571273d73d056d8ed7f6f756ebWinson Chung } 111208ed75cfdb02e571273d73d056d8ed7f6f756ebWinson Chung 112208ed75cfdb02e571273d73d056d8ed7f6f756ebWinson Chung /** 113ed0c1cc739d3b3d82c301299db8abc3146de49bbWinson Chung * Scrolls this recycler view to the top. 114ed0c1cc739d3b3d82c301299db8abc3146de49bbWinson Chung */ 115ed0c1cc739d3b3d82c301299db8abc3146de49bbWinson Chung public void scrollToTop() { 116ed0c1cc739d3b3d82c301299db8abc3146de49bbWinson Chung scrollToPosition(0); 11732f14071cc5808737c88da7b5f3047a8222f8843Winson Chung } 11832f14071cc5808737c88da7b5f3047a8222f8843Winson Chung 11932f14071cc5808737c88da7b5f3047a8222f8843Winson Chung /** 12032f14071cc5808737c88da7b5f3047a8222f8843Winson Chung * Returns the current scroll position. 12132f14071cc5808737c88da7b5f3047a8222f8843Winson Chung */ 12232f14071cc5808737c88da7b5f3047a8222f8843Winson Chung public int getScrollPosition() { 12332f14071cc5808737c88da7b5f3047a8222f8843Winson Chung List<AlphabeticalAppsList.AdapterItem> items = mApps.getAdapterItems(); 124b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung getCurScrollState(mScrollPosState, items); 125b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung if (mScrollPosState.rowIndex != -1) { 12632f14071cc5808737c88da7b5f3047a8222f8843Winson Chung int predictionBarHeight = mApps.getPredictedApps().isEmpty() ? 0 : mPredictionBarHeight; 127b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung return getPaddingTop() + predictionBarHeight + 128b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung (mScrollPosState.rowIndex * mScrollPosState.rowHeight) - 129b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung mScrollPosState.rowTopOffset; 13032f14071cc5808737c88da7b5f3047a8222f8843Winson Chung } 13132f14071cc5808737c88da7b5f3047a8222f8843Winson Chung return 0; 132ed0c1cc739d3b3d82c301299db8abc3146de49bbWinson Chung } 133ed0c1cc739d3b3d82c301299db8abc3146de49bbWinson Chung 134243fdd7cdf262b341b4f66177af27eec4f5cde45Winson Chung /** 135243fdd7cdf262b341b4f66177af27eec4f5cde45Winson Chung * We need to override the draw to ensure that we don't draw the overscroll effect beyond the 136243fdd7cdf262b341b4f66177af27eec4f5cde45Winson Chung * background bounds. 137243fdd7cdf262b341b4f66177af27eec4f5cde45Winson Chung */ 138243fdd7cdf262b341b4f66177af27eec4f5cde45Winson Chung @Override 139243fdd7cdf262b341b4f66177af27eec4f5cde45Winson Chung protected void dispatchDraw(Canvas canvas) { 140243fdd7cdf262b341b4f66177af27eec4f5cde45Winson Chung canvas.clipRect(mBackgroundPadding.left, mBackgroundPadding.top, 141243fdd7cdf262b341b4f66177af27eec4f5cde45Winson Chung getWidth() - mBackgroundPadding.right, 142243fdd7cdf262b341b4f66177af27eec4f5cde45Winson Chung getHeight() - mBackgroundPadding.bottom); 143243fdd7cdf262b341b4f66177af27eec4f5cde45Winson Chung super.dispatchDraw(canvas); 144243fdd7cdf262b341b4f66177af27eec4f5cde45Winson Chung } 145243fdd7cdf262b341b4f66177af27eec4f5cde45Winson Chung 14693f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung @Override 14793f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung protected void onFinishInflate() { 148ebe1734a67dff9ab46f3c6cce328a86b714ce620Hyunyoung Song super.onFinishInflate(); 149ef7f874a889b609bd34e692b9c9a1f8cefd1ea95Winson Chung 150ef7f874a889b609bd34e692b9c9a1f8cefd1ea95Winson Chung // Bind event handlers 15193f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung addOnItemTouchListener(this); 15293f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung } 15393f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung 1548f1eff7b6cc8621888ee46605c32e601f80a890bWinson Chung @Override 1558f1eff7b6cc8621888ee46605c32e601f80a890bWinson Chung public void fillInLaunchSourceData(Bundle sourceData) { 1568f1eff7b6cc8621888ee46605c32e601f80a890bWinson Chung sourceData.putString(Stats.SOURCE_EXTRA_CONTAINER, Stats.CONTAINER_ALL_APPS); 1578f1eff7b6cc8621888ee46605c32e601f80a890bWinson Chung if (mApps.hasFilter()) { 1588f1eff7b6cc8621888ee46605c32e601f80a890bWinson Chung sourceData.putString(Stats.SOURCE_EXTRA_SUB_CONTAINER, 1598f1eff7b6cc8621888ee46605c32e601f80a890bWinson Chung Stats.SUB_CONTAINER_ALL_APPS_SEARCH); 1608f1eff7b6cc8621888ee46605c32e601f80a890bWinson Chung } else { 1618f1eff7b6cc8621888ee46605c32e601f80a890bWinson Chung sourceData.putString(Stats.SOURCE_EXTRA_SUB_CONTAINER, 1628f1eff7b6cc8621888ee46605c32e601f80a890bWinson Chung Stats.SUB_CONTAINER_ALL_APPS_A_Z); 1638f1eff7b6cc8621888ee46605c32e601f80a890bWinson Chung } 1648f1eff7b6cc8621888ee46605c32e601f80a890bWinson Chung } 1658f1eff7b6cc8621888ee46605c32e601f80a890bWinson Chung 16693f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung /** 16799d96ba6c8e3258f7d99a33d49da2aeb0da5d862Winson Chung * Maps the touch (from 0..1) to the adapter position that should be visible. 16893f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung */ 169ac5f6af1488ec1cf0b73aa0848a675764c2f652bHyunyoung Song @Override 170ac5f6af1488ec1cf0b73aa0848a675764c2f652bHyunyoung Song public String scrollToPositionAtProgress(float touchFraction) { 171b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung int rowCount = mApps.getNumAppRows(); 172b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung if (rowCount == 0) { 1739121fbffafe0b90a23768e5ebc5fffab2a2175c2Winson Chung return ""; 1749121fbffafe0b90a23768e5ebc5fffab2a2175c2Winson Chung } 17593f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung 176208ed75cfdb02e571273d73d056d8ed7f6f756ebWinson Chung // Stop the scroller if it is scrolling 177208ed75cfdb02e571273d73d056d8ed7f6f756ebWinson Chung stopScroll(); 178208ed75cfdb02e571273d73d056d8ed7f6f756ebWinson Chung 179b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung // Find the fastscroll section that maps to this touch fraction 180b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung List<AlphabeticalAppsList.FastScrollSectionInfo> fastScrollSections = 181b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung mApps.getFastScrollerSections(); 182b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung AlphabeticalAppsList.FastScrollSectionInfo lastInfo = fastScrollSections.get(0); 183b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung if (mScrollBarMode == FAST_SCROLL_BAR_MODE_DISTRIBUTE_BY_ROW) { 184b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung for (int i = 1; i < fastScrollSections.size(); i++) { 185b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung AlphabeticalAppsList.FastScrollSectionInfo info = fastScrollSections.get(i); 186b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung if (info.touchFraction > touchFraction) { 187b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung break; 188b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung } 189b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung lastInfo = info; 190208ed75cfdb02e571273d73d056d8ed7f6f756ebWinson Chung } 191b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung } else if (mScrollBarMode == FAST_SCROLL_BAR_MODE_DISTRIBUTE_BY_SECTIONS){ 192b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung lastInfo = fastScrollSections.get((int) (touchFraction * (fastScrollSections.size() - 1))); 193b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung } else { 194b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung throw new RuntimeException("Unexpected scroll bar mode"); 195208ed75cfdb02e571273d73d056d8ed7f6f756ebWinson Chung } 196208ed75cfdb02e571273d73d056d8ed7f6f756ebWinson Chung 197b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung // Map the touch position back to the scroll of the recycler view 198b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung getCurScrollState(mScrollPosState, mApps.getAdapterItems()); 199b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung int predictionBarHeight = mApps.getPredictedApps().isEmpty() ? 0 : mPredictionBarHeight; 200b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung int availableScrollHeight = getAvailableScrollHeight(rowCount, mScrollPosState.rowHeight, 201b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung predictionBarHeight); 202b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung LinearLayoutManager layoutManager = (LinearLayoutManager) getLayoutManager(); 203b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung if (mFastScrollMode == FAST_SCROLL_MODE_FREE_SCROLL) { 204b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung layoutManager.scrollToPositionWithOffset(0, (int) -(availableScrollHeight * touchFraction)); 20593f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung } 20693f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung 207b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung if (mPrevFastScrollFocusedPosition != lastInfo.fastScrollToItem.position) { 208b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung mPrevFastScrollFocusedPosition = lastInfo.fastScrollToItem.position; 209208ed75cfdb02e571273d73d056d8ed7f6f756ebWinson Chung 210b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung // Reset the last focused view 211b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung if (mLastFastScrollFocusedView != null) { 212b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung mLastFastScrollFocusedView.setFastScrollFocused(false, true); 213b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung mLastFastScrollFocusedView = null; 214b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung } 215f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung 216b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung if (mFastScrollMode == FAST_SCROLL_MODE_JUMP_TO_FIRST_ICON) { 217b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung smoothSnapToPosition(mPrevFastScrollFocusedPosition, mScrollPosState); 218b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung } else if (mFastScrollMode == FAST_SCROLL_MODE_FREE_SCROLL) { 219b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung final ViewHolder vh = findViewHolderForPosition(mPrevFastScrollFocusedPosition); 220b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung if (vh != null && 221b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung vh.itemView instanceof BaseRecyclerViewFastScrollBar.FastScrollFocusableView) { 222b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung mLastFastScrollFocusedView = 223b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung (BaseRecyclerViewFastScrollBar.FastScrollFocusableView) vh.itemView; 224b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung mLastFastScrollFocusedView.setFastScrollFocused(true, true); 225b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung } 226b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung } else { 227b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung throw new RuntimeException("Unexpected fast scroll mode"); 228ac5f6af1488ec1cf0b73aa0848a675764c2f652bHyunyoung Song } 229ac5f6af1488ec1cf0b73aa0848a675764c2f652bHyunyoung Song } 230b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung return lastInfo.sectionName; 231ac5f6af1488ec1cf0b73aa0848a675764c2f652bHyunyoung Song } 232ac5f6af1488ec1cf0b73aa0848a675764c2f652bHyunyoung Song 233b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung @Override 234b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung public void onFastScrollCompleted() { 235b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung super.onFastScrollCompleted(); 236b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung // Reset and clean up the last focused view 237b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung if (mLastFastScrollFocusedView != null) { 238b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung mLastFastScrollFocusedView.setFastScrollFocused(false, true); 239b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung mLastFastScrollFocusedView = null; 240ac5f6af1488ec1cf0b73aa0848a675764c2f652bHyunyoung Song } 241b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung mPrevFastScrollFocusedPosition = -1; 242ac5f6af1488ec1cf0b73aa0848a675764c2f652bHyunyoung Song } 243ac5f6af1488ec1cf0b73aa0848a675764c2f652bHyunyoung Song 244f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung /** 245515f2897e876ba52ed6371d22e6d0f7d1016aaf3Winson Chung * Updates the bounds for the scrollbar. 246f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung */ 247ac5f6af1488ec1cf0b73aa0848a675764c2f652bHyunyoung Song @Override 248b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung public void onUpdateScrollbar() { 24983f59abc9c566da5deb98afe7ea35cfb061f2920Winson Chung List<AlphabeticalAppsList.AdapterItem> items = mApps.getAdapterItems(); 25083f59abc9c566da5deb98afe7ea35cfb061f2920Winson Chung 251ef7f874a889b609bd34e692b9c9a1f8cefd1ea95Winson Chung // Skip early if there are no items or we haven't been measured 252ef7f874a889b609bd34e692b9c9a1f8cefd1ea95Winson Chung if (items.isEmpty() || mNumAppsPerRow == 0) { 253b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung mScrollbar.setScrollbarThumbOffset(-1, -1); 254f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung return; 255f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung } 256f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung 257f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung // Find the index and height of the first visible row (all rows have the same height) 258b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung int rowCount = mApps.getNumAppRows(); 259b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung getCurScrollState(mScrollPosState, items); 260b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung if (mScrollPosState.rowIndex < 0) { 261b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung mScrollbar.setScrollbarThumbOffset(-1, -1); 262b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung return; 263b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung } 264b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung 2658c01f810ebcddeb2e9f3a6599c62f701e9c3cac9Winson Chung int predictionBarHeight = mApps.getPredictedApps().isEmpty() ? 0 : mPredictionBarHeight; 266b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung synchronizeScrollBarThumbOffsetToViewScroll(mScrollPosState, rowCount, predictionBarHeight); 267b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung } 268f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung 269b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung /** 270b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung * This runnable runs a single frame of the smooth scroll animation and posts the next frame 271b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung * if necessary. 272b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung */ 273b713ad4adb7d7dbc926ee2ab793ee6ef38cd2520Sunny Goyal @Thunk Runnable mSmoothSnapNextFrameRunnable = new Runnable() { 274b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung @Override 275b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung public void run() { 276b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung if (mFastScrollFrameIndex < mFastScrollFrames.length) { 277b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung scrollBy(0, mFastScrollFrames[mFastScrollFrameIndex]); 278b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung mFastScrollFrameIndex++; 279b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung postOnAnimation(mSmoothSnapNextFrameRunnable); 280b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung } else { 281b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung // Animation completed, set the fast scroll state on the target view 282b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung final ViewHolder vh = findViewHolderForPosition(mPrevFastScrollFocusedPosition); 283b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung if (vh != null && 284b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung vh.itemView instanceof BaseRecyclerViewFastScrollBar.FastScrollFocusableView && 285b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung mLastFastScrollFocusedView != vh.itemView) { 286b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung mLastFastScrollFocusedView = 287b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung (BaseRecyclerViewFastScrollBar.FastScrollFocusableView) vh.itemView; 288b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung mLastFastScrollFocusedView.setFastScrollFocused(true, true); 289b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung } 290f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung } 291f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung } 292b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung }; 293b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung 294b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung /** 295b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung * Smoothly snaps to a given position. We do this manually by calculating the keyframes 296b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung * ourselves and animating the scroll on the recycler view. 297b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung */ 298b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung private void smoothSnapToPosition(final int position, ScrollPositionState scrollPosState) { 299b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung removeCallbacks(mSmoothSnapNextFrameRunnable); 300b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung 301b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung // Calculate the full animation from the current scroll position to the final scroll 302b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung // position, and then run the animation for the duration. 303b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung int predictionBarHeight = mApps.getPredictedApps().isEmpty() ? 0 : mPredictionBarHeight; 304b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung int curScrollY = getPaddingTop() + predictionBarHeight + 305b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung (scrollPosState.rowIndex * scrollPosState.rowHeight) - scrollPosState.rowTopOffset; 306b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung int newScrollY = getScrollAtPosition(position, scrollPosState.rowHeight); 307b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung int numFrames = mFastScrollFrames.length; 308b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung for (int i = 0; i < numFrames; i++) { 309b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung // TODO(winsonc): We can interpolate this as well. 310b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung mFastScrollFrames[i] = (newScrollY - curScrollY) / numFrames; 311b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung } 312b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung mFastScrollFrameIndex = 0; 313b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung postOnAnimation(mSmoothSnapNextFrameRunnable); 314f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung } 315ed0c1cc739d3b3d82c301299db8abc3146de49bbWinson Chung 316ed0c1cc739d3b3d82c301299db8abc3146de49bbWinson Chung /** 317b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung * Returns the current scroll state of the apps rows, not including the prediction 318b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung * bar. 319ed0c1cc739d3b3d82c301299db8abc3146de49bbWinson Chung */ 320ed0c1cc739d3b3d82c301299db8abc3146de49bbWinson Chung private void getCurScrollState(ScrollPositionState stateOut, 321ed0c1cc739d3b3d82c301299db8abc3146de49bbWinson Chung List<AlphabeticalAppsList.AdapterItem> items) { 322ed0c1cc739d3b3d82c301299db8abc3146de49bbWinson Chung stateOut.rowIndex = -1; 323ed0c1cc739d3b3d82c301299db8abc3146de49bbWinson Chung stateOut.rowTopOffset = -1; 324ed0c1cc739d3b3d82c301299db8abc3146de49bbWinson Chung stateOut.rowHeight = -1; 325532e6ea2e6e4b5f968038b52db9bb998cb9fe84aWinson Chung 326ef7f874a889b609bd34e692b9c9a1f8cefd1ea95Winson Chung // Return early if there are no items or we haven't been measured 327ef7f874a889b609bd34e692b9c9a1f8cefd1ea95Winson Chung if (items.isEmpty() || mNumAppsPerRow == 0) { 328532e6ea2e6e4b5f968038b52db9bb998cb9fe84aWinson Chung return; 329532e6ea2e6e4b5f968038b52db9bb998cb9fe84aWinson Chung } 330532e6ea2e6e4b5f968038b52db9bb998cb9fe84aWinson Chung 331ed0c1cc739d3b3d82c301299db8abc3146de49bbWinson Chung int childCount = getChildCount(); 332ed0c1cc739d3b3d82c301299db8abc3146de49bbWinson Chung for (int i = 0; i < childCount; i++) { 333ed0c1cc739d3b3d82c301299db8abc3146de49bbWinson Chung View child = getChildAt(i); 334ed0c1cc739d3b3d82c301299db8abc3146de49bbWinson Chung int position = getChildPosition(child); 335ed0c1cc739d3b3d82c301299db8abc3146de49bbWinson Chung if (position != NO_POSITION) { 336ed0c1cc739d3b3d82c301299db8abc3146de49bbWinson Chung AlphabeticalAppsList.AdapterItem item = items.get(position); 3375f4e0fdd2e4edeb9211e2dcd1c99497f175731f8Winson Chung if (item.viewType == AllAppsGridAdapter.ICON_VIEW_TYPE) { 338b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung stateOut.rowIndex = item.rowIndex; 339ed0c1cc739d3b3d82c301299db8abc3146de49bbWinson Chung stateOut.rowTopOffset = getLayoutManager().getDecoratedTop(child); 340ed0c1cc739d3b3d82c301299db8abc3146de49bbWinson Chung stateOut.rowHeight = child.getHeight(); 341ed0c1cc739d3b3d82c301299db8abc3146de49bbWinson Chung break; 342ed0c1cc739d3b3d82c301299db8abc3146de49bbWinson Chung } 343ed0c1cc739d3b3d82c301299db8abc3146de49bbWinson Chung } 344ed0c1cc739d3b3d82c301299db8abc3146de49bbWinson Chung } 345ed0c1cc739d3b3d82c301299db8abc3146de49bbWinson Chung } 346b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung 347b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung /** 348b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung * Returns the scrollY for the given position in the adapter. 349b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung */ 350b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung private int getScrollAtPosition(int position, int rowHeight) { 351b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung AlphabeticalAppsList.AdapterItem item = mApps.getAdapterItems().get(position); 352b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung if (item.viewType == AllAppsGridAdapter.ICON_VIEW_TYPE) { 353b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung int predictionBarHeight = mApps.getPredictedApps().isEmpty() ? 0 : mPredictionBarHeight; 354b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung return getPaddingTop() + predictionBarHeight + item.rowIndex * rowHeight; 355b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung } else { 356b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung return 0; 357b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung } 358b1777447d9b9700b48f8060f8b318f2363c43e8dWinson Chung } 35993f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung} 360