summaryrefslogtreecommitdiffstats
path: root/src/com/android/launcher3/allapps/AllAppsFastScrollHelper.java
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/AllAppsFastScrollHelper.java
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/AllAppsFastScrollHelper.java')
-rw-r--r--src/com/android/launcher3/allapps/AllAppsFastScrollHelper.java228
1 files changed, 228 insertions, 0 deletions
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);
+ }
+}