From b1777447d9b9700b48f8060f8b318f2363c43e8d Mon Sep 17 00:00:00 2001 From: Winson Chung Date: Tue, 16 Jun 2015 13:35:04 -0700 Subject: Refactoring fast scroller. - Fixing issue with fast scroller not fitting name width. - Refactoring fast scrolling/scroll bar code out of base recycler view - Adding animations to fast scroller to match design - Smooth scrolling when jumping between app rows - Fixing issue with fast scroller jumping when you first pick it up - Fixing issue with wrong background paddings being used Bug: 21874346 Bug: 22031923 Change-Id: I9f011b1f375751f437604b900e95a2942d3f4601 --- .../launcher3/allapps/AllAppsContainerView.java | 32 +-- .../launcher3/allapps/AllAppsGridAdapter.java | 2 +- .../launcher3/allapps/AllAppsRecyclerView.java | 248 ++++++++++++--------- .../launcher3/allapps/AlphabeticalAppsList.java | 76 +++++-- 4 files changed, 228 insertions(+), 130 deletions(-) (limited to 'src/com/android/launcher3/allapps') diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java index d56e9fc1e..32b7be807 100644 --- a/src/com/android/launcher3/allapps/AllAppsContainerView.java +++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java @@ -17,7 +17,6 @@ package com.android.launcher3.allapps; import android.annotation.SuppressLint; import android.annotation.TargetApi; -import android.content.ComponentName; import android.content.Context; import android.content.res.Resources; import android.graphics.Point; @@ -155,6 +154,7 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc private int mSectionNamesMargin; private int mNumAppsPerRow; private int mNumPredictedAppsPerRow; + private int mRecyclerViewTopBottomPadding; // This coordinate is relative to this container view private final Point mBoundsCheckLastTouchDownPos = new Point(-1, -1); // This coordinate is relative to its parent @@ -189,7 +189,8 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc mPredictionBarHeight = (int) (grid.allAppsIconSizePx + grid.iconDrawablePaddingOriginalPx + Utilities.calculateTextHeight(grid.allAppsIconTextSizePx) + 2 * res.getDimensionPixelSize(R.dimen.all_apps_icon_top_bottom_padding) + - 2 * res.getDimensionPixelSize(R.dimen.all_apps_prediction_bar_top_bottom_padding)); + res.getDimensionPixelSize(R.dimen.all_apps_prediction_bar_top_padding) + + res.getDimensionPixelSize(R.dimen.all_apps_prediction_bar_bottom_padding)); mSectionNamesMargin = res.getDimensionPixelSize(R.dimen.all_apps_grid_view_start_margin); mApps = new AlphabeticalAppsList(context); mApps.setAdapterChangedCallback(this); @@ -199,6 +200,8 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc mApps.setAdapter(mAdapter); mLayoutManager = mAdapter.getLayoutManager(); mItemDecoration = mAdapter.getItemDecoration(); + mRecyclerViewTopBottomPadding = + res.getDimensionPixelSize(R.dimen.all_apps_list_top_bottom_padding); mSearchQueryBuilder = new SpannableStringBuilder(); Selection.setSelection(mSearchQueryBuilder, 0); @@ -414,7 +417,7 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc new SimpleSectionMergeAlgorithm((int) Math.ceil(mNumAppsPerRow / 2f), MIN_ROWS_IN_MERGED_SECTION_PHONE, MAX_NUM_MERGES_PHONE); - mAppsRecyclerView.setNumAppsPerRow(mNumAppsPerRow, mNumPredictedAppsPerRow); + mAppsRecyclerView.setNumAppsPerRow(mNumAppsPerRow); mAdapter.setNumAppsPerRow(mNumAppsPerRow); mApps.setNumAppsPerRow(mNumAppsPerRow, mNumPredictedAppsPerRow, mergeAlgorithm); } @@ -431,14 +434,16 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc protected void onUpdateBackgroundAndPaddings(Rect searchBarBounds, Rect padding) { boolean isRtl = Utilities.isRtl(getResources()); - // TODO: Use quantum_panel instead of quantum_panel_shape. + // TODO: Use quantum_panel instead of quantum_panel_shape InsetDrawable background = new InsetDrawable( getResources().getDrawable(R.drawable.quantum_panel_shape), padding.left, 0, padding.right, 0); + Rect bgPadding = new Rect(); + background.getPadding(bgPadding); mContainerView.setBackground(background); mRevealView.setBackground(background.getConstantState().newDrawable()); - mAppsRecyclerView.updateBackgroundPadding(padding); - mAdapter.updateBackgroundPadding(padding); + mAppsRecyclerView.updateBackgroundPadding(bgPadding); + mAdapter.updateBackgroundPadding(bgPadding); // Hack: We are going to let the recycler view take the full width, so reset the padding on // the container to zero after setting the background and apply the top-bottom padding to @@ -448,13 +453,14 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc // Pad the recycler view by the background padding plus the start margin (for the section // names) - int startInset = Math.max(mSectionNamesMargin, mAppsRecyclerView.getScrollbarWidth()); + int startInset = Math.max(mSectionNamesMargin, mAppsRecyclerView.getMaxScrollbarWidth()); + int topBottomPadding = mRecyclerViewTopBottomPadding; if (isRtl) { - mAppsRecyclerView.setPadding(padding.left + mAppsRecyclerView.getScrollbarWidth(), 0, - padding.right + startInset, 0); + mAppsRecyclerView.setPadding(padding.left + mAppsRecyclerView.getMaxScrollbarWidth(), + topBottomPadding, padding.right + startInset, topBottomPadding); } else { - mAppsRecyclerView.setPadding(padding.left + startInset, 0, - padding.right + mAppsRecyclerView.getScrollbarWidth(), 0); + mAppsRecyclerView.setPadding(padding.left + startInset, topBottomPadding, + padding.right + mAppsRecyclerView.getMaxScrollbarWidth(), topBottomPadding); } // Inset the search bar to fit its bounds above the container @@ -474,8 +480,8 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc // Update the prediction bar insets as well mPredictionBarView = (ViewGroup) findViewById(R.id.prediction_bar); FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) mPredictionBarView.getLayoutParams(); - lp.leftMargin = padding.left + mAppsRecyclerView.getScrollbarWidth(); - lp.rightMargin = padding.right + mAppsRecyclerView.getScrollbarWidth(); + lp.leftMargin = padding.left + mAppsRecyclerView.getMaxScrollbarWidth(); + lp.rightMargin = padding.right + mAppsRecyclerView.getMaxScrollbarWidth(); mPredictionBarView.requestLayout(); } diff --git a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java index 68407bdd5..19e2757f9 100644 --- a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java +++ b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java @@ -337,7 +337,7 @@ class AllAppsGridAdapter extends RecyclerView.Adapter items = mApps.getAdapterItems(); - getCurScrollState(scrollPosState, items); - if (scrollPosState.rowIndex != -1) { + getCurScrollState(mScrollPosState, items); + if (mScrollPosState.rowIndex != -1) { int predictionBarHeight = mApps.getPredictedApps().isEmpty() ? 0 : mPredictionBarHeight; - return getPaddingTop() + (scrollPosState.rowIndex * scrollPosState.rowHeight) + - predictionBarHeight - scrollPosState.rowTopOffset; + return getPaddingTop() + predictionBarHeight + + (mScrollPosState.rowIndex * mScrollPosState.rowHeight) - + mScrollPosState.rowTopOffset; } return 0; } @@ -132,143 +145,159 @@ public class AllAppsRecyclerView extends BaseRecyclerView } } - @Override - protected void onFastScrollingEnd() { - mLastFastscrollPosition = -1; - } - /** * Maps the touch (from 0..1) to the adapter position that should be visible. */ @Override public String scrollToPositionAtProgress(float touchFraction) { - // Ensure that we have any sections - List fastScrollSections = - mApps.getFastScrollerSections(); - if (fastScrollSections.isEmpty()) { + int rowCount = mApps.getNumAppRows(); + if (rowCount == 0) { return ""; } // Stop the scroller if it is scrolling - LinearLayoutManager layoutManager = (LinearLayoutManager) getLayoutManager(); stopScroll(); - // If there is a prediction bar, then capture the appropriate area for the prediction bar - float predictionBarFraction = 0f; - if (!mApps.getPredictedApps().isEmpty()) { - predictionBarFraction = (float) mNumPredictedAppsPerRow / mApps.getSize(); - if (touchFraction <= predictionBarFraction) { - // Scroll to the top of the view, where the prediction bar is - layoutManager.scrollToPositionWithOffset(0, 0); - return ""; + // Find the fastscroll section that maps to this touch fraction + List fastScrollSections = + mApps.getFastScrollerSections(); + AlphabeticalAppsList.FastScrollSectionInfo lastInfo = fastScrollSections.get(0); + if (mScrollBarMode == FAST_SCROLL_BAR_MODE_DISTRIBUTE_BY_ROW) { + for (int i = 1; i < fastScrollSections.size(); i++) { + AlphabeticalAppsList.FastScrollSectionInfo info = fastScrollSections.get(i); + if (info.touchFraction > touchFraction) { + break; + } + lastInfo = info; } + } else if (mScrollBarMode == FAST_SCROLL_BAR_MODE_DISTRIBUTE_BY_SECTIONS){ + lastInfo = fastScrollSections.get((int) (touchFraction * (fastScrollSections.size() - 1))); + } else { + throw new RuntimeException("Unexpected scroll bar mode"); } - // Since the app ranges are from 0..1, we need to map the touch fraction back to 0..1 from - // predictionBarFraction..1 - touchFraction = (touchFraction - predictionBarFraction) * - (1f / (1f - predictionBarFraction)); - AlphabeticalAppsList.FastScrollSectionInfo lastScrollSection = fastScrollSections.get(0); - for (int i = 1; i < fastScrollSections.size(); i++) { - AlphabeticalAppsList.FastScrollSectionInfo scrollSection = fastScrollSections.get(i); - if (lastScrollSection.appRangeFraction <= touchFraction && - touchFraction < scrollSection.appRangeFraction) { - break; - } - lastScrollSection = scrollSection; + // Map the touch position back to the scroll of the recycler view + getCurScrollState(mScrollPosState, mApps.getAdapterItems()); + int predictionBarHeight = mApps.getPredictedApps().isEmpty() ? 0 : mPredictionBarHeight; + int availableScrollHeight = getAvailableScrollHeight(rowCount, mScrollPosState.rowHeight, + predictionBarHeight); + LinearLayoutManager layoutManager = (LinearLayoutManager) getLayoutManager(); + if (mFastScrollMode == FAST_SCROLL_MODE_FREE_SCROLL) { + layoutManager.scrollToPositionWithOffset(0, (int) -(availableScrollHeight * touchFraction)); } - // Scroll to the view at the position, anchored at the top of the screen. We call the scroll - // method on the LayoutManager directly since it is not exposed by RecyclerView. - if (mLastFastscrollPosition != lastScrollSection.appItem.position) { - mLastFastscrollPosition = lastScrollSection.appItem.position; - layoutManager.scrollToPositionWithOffset(lastScrollSection.appItem.position, 0); - } + if (mPrevFastScrollFocusedPosition != lastInfo.fastScrollToItem.position) { + mPrevFastScrollFocusedPosition = lastInfo.fastScrollToItem.position; - return lastScrollSection.sectionName; - } + // Reset the last focused view + if (mLastFastScrollFocusedView != null) { + mLastFastScrollFocusedView.setFastScrollFocused(false, true); + mLastFastScrollFocusedView = null; + } - /** - * Returns the row index for a app index in the list. - */ - private int findRowForAppIndex(int index) { - List sections = mApps.getSections(); - int appIndex = 0; - int rowCount = 0; - for (AlphabeticalAppsList.SectionInfo info : sections) { - int numRowsInSection = (int) Math.ceil((float) info.numApps / mNumAppsPerRow); - if (appIndex + info.numApps > index) { - return rowCount + ((index - appIndex) / mNumAppsPerRow); + if (mFastScrollMode == FAST_SCROLL_MODE_JUMP_TO_FIRST_ICON) { + smoothSnapToPosition(mPrevFastScrollFocusedPosition, mScrollPosState); + } else if (mFastScrollMode == FAST_SCROLL_MODE_FREE_SCROLL) { + final ViewHolder vh = findViewHolderForPosition(mPrevFastScrollFocusedPosition); + if (vh != null && + vh.itemView instanceof BaseRecyclerViewFastScrollBar.FastScrollFocusableView) { + mLastFastScrollFocusedView = + (BaseRecyclerViewFastScrollBar.FastScrollFocusableView) vh.itemView; + mLastFastScrollFocusedView.setFastScrollFocused(true, true); + } + } else { + throw new RuntimeException("Unexpected fast scroll mode"); } - appIndex += info.numApps; - rowCount += numRowsInSection; } - return appIndex; + return lastInfo.sectionName; } - /** - * Returns the total number of rows in the list. - */ - private int getNumRows() { - List sections = mApps.getSections(); - int rowCount = 0; - for (AlphabeticalAppsList.SectionInfo info : sections) { - int numRowsInSection = (int) Math.ceil((float) info.numApps / mNumAppsPerRow); - rowCount += numRowsInSection; + @Override + public void onFastScrollCompleted() { + super.onFastScrollCompleted(); + // Reset and clean up the last focused view + if (mLastFastScrollFocusedView != null) { + mLastFastScrollFocusedView.setFastScrollFocused(false, true); + mLastFastScrollFocusedView = null; } - return rowCount; + mPrevFastScrollFocusedPosition = -1; } - /** * Updates the bounds for the scrollbar. */ @Override - public void updateVerticalScrollbarBounds() { + public void onUpdateScrollbar() { List items = mApps.getAdapterItems(); // Skip early if there are no items or we haven't been measured if (items.isEmpty() || mNumAppsPerRow == 0) { - verticalScrollbarBounds.setEmpty(); + mScrollbar.setScrollbarThumbOffset(-1, -1); return; } // Find the index and height of the first visible row (all rows have the same height) - int x, y; + int rowCount = mApps.getNumAppRows(); + getCurScrollState(mScrollPosState, items); + if (mScrollPosState.rowIndex < 0) { + mScrollbar.setScrollbarThumbOffset(-1, -1); + return; + } + int predictionBarHeight = mApps.getPredictedApps().isEmpty() ? 0 : mPredictionBarHeight; - int rowCount = getNumRows(); - getCurScrollState(scrollPosState, items); - if (scrollPosState.rowIndex != -1) { - int height = getHeight() - getPaddingTop() - getPaddingBottom(); - int totalScrollHeight = rowCount * scrollPosState.rowHeight + predictionBarHeight; - if (totalScrollHeight > height) { - int scrollbarHeight = (int) (height / ((float) totalScrollHeight / height)); - - // Calculate the position and size of the scroll bar - if (Utilities.isRtl(getResources())) { - x = mBackgroundPadding.left; - } else { - x = getWidth() - mBackgroundPadding.right - getScrollbarWidth(); - } + synchronizeScrollBarThumbOffsetToViewScroll(mScrollPosState, rowCount, predictionBarHeight); + } - // To calculate the offset, we compute the percentage of the total scrollable height - // that the user has already scrolled and then map that to the scroll bar bounds - int availableY = totalScrollHeight - height; - int availableScrollY = height - scrollbarHeight; - y = (scrollPosState.rowIndex * scrollPosState.rowHeight) + predictionBarHeight - - scrollPosState.rowTopOffset; - y = getPaddingTop() + - (int) (((float) (getPaddingTop() + y) / availableY) * availableScrollY); - - verticalScrollbarBounds.set(x, y, x + getScrollbarWidth(), y + scrollbarHeight); - return; + /** + * This runnable runs a single frame of the smooth scroll animation and posts the next frame + * if necessary. + */ + private Runnable mSmoothSnapNextFrameRunnable = new Runnable() { + @Override + public void run() { + if (mFastScrollFrameIndex < mFastScrollFrames.length) { + scrollBy(0, mFastScrollFrames[mFastScrollFrameIndex]); + mFastScrollFrameIndex++; + postOnAnimation(mSmoothSnapNextFrameRunnable); + } else { + // Animation completed, set the fast scroll state on the target view + final ViewHolder vh = findViewHolderForPosition(mPrevFastScrollFocusedPosition); + if (vh != null && + vh.itemView instanceof BaseRecyclerViewFastScrollBar.FastScrollFocusableView && + mLastFastScrollFocusedView != vh.itemView) { + mLastFastScrollFocusedView = + (BaseRecyclerViewFastScrollBar.FastScrollFocusableView) vh.itemView; + mLastFastScrollFocusedView.setFastScrollFocused(true, true); + } } } - verticalScrollbarBounds.setEmpty(); + }; + + /** + * Smoothly snaps to a given position. We do this manually by calculating the keyframes + * ourselves and animating the scroll on the recycler view. + */ + private void smoothSnapToPosition(final int position, ScrollPositionState scrollPosState) { + removeCallbacks(mSmoothSnapNextFrameRunnable); + + // Calculate the full animation from the current scroll position to the final scroll + // position, and then run the animation for the duration. + int predictionBarHeight = mApps.getPredictedApps().isEmpty() ? 0 : mPredictionBarHeight; + int curScrollY = getPaddingTop() + predictionBarHeight + + (scrollPosState.rowIndex * scrollPosState.rowHeight) - scrollPosState.rowTopOffset; + int newScrollY = getScrollAtPosition(position, scrollPosState.rowHeight); + int numFrames = mFastScrollFrames.length; + for (int i = 0; i < numFrames; i++) { + // TODO(winsonc): We can interpolate this as well. + mFastScrollFrames[i] = (newScrollY - curScrollY) / numFrames; + } + mFastScrollFrameIndex = 0; + postOnAnimation(mSmoothSnapNextFrameRunnable); } /** - * Returns the current scroll state. + * Returns the current scroll state of the apps rows, not including the prediction + * bar. */ private void getCurScrollState(ScrollPositionState stateOut, List items) { @@ -288,7 +317,7 @@ public class AllAppsRecyclerView extends BaseRecyclerView if (position != NO_POSITION) { AlphabeticalAppsList.AdapterItem item = items.get(position); if (item.viewType == AllAppsGridAdapter.ICON_VIEW_TYPE) { - stateOut.rowIndex = findRowForAppIndex(item.appIndex); + stateOut.rowIndex = item.rowIndex; stateOut.rowTopOffset = getLayoutManager().getDecoratedTop(child); stateOut.rowHeight = child.getHeight(); break; @@ -296,4 +325,17 @@ public class AllAppsRecyclerView extends BaseRecyclerView } } } + + /** + * Returns the scrollY for the given position in the adapter. + */ + private int getScrollAtPosition(int position, int rowHeight) { + AlphabeticalAppsList.AdapterItem item = mApps.getAdapterItems().get(position); + if (item.viewType == AllAppsGridAdapter.ICON_VIEW_TYPE) { + int predictionBarHeight = mApps.getPredictedApps().isEmpty() ? 0 : mPredictionBarHeight; + return getPaddingTop() + predictionBarHeight + item.rowIndex * rowHeight; + } else { + return 0; + } + } } diff --git a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java index aa73c74cf..ea99872ed 100644 --- a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java +++ b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java @@ -62,15 +62,13 @@ public class AlphabeticalAppsList { public static class FastScrollSectionInfo { // The section name public String sectionName; - // To map the touch (from 0..1) to the index in the app list to jump to in the fast - // scroller, we use the fraction in range (0..1) of the app index / total app count. - public float appRangeFraction; // The AdapterItem to scroll to for this section - public AdapterItem appItem; + public AdapterItem fastScrollToItem; + // The touch fraction that should map to this fast scroll section info + public float touchFraction; - public FastScrollSectionInfo(String sectionName, float appRangeFraction) { + public FastScrollSectionInfo(String sectionName) { this.sectionName = sectionName; - this.appRangeFraction = appRangeFraction; } } @@ -83,6 +81,8 @@ public class AlphabeticalAppsList { public int position; // The type of this item public int viewType; + // The row that this item shows up on + public int rowIndex; /** Section & App properties */ // The section for this item @@ -94,6 +94,8 @@ public class AlphabeticalAppsList { public String sectionName = null; // The index of this app in the section public int sectionAppIndex = -1; + // The index of this app in the row + public int rowAppIndex; // The associated AppInfo for the app public AppInfo appInfo = null; // The index of this app not including sections @@ -172,6 +174,7 @@ public class AlphabeticalAppsList { private AdapterChangedCallback mAdapterChangedCallback; private int mNumAppsPerRow; private int mNumPredictedAppsPerRow; + private int mNumAppRowsInAdapter; public AlphabeticalAppsList(Context context) { mLauncher = (Launcher) context; @@ -240,6 +243,13 @@ public class AlphabeticalAppsList { return mFilteredApps.size(); } + /** + * Returns the number of rows of applications (not including predictions) + */ + public int getNumAppRows() { + return mNumAppRowsInAdapter; + } + /** * Returns whether there are is a filter set. */ @@ -419,23 +429,23 @@ public class AlphabeticalAppsList { // Create a new spacer for the prediction bar AdapterItem sectionItem = AdapterItem.asPredictionBarSpacer(position++); mAdapterItems.add(sectionItem); + // Add a fastscroller section for the prediction bar + lastFastScrollerSectionInfo = new FastScrollSectionInfo(""); + lastFastScrollerSectionInfo.fastScrollToItem = sectionItem; + mFastScrollerSections.add(lastFastScrollerSectionInfo); } } // Recreate the filtered and sectioned apps (for convenience for the grid layout) from the // ordered set of sections - List apps = getFiltersAppInfos(); - int numApps = apps.size(); - for (int i = 0; i < numApps; i++) { - AppInfo info = apps.get(i); + for (AppInfo info : getFiltersAppInfos()) { String sectionName = getAndUpdateCachedSectionName(info.title); // Create a new section if the section names do not match if (lastSectionInfo == null || !sectionName.equals(lastSectionName)) { lastSectionName = sectionName; lastSectionInfo = new SectionInfo(); - lastFastScrollerSectionInfo = new FastScrollSectionInfo(sectionName, - (float) appIndex / numApps); + lastFastScrollerSectionInfo = new FastScrollSectionInfo(sectionName); mSections.add(lastSectionInfo); mFastScrollerSections.add(lastFastScrollerSectionInfo); @@ -451,7 +461,7 @@ public class AlphabeticalAppsList { lastSectionInfo.numApps++, info, appIndex++); if (lastSectionInfo.firstAppItem == null) { lastSectionInfo.firstAppItem = appItem; - lastFastScrollerSectionInfo.appItem = appItem; + lastFastScrollerSectionInfo.fastScrollToItem = appItem; } mAdapterItems.add(appItem); mFilteredApps.add(info); @@ -460,6 +470,45 @@ public class AlphabeticalAppsList { // Merge multiple sections together as requested by the merge strategy for this device mergeSections(); + if (mNumAppsPerRow != 0) { + // Update the number of rows in the adapter after we do all the merging (otherwise, we + // would have to shift the values again) + int numAppsInSection = 0; + int numAppsInRow = 0; + int rowIndex = -1; + for (AdapterItem item : mAdapterItems) { + item.rowIndex = 0; + if (item.viewType == AllAppsGridAdapter.SECTION_BREAK_VIEW_TYPE) { + numAppsInSection = 0; + } else if (item.viewType == AllAppsGridAdapter.ICON_VIEW_TYPE) { + if (numAppsInSection % mNumAppsPerRow == 0) { + numAppsInRow = 0; + rowIndex++; + } + item.rowIndex = rowIndex; + item.rowAppIndex = numAppsInRow; + numAppsInSection++; + numAppsInRow++; + } + } + mNumAppRowsInAdapter = rowIndex + 1; + + // Pre-calculate all the fast scroller fractions based on the number of rows, if we have + // predicted apps, then we should account for that as a row in the touchFraction + float rowFraction = 1f / (mNumAppRowsInAdapter + (mPredictedApps.isEmpty() ? 0 : 1)); + float initialOffset = mPredictedApps.isEmpty() ? 0 : rowFraction; + for (FastScrollSectionInfo info : mFastScrollerSections) { + AdapterItem item = info.fastScrollToItem; + if (item.viewType != AllAppsGridAdapter.ICON_VIEW_TYPE) { + info.touchFraction = 0f; + continue; + } + + float subRowFraction = item.rowAppIndex * (rowFraction / mNumAppsPerRow); + info.touchFraction = initialOffset + item.rowIndex * rowFraction + subRowFraction; + } + } + // Refresh the recycler view if (mAdapter != null) { mAdapter.notifyDataSetChanged(); @@ -511,6 +560,7 @@ public class AlphabeticalAppsList { // Remove the next section break mAdapterItems.remove(nextSection.sectionBreakItem); int pos = mAdapterItems.indexOf(section.firstAppItem); + // Point the section for these new apps to the merged section int nextPos = pos + section.numApps; for (int j = nextPos; j < (nextPos + nextSection.numApps); j++) { -- cgit v1.2.3