Merge "Using inbuild smooth scroller instead of custom fastscrolling logic" into ub-launcher3-rvc-dev am: bb74388420
Change-Id: Ia3b33a501d95077de26b3068fe22d5e669c652e3
This commit is contained in:
commit
71c1f874a9
|
@ -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<RecyclerView.ViewHolder> 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<AlphabeticalAppsList.FastScrollSectionInfo> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -71,11 +71,6 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.
|
|||
public static final int VIEW_TYPE_MASK_DIVIDER = VIEW_TYPE_ALL_APPS_DIVIDER;
|
||||
public static final int VIEW_TYPE_MASK_ICON = VIEW_TYPE_ICON;
|
||||
|
||||
|
||||
public interface BindViewCallback {
|
||||
void onBindView(ViewHolder holder);
|
||||
}
|
||||
|
||||
/**
|
||||
* ViewHolder for each icon.
|
||||
*/
|
||||
|
@ -186,7 +181,6 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.
|
|||
|
||||
private int mAppsPerRow;
|
||||
|
||||
private BindViewCallback mBindViewCallback;
|
||||
private OnFocusChangeListener mIconFocusListener;
|
||||
|
||||
// The text to show when there are no search results and no market search handler.
|
||||
|
@ -247,13 +241,6 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.
|
|||
mMarketSearchIntent = PackageManagerHelper.getMarketSearchIntent(mLauncher, query);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the callback for when views are bound.
|
||||
*/
|
||||
public void setBindViewCallback(BindViewCallback cb) {
|
||||
mBindViewCallback = cb;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the grid layout manager.
|
||||
*/
|
||||
|
@ -319,9 +306,6 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.
|
|||
// nothing to do
|
||||
break;
|
||||
}
|
||||
if (mBindViewCallback != null) {
|
||||
mBindViewCallback.onBindView(holder);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -53,12 +53,12 @@ import java.util.List;
|
|||
public class AllAppsRecyclerView extends BaseRecyclerView implements LogContainerProvider {
|
||||
|
||||
private AlphabeticalAppsList mApps;
|
||||
private AllAppsFastScrollHelper mFastScrollHelper;
|
||||
private final int mNumAppsPerRow;
|
||||
|
||||
// The specific view heights that we use to calculate scroll
|
||||
private SparseIntArray mViewHeights = new SparseIntArray();
|
||||
private SparseIntArray mCachedScrollPositions = new SparseIntArray();
|
||||
private final SparseIntArray mViewHeights = new SparseIntArray();
|
||||
private final SparseIntArray mCachedScrollPositions = new SparseIntArray();
|
||||
private final AllAppsFastScrollHelper mFastScrollHelper;
|
||||
|
||||
// The empty-search result background
|
||||
private AllAppsBackgroundDrawable mEmptySearchBackground;
|
||||
|
@ -85,6 +85,7 @@ public class AllAppsRecyclerView extends BaseRecyclerView implements LogContaine
|
|||
mEmptySearchBackgroundTopOffset = res.getDimensionPixelSize(
|
||||
R.dimen.all_apps_empty_search_bg_top_offset);
|
||||
mNumAppsPerRow = LauncherAppState.getIDP(context).numColumns;
|
||||
mFastScrollHelper = new AllAppsFastScrollHelper(this);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -92,7 +93,6 @@ public class AllAppsRecyclerView extends BaseRecyclerView implements LogContaine
|
|||
*/
|
||||
public void setApps(AlphabeticalAppsList apps) {
|
||||
mApps = apps;
|
||||
mFastScrollHelper = new AllAppsFastScrollHelper(this, apps);
|
||||
}
|
||||
|
||||
public AlphabeticalAppsList getApps() {
|
||||
|
@ -221,9 +221,6 @@ public class AllAppsRecyclerView extends BaseRecyclerView implements LogContaine
|
|||
return "";
|
||||
}
|
||||
|
||||
// Stop the scroller if it is scrolling
|
||||
stopScroll();
|
||||
|
||||
// Find the fastscroll section that maps to this touch fraction
|
||||
List<AlphabeticalAppsList.FastScrollSectionInfo> 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
|
||||
|
|
|
@ -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<Rect> 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));
|
||||
|
|
Loading…
Reference in New Issue