summaryrefslogtreecommitdiffstats
path: root/src/com/android/launcher3/allapps
diff options
context:
space:
mode:
authorWinson <winsonc@google.com>2015-08-21 11:16:27 -0700
committerWinson <winsonc@google.com>2015-09-28 17:32:19 -0700
commitc088049113c261331b5685e64050d14a31cd72df (patch)
tree404a01d25361b6df9b754ec29ff3e4d053fe6bb8 /src/com/android/launcher3/allapps
parent5189454083640c81bf3a171422b3ef114f150a77 (diff)
downloadandroid_packages_apps_Trebuchet-c088049113c261331b5685e64050d14a31cd72df.tar.gz
android_packages_apps_Trebuchet-c088049113c261331b5685e64050d14a31cd72df.tar.bz2
android_packages_apps_Trebuchet-c088049113c261331b5685e64050d14a31cd72df.zip
Highlighting sectioned apps on fast-scroll.
- This CL fixes an old assumption we had about the height of rows in AllApps, and ensures that we account for the difference in height between the predictive icons and the normal icons. - In addition, we refactor FastBitmapDrawable to have multiple states, which it manages in drawing itself, including the press state and fast scroll focus states. And we also refactor some of the fast scroll logic in the all apps recycler view out to its own class. Change-Id: I1988159b2767df733bbbfc7dc601859cde6c9943
Diffstat (limited to 'src/com/android/launcher3/allapps')
-rw-r--r--src/com/android/launcher3/allapps/AllAppsContainerView.java14
-rw-r--r--src/com/android/launcher3/allapps/AllAppsFastScrollHelper.java228
-rw-r--r--src/com/android/launcher3/allapps/AllAppsGridAdapter.java21
-rw-r--r--src/com/android/launcher3/allapps/AllAppsRecyclerView.java190
4 files changed, 311 insertions, 142 deletions
diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java
index 564527e28..90f132231 100644
--- a/src/com/android/launcher3/allapps/AllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java
@@ -28,6 +28,7 @@ import android.text.SpannableStringBuilder;
import android.text.method.TextKeyListener;
import android.util.AttributeSet;
import android.view.KeyEvent;
+import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
@@ -35,6 +36,7 @@ import android.view.ViewGroup;
import android.widget.LinearLayout;
import com.android.launcher3.AppInfo;
import com.android.launcher3.BaseContainerView;
+import com.android.launcher3.BubbleTextView;
import com.android.launcher3.CellLayout;
import com.android.launcher3.DeleteDropTarget;
import com.android.launcher3.DeviceProfile;
@@ -332,6 +334,18 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc
mAppsRecyclerView.addItemDecoration(mItemDecoration);
}
+ // Precalculate the prediction icon and normal icon sizes
+ LayoutInflater layoutInflater = LayoutInflater.from(getContext());
+ BubbleTextView icon = (BubbleTextView) layoutInflater.inflate(R.layout.all_apps_icon, this, false);
+ icon.applyFromApplicationInfo(mLauncher.createDummyAppInfo());
+ icon.measure(MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE, MeasureSpec.AT_MOST),
+ MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE, MeasureSpec.AT_MOST));
+ BubbleTextView predIcon = (BubbleTextView) layoutInflater.inflate(R.layout.all_apps_prediction_bar_icon, this, false);
+ predIcon.applyFromApplicationInfo(mLauncher.createDummyAppInfo());
+ predIcon.measure(MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE, MeasureSpec.AT_MOST),
+ MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE, MeasureSpec.AT_MOST));
+ mAppsRecyclerView.setPremeasuredIconHeights(predIcon.getMeasuredHeight(), icon.getMeasuredHeight());
+
updateBackgroundAndPaddings();
}
diff --git a/src/com/android/launcher3/allapps/AllAppsFastScrollHelper.java b/src/com/android/launcher3/allapps/AllAppsFastScrollHelper.java
new file mode 100644
index 000000000..10453420a
--- /dev/null
+++ b/src/com/android/launcher3/allapps/AllAppsFastScrollHelper.java
@@ -0,0 +1,228 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.allapps;
+
+import android.support.v7.widget.RecyclerView;
+import android.view.View;
+
+import com.android.launcher3.BaseRecyclerView;
+import com.android.launcher3.BaseRecyclerViewFastScrollBar;
+import com.android.launcher3.FastBitmapDrawable;
+import com.android.launcher3.util.Thunk;
+
+import java.util.HashSet;
+
+public class AllAppsFastScrollHelper implements AllAppsGridAdapter.BindViewCallback {
+
+ private static final int INITIAL_TOUCH_SETTLING_DURATION = 300;
+ private static final int REPEAT_TOUCH_SETTLING_DURATION = 200;
+ private static final float FAST_SCROLL_TOUCH_VELOCITY_BARRIER = 1900f;
+
+ private AllAppsRecyclerView mRv;
+ private AlphabeticalAppsList mApps;
+
+ // Keeps track of the current and targetted 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<BaseRecyclerViewFastScrollBar.FastScrollFocusableView> 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) {
+ 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;
+ }
+ 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.
+ int newScrollY = Math.min(availableScrollHeight,
+ mRv.getPaddingTop() + mRv.getTop(info.fastScrollToItem.rowIndex));
+ int numFrames = mFastScrollFrames.length;
+ for (int i = 0; i < numFrames; i++) {
+ // TODO(winsonc): We can interpolate this as well.
+ mFastScrollFrames[i] = (newScrollY - scrollY) / numFrames;
+ }
+ mFastScrollFrameIndex = 0;
+ mRv.postOnAnimation(mSmoothSnapNextFrameRunnable);
+ }
+
+ 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();
+ }
+
+ @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) {
+ if (holder.mContent instanceof BaseRecyclerViewFastScrollBar.FastScrollFocusableView) {
+ BaseRecyclerViewFastScrollBar.FastScrollFocusableView v =
+ (BaseRecyclerViewFastScrollBar.FastScrollFocusableView) holder.mContent;
+ updateViewFastScrollFocusState(v, holder.getPosition(), false /* animated */);
+ mTrackedFastScrollViews.add(v);
+ }
+ }
+ }
+
+ /**
+ * 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++) {
+ View v = mRv.getChildAt(i);
+ if (v instanceof BaseRecyclerViewFastScrollBar.FastScrollFocusableView) {
+ mTrackedFastScrollViews.add((BaseRecyclerViewFastScrollBar.FastScrollFocusableView) v);
+ }
+ }
+ }
+
+ /**
+ * Updates the fast scroll focus on all the children.
+ */
+ private void updateTrackedViewsFastScrollFocusState() {
+ for (BaseRecyclerViewFastScrollBar.FastScrollFocusableView v : mTrackedFastScrollViews) {
+ RecyclerView.ViewHolder viewHolder = mRv.getChildViewHolder((View) v);
+ int pos = (viewHolder != null) ? viewHolder.getPosition() : -1;
+ updateViewFastScrollFocusState(v, pos, true);
+ }
+ }
+
+ /**
+ * Updates the fast scroll focus on all a given view.
+ */
+ private void updateViewFastScrollFocusState(BaseRecyclerViewFastScrollBar.FastScrollFocusableView v,
+ int pos, boolean animated) {
+ FastBitmapDrawable.State newState = FastBitmapDrawable.State.NORMAL;
+ if (mCurrentFastScrollSection != null && pos > -1) {
+ AlphabeticalAppsList.AdapterItem item = mApps.getAdapterItems().get(pos);
+ newState = item.sectionName.equals(mCurrentFastScrollSection) ?
+ FastBitmapDrawable.State.FAST_SCROLL_HIGHLIGHTED :
+ FastBitmapDrawable.State.FAST_SCROLL_UNHIGHLIGHTED;
+ }
+ v.setFastScrollFocusState(newState, animated);
+ }
+}
diff --git a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
index 1f95133d4..99004f2f7 100644
--- a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
+++ b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
@@ -69,6 +69,10 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.
// The message to continue to a market search when there are no filtered results
public static final int SEARCH_MARKET_VIEW_TYPE = 5;
+ public interface BindViewCallback {
+ public void onBindView(ViewHolder holder);
+ }
+
/**
* ViewHolder for each icon.
*/
@@ -328,6 +332,7 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.
private View.OnTouchListener mTouchListener;
private View.OnClickListener mIconClickListener;
private View.OnLongClickListener mIconLongClickListener;
+ private BindViewCallback mBindViewCallback;
@Thunk final Rect mBackgroundPadding = new Rect();
@Thunk int mPredictionBarDividerOffset;
@Thunk int mAppsPerRow;
@@ -425,6 +430,13 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.
}
/**
+ * Sets the callback for when views are bound.
+ */
+ public void setBindViewCallback(BindViewCallback cb) {
+ mBindViewCallback = cb;
+ }
+
+ /**
* Notifies the adapter of the background padding so that it can draw things correctly in the
* item decorator.
*/
@@ -529,6 +541,15 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.
}
break;
}
+ if (mBindViewCallback != null) {
+ mBindViewCallback.onBindView(holder);
+ }
+ }
+
+ @Override
+ public boolean onFailedToRecycleView(ViewHolder holder) {
+ // Always recycle and we will reset the view when it is bound
+ return true;
}
@Override
diff --git a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
index 10d10f11b..48b9494b7 100644
--- a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
@@ -15,24 +15,20 @@
*/
package com.android.launcher3.allapps;
-import android.animation.ObjectAnimator;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
-import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;
import android.view.View;
import com.android.launcher3.BaseRecyclerView;
-import com.android.launcher3.BaseRecyclerViewFastScrollBar;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.R;
import com.android.launcher3.Stats;
import com.android.launcher3.Utilities;
-import com.android.launcher3.util.Thunk;
import java.util.List;
@@ -42,25 +38,17 @@ import java.util.List;
public class AllAppsRecyclerView extends BaseRecyclerView
implements Stats.LaunchSourceProvider {
- private static final int FAST_SCROLL_MODE_JUMP_TO_FIRST_ICON = 0;
- private static final int FAST_SCROLL_MODE_FREE_SCROLL = 1;
-
- private static final int FAST_SCROLL_BAR_MODE_DISTRIBUTE_BY_ROW = 0;
- private static final int FAST_SCROLL_BAR_MODE_DISTRIBUTE_BY_SECTIONS = 1;
-
private AlphabeticalAppsList mApps;
+ private AllAppsFastScrollHelper mFastScrollHelper;
+ private BaseRecyclerView.ScrollPositionState mScrollPosState =
+ new BaseRecyclerView.ScrollPositionState();
private int mNumAppsPerRow;
- @Thunk BaseRecyclerViewFastScrollBar.FastScrollFocusableView mLastFastScrollFocusedView;
- @Thunk int mPrevFastScrollFocusedPosition;
- @Thunk int mFastScrollFrameIndex;
- @Thunk final int[] mFastScrollFrames = new int[10];
-
- private final int mFastScrollMode = FAST_SCROLL_MODE_JUMP_TO_FIRST_ICON;
- private final int mScrollBarMode = FAST_SCROLL_BAR_MODE_DISTRIBUTE_BY_ROW;
-
- private ScrollPositionState mScrollPosState = new ScrollPositionState();
+ // The specific icon heights that we use to calculate scroll
+ private int mPredictionIconHeight;
+ private int mIconHeight;
+ // The empty-search result background
private AllAppsBackgroundDrawable mEmptySearchBackground;
private int mEmptySearchBackgroundTopOffset;
@@ -79,8 +67,8 @@ public class AllAppsRecyclerView extends BaseRecyclerView
public AllAppsRecyclerView(Context context, AttributeSet attrs, int defStyleAttr,
int defStyleRes) {
super(context, attrs, defStyleAttr);
-
Resources res = getResources();
+ addOnItemTouchListener(this);
mScrollbar.setDetachThumbOnFastScroll();
mEmptySearchBackgroundTopOffset = res.getDimensionPixelSize(
R.dimen.all_apps_empty_search_bg_top_offset);
@@ -91,6 +79,7 @@ public class AllAppsRecyclerView extends BaseRecyclerView
*/
public void setApps(AlphabeticalAppsList apps) {
mApps = apps;
+ mFastScrollHelper = new AllAppsFastScrollHelper(this, apps);
}
/**
@@ -110,6 +99,14 @@ public class AllAppsRecyclerView extends BaseRecyclerView
}
/**
+ * Sets the heights of the icons in this view (for scroll calculations).
+ */
+ public void setPremeasuredIconHeights(int predictionIconHeight, int iconHeight) {
+ mPredictionIconHeight = predictionIconHeight;
+ mIconHeight = iconHeight;
+ }
+
+ /**
* Scrolls this recycler view to the top.
*/
public void scrollToTop() {
@@ -126,6 +123,7 @@ public class AllAppsRecyclerView extends BaseRecyclerView
*/
@Override
protected void dispatchDraw(Canvas canvas) {
+ // Clip to ensure that we don't draw the overscroll effect beyond the background bounds
canvas.clipRect(mBackgroundPadding.left, mBackgroundPadding.top,
getWidth() - mBackgroundPadding.right,
getHeight() - mBackgroundPadding.bottom);
@@ -157,14 +155,6 @@ public class AllAppsRecyclerView extends BaseRecyclerView
}
@Override
- protected void onFinishInflate() {
- super.onFinishInflate();
-
- // Bind event handlers
- addOnItemTouchListener(this);
- }
-
- @Override
public void fillInLaunchSourceData(Bundle sourceData) {
sourceData.putString(Stats.SOURCE_EXTRA_CONTAINER, Stats.CONTAINER_ALL_APPS);
if (mApps.hasFilter()) {
@@ -212,63 +202,31 @@ public class AllAppsRecyclerView extends BaseRecyclerView
List<AlphabeticalAppsList.FastScrollSectionInfo> 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;
+ for (int i = 1; i < fastScrollSections.size(); i++) {
+ AlphabeticalAppsList.FastScrollSectionInfo info = fastScrollSections.get(i);
+ if (info.touchFraction > touchFraction) {
+ break;
}
- } 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");
+ lastInfo = info;
}
- // Map the touch position back to the scroll of the recycler view
- getCurScrollState(mScrollPosState);
- int availableScrollHeight = getAvailableScrollHeight(rowCount, mScrollPosState.rowHeight);
- LinearLayoutManager layoutManager = (LinearLayoutManager) getLayoutManager();
- if (mFastScrollMode == FAST_SCROLL_MODE_FREE_SCROLL) {
- layoutManager.scrollToPositionWithOffset(0, (int) -(availableScrollHeight * touchFraction));
- }
-
- if (mPrevFastScrollFocusedPosition != lastInfo.fastScrollToItem.position) {
- mPrevFastScrollFocusedPosition = lastInfo.fastScrollToItem.position;
-
- // Reset the last focused view
- if (mLastFastScrollFocusedView != null) {
- mLastFastScrollFocusedView.setFastScrollFocused(false, true);
- mLastFastScrollFocusedView = null;
- }
-
- 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");
- }
- }
+ // Update the fast scroll
+ int scrollY = getScrollTop(mScrollPosState);
+ int availableScrollHeight = getAvailableScrollHeight(mApps.getNumAppRows());
+ mFastScrollHelper.smoothScrollToSection(scrollY, availableScrollHeight, lastInfo);
return lastInfo.sectionName;
}
@Override
public void onFastScrollCompleted() {
super.onFastScrollCompleted();
- // Reset and clean up the last focused view
- if (mLastFastScrollFocusedView != null) {
- mLastFastScrollFocusedView.setFastScrollFocused(false, true);
- mLastFastScrollFocusedView = null;
- }
- mPrevFastScrollFocusedPosition = -1;
+ mFastScrollHelper.onFastScrollCompleted();
+ }
+
+ @Override
+ public void setAdapter(Adapter adapter) {
+ super.setAdapter(adapter);
+ mFastScrollHelper.onSetAdapter((AllAppsGridAdapter) adapter);
}
/**
@@ -286,7 +244,7 @@ public class AllAppsRecyclerView extends BaseRecyclerView
// Find the index and height of the first visible row (all rows have the same height)
int rowCount = mApps.getNumAppRows();
- getCurScrollState(mScrollPosState);
+ getCurScrollState(mScrollPosState, -1);
if (mScrollPosState.rowIndex < 0) {
mScrollbar.setThumbOffset(-1, -1);
return;
@@ -294,7 +252,7 @@ public class AllAppsRecyclerView extends BaseRecyclerView
// Only show the scrollbar if there is height to be scrolled
int availableScrollBarHeight = getAvailableScrollBarHeight();
- int availableScrollHeight = getAvailableScrollHeight(mApps.getNumAppRows(), mScrollPosState.rowHeight);
+ int availableScrollHeight = getAvailableScrollHeight(mApps.getNumAppRows());
if (availableScrollHeight <= 0) {
mScrollbar.setThumbOffset(-1, -1);
return;
@@ -303,8 +261,7 @@ public class AllAppsRecyclerView extends BaseRecyclerView
// Calculate the current scroll position, the scrollY of the recycler view accounts for the
// view padding, while the scrollBarY is drawn right up to the background padding (ignoring
// padding)
- int scrollY = getPaddingTop() +
- (mScrollPosState.rowIndex * mScrollPosState.rowHeight) - mScrollPosState.rowTopOffset;
+ int scrollY = getScrollTop(mScrollPosState);
int scrollBarY = mBackgroundPadding.top +
(int) (((float) scrollY / availableScrollHeight) * availableScrollBarHeight);
@@ -355,58 +312,12 @@ public class AllAppsRecyclerView extends BaseRecyclerView
}
/**
- * 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) {
- 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);
- }
- }
- }
- };
-
- /**
- * 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 curScrollY = getPaddingTop() +
- (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 of the apps rows.
*/
- protected void getCurScrollState(ScrollPositionState stateOut) {
+ protected void getCurScrollState(ScrollPositionState stateOut, int viewTypeMask) {
stateOut.rowIndex = -1;
stateOut.rowTopOffset = -1;
- stateOut.rowHeight = -1;
+ stateOut.itemPos = -1;
// Return early if there are no items or we haven't been measured
List<AlphabeticalAppsList.AdapterItem> items = mApps.getAdapterItems();
@@ -420,15 +331,15 @@ public class AllAppsRecyclerView extends BaseRecyclerView
int position = getChildPosition(child);
if (position != NO_POSITION) {
AlphabeticalAppsList.AdapterItem item = items.get(position);
- if (item.viewType == AllAppsGridAdapter.ICON_VIEW_TYPE ||
- item.viewType == AllAppsGridAdapter.PREDICTION_ICON_VIEW_TYPE) {
+ if ((item.viewType & viewTypeMask) != 0) {
stateOut.rowIndex = item.rowIndex;
stateOut.rowTopOffset = getLayoutManager().getDecoratedTop(child);
- stateOut.rowHeight = child.getHeight();
- break;
+ stateOut.itemPos = position;
+ return;
}
}
}
+ return;
}
@Override
@@ -438,18 +349,13 @@ public class AllAppsRecyclerView extends BaseRecyclerView
return !mApps.hasFilter();
}
- /**
- * 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 ||
- item.viewType == AllAppsGridAdapter.PREDICTION_ICON_VIEW_TYPE) {
- int offset = item.rowIndex > 0 ? getPaddingTop() : 0;
- return offset + item.rowIndex * rowHeight;
- } else {
+ protected int getTop(int rowIndex) {
+ if (getChildCount() == 0 || rowIndex <= 0) {
return 0;
}
+
+ // The prediction bar icons have more padding, so account for that in the row offset
+ return mPredictionIconHeight + (rowIndex - 1) * mIconHeight;
}
/**