From 355e845851e55feffb82d325bab40f0e2ba24897 Mon Sep 17 00:00:00 2001 From: Sunny Goyal Date: Fri, 17 Apr 2020 16:31:40 -0700 Subject: [PATCH] Using inbuild smooth scroller instead of custom fastscrolling logic Change-Id: I9208720c3bf164bd664e5bacd75b672573fe7601 --- .../allapps/AllAppsFastScrollHelper.java | 236 +++++------------- .../launcher3/allapps/AllAppsGridAdapter.java | 16 -- .../allapps/AllAppsRecyclerView.java | 17 +- .../views/RecyclerViewFastScroller.java | 9 +- 4 files changed, 69 insertions(+), 209 deletions(-) diff --git a/src/com/android/launcher3/allapps/AllAppsFastScrollHelper.java b/src/com/android/launcher3/allapps/AllAppsFastScrollHelper.java index 3ee1293e28..f97eb28dda 100644 --- a/src/com/android/launcher3/allapps/AllAppsFastScrollHelper.java +++ b/src/com/android/launcher3/allapps/AllAppsFastScrollHelper.java @@ -15,206 +15,90 @@ */ package com.android.launcher3.allapps; -import com.android.launcher3.util.Thunk; +import androidx.recyclerview.widget.LinearSmoothScroller; +import androidx.recyclerview.widget.RecyclerView.ViewHolder; -import java.util.HashSet; -import java.util.List; +import com.android.launcher3.allapps.AlphabeticalAppsList.FastScrollSectionInfo; -import androidx.recyclerview.widget.RecyclerView; +public class AllAppsFastScrollHelper { -public class AllAppsFastScrollHelper implements AllAppsGridAdapter.BindViewCallback { + private static final int NO_POSITION = -1; - private static final int INITIAL_TOUCH_SETTLING_DURATION = 100; - private static final int REPEAT_TOUCH_SETTLING_DURATION = 200; + private int mTargetFastScrollPosition = NO_POSITION; private AllAppsRecyclerView mRv; - private AlphabeticalAppsList mApps; + private ViewHolder mLastSelectedViewHolder; - // Keeps track of the current and targeted fast scroll section (the section to scroll to after - // the initial delay) - int mTargetFastScrollPosition = -1; - @Thunk String mCurrentFastScrollSection; - @Thunk String mTargetFastScrollSection; - - // The settled states affect the delay before the fast scroll animation is applied - private boolean mHasFastScrollTouchSettled; - private boolean mHasFastScrollTouchSettledAtLeastOnce; - - // Set of all views animated during fast scroll. We keep track of these ourselves since there - // is no way to reset a view once it gets scrapped or recycled without other hacks - private HashSet mTrackedFastScrollViews = new HashSet<>(); - - // Smooth fast-scroll animation frames - @Thunk int mFastScrollFrameIndex; - @Thunk final int[] mFastScrollFrames = new int[10]; - - /** - * This runnable runs a single frame of the smooth scroll animation and posts the next frame - * if necessary. - */ - @Thunk Runnable mSmoothSnapNextFrameRunnable = new Runnable() { - @Override - public void run() { - if (mFastScrollFrameIndex < mFastScrollFrames.length) { - mRv.scrollBy(0, mFastScrollFrames[mFastScrollFrameIndex]); - mFastScrollFrameIndex++; - mRv.postOnAnimation(mSmoothSnapNextFrameRunnable); - } - } - }; - - /** - * This runnable updates the current fast scroll section to the target fastscroll section. - */ - Runnable mFastScrollToTargetSectionRunnable = new Runnable() { - @Override - public void run() { - // Update to the target section - mCurrentFastScrollSection = mTargetFastScrollSection; - mHasFastScrollTouchSettled = true; - mHasFastScrollTouchSettledAtLeastOnce = true; - updateTrackedViewsFastScrollFocusState(); - } - }; - - public AllAppsFastScrollHelper(AllAppsRecyclerView rv, AlphabeticalAppsList apps) { + public AllAppsFastScrollHelper(AllAppsRecyclerView rv) { mRv = rv; - mApps = apps; - } - - public void onSetAdapter(AllAppsGridAdapter adapter) { - adapter.setBindViewCallback(this); } /** * Smooth scrolls the recycler view to the given section. - * - * @return whether the fastscroller can scroll to the new section. */ - public boolean smoothScrollToSection(int scrollY, int availableScrollHeight, - AlphabeticalAppsList.FastScrollSectionInfo info) { - if (mTargetFastScrollPosition != info.fastScrollToItem.position) { - mTargetFastScrollPosition = info.fastScrollToItem.position; - smoothSnapToPosition(scrollY, availableScrollHeight, info); - return true; + public void smoothScrollToSection(FastScrollSectionInfo info) { + if (mTargetFastScrollPosition == info.fastScrollToItem.position) { + return; } - return false; - } - - /** - * 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(int scrollY, int availableScrollHeight, - AlphabeticalAppsList.FastScrollSectionInfo info) { - mRv.removeCallbacks(mSmoothSnapNextFrameRunnable); - mRv.removeCallbacks(mFastScrollToTargetSectionRunnable); - - trackAllChildViews(); - if (mHasFastScrollTouchSettled) { - // In this case, the user has already settled once (and the fast scroll state has - // animated) and they are just fine-tuning their section from the last section, so - // we should make it feel fast and update immediately. - mCurrentFastScrollSection = info.sectionName; - mTargetFastScrollSection = null; - updateTrackedViewsFastScrollFocusState(); - } else { - // Otherwise, the user has scrubbed really far, and we don't want to distract the user - // with the flashing fast scroll state change animation in addition to the fast scroll - // section popup, so reset the views to normal, and wait for the touch to settle again - // before animating the fast scroll state. - mCurrentFastScrollSection = null; - mTargetFastScrollSection = info.sectionName; - mHasFastScrollTouchSettled = false; - updateTrackedViewsFastScrollFocusState(); - - // Delay scrolling to a new section until after some duration. If the user has been - // scrubbing a while and makes multiple big jumps, then reduce the time needed for the - // fast scroll to settle so it doesn't feel so long. - mRv.postDelayed(mFastScrollToTargetSectionRunnable, - mHasFastScrollTouchSettledAtLeastOnce ? - REPEAT_TOUCH_SETTLING_DURATION : - INITIAL_TOUCH_SETTLING_DURATION); - } - - // Calculate the full animation from the current scroll position to the final scroll - // position, and then run the animation for the duration. If we are scrolling to the - // first fast scroll section, then just scroll to the top of the list itself. - List fastScrollSections = - mApps.getFastScrollerSections(); - int newPosition = info.fastScrollToItem.position; - int newScrollY = fastScrollSections.size() > 0 && fastScrollSections.get(0) == info - ? 0 - : Math.min(availableScrollHeight, mRv.getCurrentScrollY(newPosition, 0)); - int numFrames = mFastScrollFrames.length; - int deltaY = newScrollY - scrollY; - float ySign = Math.signum(deltaY); - int step = (int) (ySign * Math.ceil((float) Math.abs(deltaY) / numFrames)); - for (int i = 0; i < numFrames; i++) { - // TODO(winsonc): We can interpolate this as well. - mFastScrollFrames[i] = (int) (ySign * Math.min(Math.abs(step), Math.abs(deltaY))); - deltaY -= step; - } - mFastScrollFrameIndex = 0; - mRv.postOnAnimation(mSmoothSnapNextFrameRunnable); + mTargetFastScrollPosition = info.fastScrollToItem.position; + mRv.getLayoutManager().startSmoothScroll(new MyScroller(mTargetFastScrollPosition)); } public void onFastScrollCompleted() { - // TODO(winsonc): Handle the case when the user scrolls and releases before the animation - // runs - - // Stop animating the fast scroll position and state - mRv.removeCallbacks(mSmoothSnapNextFrameRunnable); - mRv.removeCallbacks(mFastScrollToTargetSectionRunnable); - - // Reset the tracking variables - mHasFastScrollTouchSettled = false; - mHasFastScrollTouchSettledAtLeastOnce = false; - mCurrentFastScrollSection = null; - mTargetFastScrollSection = null; - mTargetFastScrollPosition = -1; - - updateTrackedViewsFastScrollFocusState(); - mTrackedFastScrollViews.clear(); + mTargetFastScrollPosition = NO_POSITION; + setLastHolderSelected(false); + mLastSelectedViewHolder = null; } - @Override - public void onBindView(AllAppsGridAdapter.ViewHolder holder) { - // Update newly bound views to the current fast scroll state if we are fast scrolling - if (mCurrentFastScrollSection != null || mTargetFastScrollSection != null) { - mTrackedFastScrollViews.add(holder); + + private void setLastHolderSelected(boolean isSelected) { + if (mLastSelectedViewHolder != null) { + mLastSelectedViewHolder.itemView.setActivated(isSelected); + mLastSelectedViewHolder.setIsRecyclable(!isSelected); } } - /** - * Starts tracking all the recycler view's children which are FastScrollFocusableViews. - */ - private void trackAllChildViews() { - int childCount = mRv.getChildCount(); - for (int i = 0; i < childCount; i++) { - RecyclerView.ViewHolder viewHolder = mRv.getChildViewHolder(mRv.getChildAt(i)); - if (viewHolder != null) { - mTrackedFastScrollViews.add(viewHolder); - } - } - } + private class MyScroller extends LinearSmoothScroller { - /** - * Updates the fast scroll focus on all the children. - */ - private void updateTrackedViewsFastScrollFocusState() { - for (RecyclerView.ViewHolder viewHolder : mTrackedFastScrollViews) { - int pos = viewHolder.getAdapterPosition(); - boolean isActive = false; - if (mCurrentFastScrollSection != null - && pos > RecyclerView.NO_POSITION - && pos < mApps.getAdapterItems().size()) { - AlphabeticalAppsList.AdapterItem item = mApps.getAdapterItems().get(pos); - isActive = item != null && - mCurrentFastScrollSection.equals(item.sectionName) && - item.position == mTargetFastScrollPosition; + private final int mTargetPosition; + + public MyScroller(int targetPosition) { + super(mRv.getContext()); + + mTargetPosition = targetPosition; + setTargetPosition(targetPosition); + } + + @Override + protected int getVerticalSnapPreference() { + return SNAP_TO_START; + } + + @Override + protected void onStop() { + super.onStop(); + if (mTargetPosition != mTargetFastScrollPosition) { + // Target changed, before the last scroll can finish + return; + } + + ViewHolder currentHolder = mRv.findViewHolderForAdapterPosition(mTargetPosition); + if (currentHolder == mLastSelectedViewHolder) { + return; + } + + setLastHolderSelected(false); + mLastSelectedViewHolder = currentHolder; + setLastHolderSelected(true); + } + + @Override + protected void onStart() { + super.onStart(); + if (mTargetPosition != mTargetFastScrollPosition) { + setLastHolderSelected(false); + mLastSelectedViewHolder = null; } - viewHolder.itemView.setActivated(isActive); } } } diff --git a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java index 4aebec0798..3afa756add 100644 --- a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java +++ b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java @@ -71,11 +71,6 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter fastScrollSections = mApps.getFastScrollerSections(); @@ -236,10 +233,7 @@ public class AllAppsRecyclerView extends BaseRecyclerView implements LogContaine lastInfo = info; } - // Update the fast scroll - int scrollY = getCurrentScrollY(); - int availableScrollHeight = getAvailableScrollHeight(); - mFastScrollHelper.smoothScrollToSection(scrollY, availableScrollHeight, lastInfo); + mFastScrollHelper.smoothScrollToSection(lastInfo); return lastInfo.sectionName; } @@ -257,7 +251,6 @@ public class AllAppsRecyclerView extends BaseRecyclerView implements LogContaine mCachedScrollPositions.clear(); } }); - mFastScrollHelper.onSetAdapter((AllAppsGridAdapter) adapter); } @Override diff --git a/src/com/android/launcher3/views/RecyclerViewFastScroller.java b/src/com/android/launcher3/views/RecyclerViewFastScroller.java index 56538016df..6a8333233c 100644 --- a/src/com/android/launcher3/views/RecyclerViewFastScroller.java +++ b/src/com/android/launcher3/views/RecyclerViewFastScroller.java @@ -67,7 +67,6 @@ public class RecyclerViewFastScroller extends View { private final static int MAX_TRACK_ALPHA = 30; private final static int SCROLL_BAR_VIS_DURATION = 150; - private static final float FAST_SCROLL_OVERLAY_Y_OFFSET_FACTOR = 0.75f; private static final List SYSTEM_GESTURE_EXCLUSION_RECT = Collections.singletonList(new Rect()); @@ -184,7 +183,7 @@ public class RecyclerViewFastScroller extends View { if (mThumbOffsetY == y) { return; } - updatePopupY((int) y); + updatePopupY(y); mThumbOffsetY = y; invalidate(); } @@ -237,7 +236,7 @@ public class RecyclerViewFastScroller extends View { } else if (mRv.supportsFastScrolling() && isNearScrollBar(mDownX)) { calcTouchOffsetAndPrepToFastScroll(mDownY, mLastY); - updateFastScrollSectionNameAndThumbOffset(mLastY, y); + updateFastScrollSectionNameAndThumbOffset(y); } break; case MotionEvent.ACTION_MOVE: @@ -252,7 +251,7 @@ public class RecyclerViewFastScroller extends View { calcTouchOffsetAndPrepToFastScroll(mDownY, mLastY); } if (mIsDragging) { - updateFastScrollSectionNameAndThumbOffset(mLastY, y); + updateFastScrollSectionNameAndThumbOffset(y); } break; case MotionEvent.ACTION_UP: @@ -281,7 +280,7 @@ public class RecyclerViewFastScroller extends View { showActiveScrollbar(true); } - private void updateFastScrollSectionNameAndThumbOffset(int lastY, int y) { + private void updateFastScrollSectionNameAndThumbOffset(int y) { // Update the fastscroller section name at this touch position int bottom = mRv.getScrollbarTrackHeight() - mThumbHeight; float boundedY = (float) Math.max(0, Math.min(bottom, y - mTouchOffsetY));