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 --- proguard.flags | 48 ++-- res/drawable-ldrtl/all_apps_fastscroll_bg.xml | 2 +- res/drawable/all_apps_fastscroll_bg.xml | 27 -- res/drawable/all_apps_scrollbar_thumb.xml | 21 -- res/drawable/container_fastscroll_popup_bg.xml | 27 ++ res/layout/all_apps_container.xml | 4 +- res/layout/all_apps_search_bar.xml | 4 +- res/values-sw720dp/dimens.xml | 2 +- res/values/colors.xml | 6 +- res/values/dimens.xml | 24 +- src/com/android/launcher3/BaseRecyclerView.java | 289 +++++++-------------- .../launcher3/BaseRecyclerViewFastScrollBar.java | 232 +++++++++++++++++ .../launcher3/BaseRecyclerViewFastScrollPopup.java | 160 ++++++++++++ src/com/android/launcher3/BubbleTextView.java | 83 +++++- src/com/android/launcher3/Launcher.java | 21 -- src/com/android/launcher3/LauncherAppState.java | 7 - src/com/android/launcher3/LauncherCallbacks.java | 2 + src/com/android/launcher3/Utilities.java | 19 ++ .../launcher3/allapps/AllAppsContainerView.java | 32 ++- .../launcher3/allapps/AllAppsGridAdapter.java | 2 +- .../launcher3/allapps/AllAppsRecyclerView.java | 248 ++++++++++-------- .../launcher3/allapps/AlphabeticalAppsList.java | 76 +++++- .../launcher3/widget/WidgetsContainerView.java | 4 +- .../launcher3/widget/WidgetsRecyclerView.java | 65 +++-- 24 files changed, 928 insertions(+), 477 deletions(-) delete mode 100644 res/drawable/all_apps_fastscroll_bg.xml delete mode 100644 res/drawable/all_apps_scrollbar_thumb.xml create mode 100644 res/drawable/container_fastscroll_popup_bg.xml create mode 100644 src/com/android/launcher3/BaseRecyclerViewFastScrollBar.java create mode 100644 src/com/android/launcher3/BaseRecyclerViewFastScrollPopup.java diff --git a/proguard.flags b/proguard.flags index 7ec488b2d..6a9d6f345 100644 --- a/proguard.flags +++ b/proguard.flags @@ -1,9 +1,30 @@ +-keep class com.android.launcher3.BaseRecyclerViewFastScrollBar { + public void setWidth(int); + public int getWidth(); + public void setTrackAlpha(int); + public int getTrackAlpha(); +} + +-keep class com.android.launcher3.BaseRecyclerViewFastScrollPopup { + public void setAlpha(float); + public float getAlpha(); +} + +-keep class com.android.launcher3.BubbleTextView { + public void setFastScrollFocus(float); + public float getFastScrollFocus(); +} + +-keep class com.android.launcher3.ButtonDropTarget { + public int getTextColor(); +} + -keep class com.android.launcher3.CellLayout { public float getBackgroundAlpha(); public void setBackgroundAlpha(float); } --keep class com.android.launcher3.DragLayer$LayoutParams { +-keep class com.android.launcher3.CellLayout$LayoutParams { public void setWidth(int); public int getWidth(); public void setHeight(int); @@ -14,7 +35,7 @@ public int getY(); } --keep class com.android.launcher3.CellLayout$LayoutParams { +-keep class com.android.launcher3.DragLayer$LayoutParams { public void setWidth(int); public int getWidth(); public void setHeight(int); @@ -25,9 +46,9 @@ public int getY(); } --keep class com.android.launcher3.Workspace { - public float getBackgroundAlpha(); - public void setBackgroundAlpha(float); +-keep class com.android.launcher3.FastBitmapDrawable { + public int getBrightness(); + public void setBrightness(int); } -keep class com.android.launcher3.MemoryDumpActivity { @@ -39,16 +60,7 @@ public void setAnimationProgress(float); } --keep class com.android.launcher3.FastBitmapDrawable { - public int getBrightness(); - public void setBrightness(int); -} - --keep class com.android.launcher3.BaseRecyclerView { - public void setFastScrollerAlpha(float); - public float getFastScrollerAlpha(); -} - --keep class com.android.launcher3.ButtonDropTarget { - public int getTextColor(); -} +-keep class com.android.launcher3.Workspace { + public float getBackgroundAlpha(); + public void setBackgroundAlpha(float); +} \ No newline at end of file diff --git a/res/drawable-ldrtl/all_apps_fastscroll_bg.xml b/res/drawable-ldrtl/all_apps_fastscroll_bg.xml index 4777f70bb..d79096807 100644 --- a/res/drawable-ldrtl/all_apps_fastscroll_bg.xml +++ b/res/drawable-ldrtl/all_apps_fastscroll_bg.xml @@ -16,7 +16,7 @@ --> - + diff --git a/res/drawable/all_apps_fastscroll_bg.xml b/res/drawable/all_apps_fastscroll_bg.xml deleted file mode 100644 index 6b7448459..000000000 --- a/res/drawable/all_apps_fastscroll_bg.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/res/drawable/all_apps_scrollbar_thumb.xml b/res/drawable/all_apps_scrollbar_thumb.xml deleted file mode 100644 index 649a963b1..000000000 --- a/res/drawable/all_apps_scrollbar_thumb.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/res/drawable/container_fastscroll_popup_bg.xml b/res/drawable/container_fastscroll_popup_bg.xml new file mode 100644 index 000000000..2ef07ab96 --- /dev/null +++ b/res/drawable/container_fastscroll_popup_bg.xml @@ -0,0 +1,27 @@ + + + + + + + \ No newline at end of file diff --git a/res/layout/all_apps_container.xml b/res/layout/all_apps_container.xml index 1de82019c..0b624e6a5 100644 --- a/res/layout/all_apps_container.xml +++ b/res/layout/all_apps_container.xml @@ -38,8 +38,8 @@ android:id="@+id/prediction_bar" android:layout_width="match_parent" android:layout_height="wrap_content" - android:paddingTop="@dimen/all_apps_prediction_bar_top_bottom_padding" - android:paddingBottom="@dimen/all_apps_prediction_bar_top_bottom_padding" + android:paddingTop="@dimen/all_apps_prediction_bar_top_padding" + android:paddingBottom="@dimen/all_apps_prediction_bar_bottom_padding" android:orientation="horizontal" android:focusable="true" android:descendantFocusability="afterDescendants" diff --git a/res/layout/all_apps_search_bar.xml b/res/layout/all_apps_search_bar.xml index 8d75b15a6..cf30eac36 100644 --- a/res/layout/all_apps_search_bar.xml +++ b/res/layout/all_apps_search_bar.xml @@ -63,8 +63,8 @@ android:layout_width="wrap_content" android:layout_height="@dimen/all_apps_search_bar_height" android:layout_gravity="end|center_vertical" - android:layout_marginEnd="6dp" - android:layout_marginRight="6dp" + android:layout_marginEnd="4dp" + android:layout_marginRight="4dp" android:contentDescription="@string/all_apps_search_bar_hint" android:paddingBottom="13dp" android:paddingTop="13dp" diff --git a/res/values-sw720dp/dimens.xml b/res/values-sw720dp/dimens.xml index 9d1e3529c..d48f9eed0 100644 --- a/res/values-sw720dp/dimens.xml +++ b/res/values-sw720dp/dimens.xml @@ -17,7 +17,7 @@ 54dp - 16dp + 14dp 8dip diff --git a/res/values/colors.xml b/res/values/colors.xml index 7d36101ef..5afc5b98d 100644 --- a/res/values/colors.xml +++ b/res/values/colors.xml @@ -39,11 +39,15 @@ #FFFFFFFF #FF374248 + + #42000000 + #009688 + - #009688 #009688 + #42FFFFFF #FFFFFF #C4C4C4 #263238 diff --git a/res/values/dimens.xml b/res/values/dimens.xml index da56d9049..122b831b4 100644 --- a/res/values/dimens.xml +++ b/res/values/dimens.xml @@ -49,12 +49,20 @@ 4dip 12dip - + 8dp 4dp + 4dp + 8dp + 64dp + -24dp + 72dp + 48dp + + 0dp 8dp 24sp @@ -62,16 +70,10 @@ 8dp 8dp 24dp - 16dp - - 4dp - -24dp - 72dp - 48dp - - 4dp - 16dp - 6dp + + 0dp + 16dp + 8dp 8dp diff --git a/src/com/android/launcher3/BaseRecyclerView.java b/src/com/android/launcher3/BaseRecyclerView.java index 140c28c0c..0fae427e8 100644 --- a/src/com/android/launcher3/BaseRecyclerView.java +++ b/src/com/android/launcher3/BaseRecyclerView.java @@ -16,24 +16,15 @@ package com.android.launcher3; -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.animation.ObjectAnimator; import android.content.Context; -import android.content.res.Resources; import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.Paint; import android.graphics.Rect; -import android.graphics.drawable.Drawable; import android.support.v7.widget.RecyclerView; import android.util.AttributeSet; import android.view.MotionEvent; -import android.view.View; -import android.view.ViewConfiguration; - import com.android.launcher3.util.Thunk; + /** * A base {@link RecyclerView}, which does the following: *
    @@ -41,7 +32,7 @@ import com.android.launcher3.util.Thunk; *
  • Enable fast scroller. *
*/ -public class BaseRecyclerView extends RecyclerView +public abstract class BaseRecyclerView extends RecyclerView implements RecyclerView.OnItemTouchListener { private static final int SCROLL_DELTA_THRESHOLD_DP = 4; @@ -50,14 +41,8 @@ public class BaseRecyclerView extends RecyclerView @Thunk int mDy = 0; private float mDeltaThreshold; - // - // Keeps track of variables required for the second function of this class: fast scroller. - // - - private static final float FAST_SCROLL_OVERLAY_Y_OFFSET_FACTOR = 1.5f; - /** - * The current scroll state of the recycler view. We use this in updateVerticalScrollbarBounds() + * The current scroll state of the recycler view. We use this in onUpdateScrollbar() * and scrollToPositionAtProgress() to determine the scroll position of the recycler view so * that we can calculate what the scroll bar looks like, and where to jump to from the fast * scroller. @@ -70,27 +55,12 @@ public class BaseRecyclerView extends RecyclerView // The height of a given row (they are currently all the same height) public int rowHeight; } - // Should be maintained inside overriden method #updateVerticalScrollbarBounds - public ScrollPositionState scrollPosState = new ScrollPositionState(); - public Rect verticalScrollbarBounds = new Rect(); - private boolean mDraggingFastScroller; - - private Drawable mScrollbar; - private Drawable mFastScrollerBg; - private Rect mTmpFastScrollerInvalidateRect = new Rect(); - private Rect mFastScrollerBounds = new Rect(); - - private String mFastScrollSectionName; - private Paint mFastScrollTextPaint; - private Rect mFastScrollTextBounds = new Rect(); - private float mFastScrollAlpha; + protected BaseRecyclerViewFastScrollBar mScrollbar; private int mDownX; private int mDownY; private int mLastY; - private int mScrollbarWidth; - private int mScrollbarInset; protected Rect mBackgroundPadding = new Rect(); public BaseRecyclerView(Context context) { @@ -104,25 +74,10 @@ public class BaseRecyclerView extends RecyclerView public BaseRecyclerView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); mDeltaThreshold = getResources().getDisplayMetrics().density * SCROLL_DELTA_THRESHOLD_DP; + mScrollbar = new BaseRecyclerViewFastScrollBar(this, getResources()); ScrollListener listener = new ScrollListener(); setOnScrollListener(listener); - - Resources res = context.getResources(); - int fastScrollerSize = res.getDimensionPixelSize(R.dimen.all_apps_fast_scroll_popup_size); - mScrollbar = res.getDrawable(R.drawable.all_apps_scrollbar_thumb); - mFastScrollerBg = res.getDrawable(R.drawable.all_apps_fastscroll_bg); - mFastScrollerBg.setBounds(0, 0, fastScrollerSize, fastScrollerSize); - mFastScrollTextPaint = new Paint(); - mFastScrollTextPaint.setColor(Color.WHITE); - mFastScrollTextPaint.setAntiAlias(true); - mFastScrollTextPaint.setTextSize(res.getDimensionPixelSize( - R.dimen.all_apps_fast_scroll_text_size)); - mScrollbarWidth = res.getDimensionPixelSize(R.dimen.all_apps_fast_scroll_bar_width); - mScrollbarInset = - res.getDimensionPixelSize(R.dimen.all_apps_fast_scroll_scrubber_touch_inset); - setFastScrollerAlpha(mFastScrollAlpha); - setOverScrollMode(View.OVER_SCROLL_NEVER); } private class ScrollListener extends OnScrollListener { @@ -133,6 +88,10 @@ public class BaseRecyclerView extends RecyclerView @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { mDy = dy; + + // TODO(winsonc): If we want to animate the section heads while scrolling, we can + // initiate that here if the recycler view scroll state is not + // RecyclerView.SCROLL_STATE_IDLE. } } @@ -161,8 +120,6 @@ public class BaseRecyclerView extends RecyclerView * it is already showing). */ private boolean handleTouchEvent(MotionEvent ev) { - ViewConfiguration config = ViewConfiguration.get(getContext()); - int action = ev.getAction(); int x = (int) ev.getX(); int y = (int) ev.getY(); @@ -174,41 +131,19 @@ public class BaseRecyclerView extends RecyclerView if (shouldStopScroll(ev)) { stopScroll(); } + mScrollbar.handleTouchEvent(ev, mDownX, mDownY, mLastY); break; case MotionEvent.ACTION_MOVE: - // Check if we are scrolling - if (!mDraggingFastScroller && isPointNearScrollbar(mDownX, mDownY) && - Math.abs(y - mDownY) > config.getScaledTouchSlop()) { - getParent().requestDisallowInterceptTouchEvent(true); - mDraggingFastScroller = true; - animateFastScrollerVisibility(true); - } - if (mDraggingFastScroller) { - mLastY = y; - - // Scroll to the right position, and update the section name - int top = getPaddingTop() + (mFastScrollerBg.getBounds().height() / 2); - int bottom = getHeight() - getPaddingBottom() - - (mFastScrollerBg.getBounds().height() / 2); - float boundedY = (float) Math.max(top, Math.min(bottom, y)); - mFastScrollSectionName = scrollToPositionAtProgress((boundedY - top) / - (bottom - top)); - - // Combine the old and new fast scroller bounds to create the full invalidate - // rect - mTmpFastScrollerInvalidateRect.set(mFastScrollerBounds); - updateFastScrollerBounds(); - mTmpFastScrollerInvalidateRect.union(mFastScrollerBounds); - invalidateFastScroller(mTmpFastScrollerInvalidateRect); - } + mLastY = y; + mScrollbar.handleTouchEvent(ev, mDownX, mDownY, mLastY); break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: - mDraggingFastScroller = false; - animateFastScrollerVisibility(false); + onFastScrollCompleted(); + mScrollbar.handleTouchEvent(ev, mDownX, mDownY, mLastY); break; } - return mDraggingFastScroller; + return mScrollbar.isDragging(); } public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) { @@ -234,159 +169,117 @@ public class BaseRecyclerView extends RecyclerView mBackgroundPadding.set(padding); } - @Override - protected void dispatchDraw(Canvas canvas) { - super.dispatchDraw(canvas); - drawVerticalScrubber(canvas); - drawFastScrollerPopup(canvas); + public Rect getBackgroundPadding() { + return mBackgroundPadding; } /** - * Draws the vertical scrollbar. + * Returns the scroll bar width when the user is scrolling. */ - private void drawVerticalScrubber(Canvas canvas) { - updateVerticalScrollbarBounds(); - - // Draw the scroll bar - int restoreCount = canvas.save(Canvas.MATRIX_SAVE_FLAG); - canvas.translate(verticalScrollbarBounds.left, verticalScrollbarBounds.top); - mScrollbar.setBounds(0, 0, mScrollbarWidth, verticalScrollbarBounds.height()); - mScrollbar.draw(canvas); - canvas.restoreToCount(restoreCount); + public int getMaxScrollbarWidth() { + return mScrollbar.getThumbMaxWidth(); } /** - * Draws the fast scroller popup. + * Returns the available scroll height: + * AvailableScrollHeight = Total height of the all items - last page height + * + * This assumes that all rows are the same height. + * + * @param yOffset the offset from the top of the recycler view to start tracking. */ - private void drawFastScrollerPopup(Canvas canvas) { - if (mFastScrollAlpha > 0f && mFastScrollSectionName != null && !mFastScrollSectionName.isEmpty()) { - // Draw the fast scroller popup - int restoreCount = canvas.save(Canvas.MATRIX_SAVE_FLAG); - canvas.translate(mFastScrollerBounds.left, mFastScrollerBounds.top); - mFastScrollerBg.setAlpha((int) (mFastScrollAlpha * 255)); - mFastScrollerBg.draw(canvas); - mFastScrollTextPaint.setAlpha((int) (mFastScrollAlpha * 255)); - mFastScrollTextPaint.getTextBounds(mFastScrollSectionName, 0, - mFastScrollSectionName.length(), mFastScrollTextBounds); - float textWidth = mFastScrollTextPaint.measureText(mFastScrollSectionName); - canvas.drawText(mFastScrollSectionName, - (mFastScrollerBounds.width() - textWidth) / 2, - mFastScrollerBounds.height() - - (mFastScrollerBounds.height() - mFastScrollTextBounds.height()) / 2, - mFastScrollTextPaint); - canvas.restoreToCount(restoreCount); - } + protected int getAvailableScrollHeight(int rowCount, int rowHeight, int yOffset) { + int visibleHeight = getHeight() - mBackgroundPadding.top - mBackgroundPadding.bottom; + int scrollHeight = getPaddingTop() + yOffset + rowCount * rowHeight + getPaddingBottom(); + int availableScrollHeight = scrollHeight - visibleHeight; + return availableScrollHeight; } /** - * Returns the scroll bar width. + * Returns the available scroll bar height: + * AvailableScrollBarHeight = Total height of the visible view - thumb height */ - public int getScrollbarWidth() { - return mScrollbarWidth; + protected int getAvailableScrollBarHeight() { + int visibleHeight = getHeight() - mBackgroundPadding.top - mBackgroundPadding.bottom; + int availableScrollBarHeight = visibleHeight - mScrollbar.getThumbHeight(); + return availableScrollBarHeight; } /** - * Sets the fast scroller alpha. + * Returns the track color (ignoring alpha), can be overridden by each subclass. */ - public void setFastScrollerAlpha(float alpha) { - mFastScrollAlpha = alpha; - invalidateFastScroller(mFastScrollerBounds); + public int getFastScrollerTrackColor(int defaultTrackColor) { + return defaultTrackColor; } /** - * Returns the fast scroller alpha. + * Returns the inactive thumb color, can be overridden by each subclass. */ - public float getFastScrollerAlpha() { - return mFastScrollAlpha; + public int getFastScrollerThumbInactiveColor(int defaultInactiveThumbColor) { + return defaultInactiveThumbColor; } - /** - * Maps the touch (from 0..1) to the adapter position that should be visible. - *

Override in each subclass of this base class. - */ - public String scrollToPositionAtProgress(float touchFraction) { - return null; + @Override + protected void dispatchDraw(Canvas canvas) { + super.dispatchDraw(canvas); + onUpdateScrollbar(); + mScrollbar.draw(canvas); } /** - * Updates the bounds for the scrollbar. - *

Override in each subclass of this base class. - */ - public void updateVerticalScrollbarBounds() {}; - - /** - * Animates the visibility of the fast scroller popup. + * Updates the scrollbar thumb offset to match the visible scroll of the recycler view. It does + * this by mapping the available scroll area of the recycler view to the available space for the + * scroll bar. + * + * @param scrollPosState the current scroll position + * @param rowCount the number of rows, used to calculate the total scroll height (assumes that + * all rows are the same height) + * @param yOffset the offset to start tracking in the recycler view (only used for all apps) */ - private void animateFastScrollerVisibility(final boolean visible) { - ObjectAnimator anim = ObjectAnimator.ofFloat(this, "fastScrollerAlpha", visible ? 1f : 0f); - anim.setDuration(visible ? 200 : 150); - anim.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationStart(Animator animation) { - if (visible) { - onFastScrollingStart(); - } - } + protected void synchronizeScrollBarThumbOffsetToViewScroll(ScrollPositionState scrollPosState, + int rowCount, int yOffset) { + int availableScrollHeight = getAvailableScrollHeight(rowCount, scrollPosState.rowHeight, + yOffset); + int availableScrollBarHeight = getAvailableScrollBarHeight(); + + // Only show the scrollbar if there is height to be scrolled + if (availableScrollHeight <= 0) { + mScrollbar.setScrollbarThumbOffset(-1, -1); + return; + } - @Override - public void onAnimationEnd(Animator animation) { - if (!visible) { - onFastScrollingEnd(); - } - } - }); - anim.start(); + // 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() + yOffset + + (scrollPosState.rowIndex * scrollPosState.rowHeight) - scrollPosState.rowTopOffset; + int scrollBarY = mBackgroundPadding.top + + (int) (((float) scrollY / availableScrollHeight) * availableScrollBarHeight); + + // Calculate the position and size of the scroll bar + int scrollBarX; + if (Utilities.isRtl(getResources())) { + scrollBarX = mBackgroundPadding.left; + } else { + scrollBarX = getWidth() - mBackgroundPadding.right - mScrollbar.getWidth(); + } + mScrollbar.setScrollbarThumbOffset(scrollBarX, scrollBarY); } /** - * To be overridden by subclasses. - */ - protected void onFastScrollingStart() {} - - /** - * To be overridden by subclasses. - */ - protected void onFastScrollingEnd() {} - - /** - * Invalidates the fast scroller popup. + * Maps the touch (from 0..1) to the adapter position that should be visible. + *

Override in each subclass of this base class. */ - protected void invalidateFastScroller(Rect bounds) { - invalidate(bounds.left, bounds.top, bounds.right, bounds.bottom); - } + public abstract String scrollToPositionAtProgress(float touchFraction); /** - * Returns whether a given point is near the scrollbar. + * Updates the bounds for the scrollbar. + *

Override in each subclass of this base class. */ - private boolean isPointNearScrollbar(int x, int y) { - // Check if we are scrolling - updateVerticalScrollbarBounds(); - verticalScrollbarBounds.inset(mScrollbarInset, mScrollbarInset); - return verticalScrollbarBounds.contains(x, y); - } + public abstract void onUpdateScrollbar(); /** - * Updates the bounds for the fast scroller. + *

Override in each subclass of this base class. */ - private void updateFastScrollerBounds() { - if (mFastScrollAlpha > 0f && !mFastScrollSectionName.isEmpty()) { - int x; - int y; - - // Calculate the position for the fast scroller popup - Rect bgBounds = mFastScrollerBg.getBounds(); - if (Utilities.isRtl(getResources())) { - x = mBackgroundPadding.left + (2 * getScrollbarWidth()); - } else { - x = getWidth() - mBackgroundPadding.right - (2 * getScrollbarWidth()) - - bgBounds.width(); - } - y = mLastY - (int) (FAST_SCROLL_OVERLAY_Y_OFFSET_FACTOR * bgBounds.height()); - y = Math.max(getPaddingTop(), Math.min(y, getHeight() - getPaddingBottom() - - bgBounds.height())); - mFastScrollerBounds.set(x, y, x + bgBounds.width(), y + bgBounds.height()); - } else { - mFastScrollerBounds.setEmpty(); - } - } + public void onFastScrollCompleted() {} } \ No newline at end of file diff --git a/src/com/android/launcher3/BaseRecyclerViewFastScrollBar.java b/src/com/android/launcher3/BaseRecyclerViewFastScrollBar.java new file mode 100644 index 000000000..96e994b05 --- /dev/null +++ b/src/com/android/launcher3/BaseRecyclerViewFastScrollBar.java @@ -0,0 +1,232 @@ +/* + * 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; + +import android.animation.Animator; +import android.animation.AnimatorSet; +import android.animation.ArgbEvaluator; +import android.animation.ObjectAnimator; +import android.animation.ValueAnimator; +import android.content.res.Resources; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.Point; +import android.graphics.Rect; +import android.view.MotionEvent; +import android.view.ViewConfiguration; + +/** + * The track and scrollbar that shows when you scroll the list. + */ +public class BaseRecyclerViewFastScrollBar { + + public interface FastScrollFocusableView { + void setFastScrollFocused(boolean focused, boolean animated); + } + + private final static int MAX_TRACK_ALPHA = 30; + private final static int SCROLL_BAR_VIS_DURATION = 150; + + private BaseRecyclerView mRv; + private BaseRecyclerViewFastScrollPopup mPopup; + + private AnimatorSet mScrollbarAnimator; + + private int mThumbInactiveColor; + private int mThumbActiveColor; + private Point mThumbOffset = new Point(-1, -1); + private Paint mThumbPaint; + private Paint mTrackPaint; + private int mThumbMinWidth; + private int mThumbMaxWidth; + private int mThumbWidth; + private int mThumbHeight; + // The inset is the buffer around which a point will still register as a click on the scrollbar + private int mTouchInset; + private boolean mIsDragging; + + // This is the offset from the top of the scrollbar when the user first starts touching. To + // prevent jumping, this offset is applied as the user scrolls. + private int mTouchOffset; + + private Rect mInvalidateRect = new Rect(); + private Rect mTmpRect = new Rect(); + + public BaseRecyclerViewFastScrollBar(BaseRecyclerView rv, Resources res) { + mRv = rv; + mPopup = new BaseRecyclerViewFastScrollPopup(rv, res); + mTrackPaint = new Paint(); + mTrackPaint.setColor(rv.getFastScrollerTrackColor(Color.BLACK)); + mTrackPaint.setAlpha(0); + mThumbInactiveColor = rv.getFastScrollerThumbInactiveColor( + res.getColor(R.color.container_fastscroll_thumb_inactive_color)); + mThumbActiveColor = res.getColor(R.color.container_fastscroll_thumb_active_color); + mThumbPaint = new Paint(); + mThumbPaint.setColor(mThumbInactiveColor); + mThumbWidth = mThumbMinWidth = res.getDimensionPixelSize(R.dimen.container_fastscroll_thumb_min_width); + mThumbMaxWidth = res.getDimensionPixelSize(R.dimen.container_fastscroll_thumb_max_width); + mThumbHeight = res.getDimensionPixelSize(R.dimen.container_fastscroll_thumb_height); + mTouchInset = res.getDimensionPixelSize(R.dimen.container_fastscroll_thumb_touch_inset); + } + + public void setScrollbarThumbOffset(int x, int y) { + if (mThumbOffset.x == x && mThumbOffset.y == y) { + return; + } + mInvalidateRect.set(mThumbOffset.x, 0, mThumbOffset.x + mThumbWidth, mRv.getHeight()); + mThumbOffset.set(x, y); + mInvalidateRect.union(new Rect(mThumbOffset.x, 0, mThumbOffset.x + mThumbWidth, + mRv.getHeight())); + mRv.invalidate(mInvalidateRect); + } + + // Setter/getter for the search bar width for animations + public void setWidth(int width) { + mInvalidateRect.set(mThumbOffset.x, 0, mThumbOffset.x + mThumbWidth, mRv.getHeight()); + mThumbWidth = width; + mInvalidateRect.union(new Rect(mThumbOffset.x, 0, mThumbOffset.x + mThumbWidth, + mRv.getHeight())); + mRv.invalidate(mInvalidateRect); + } + + public int getWidth() { + return mThumbWidth; + } + + // Setter/getter for the track background alpha for animations + public void setTrackAlpha(int alpha) { + mTrackPaint.setAlpha(alpha); + mInvalidateRect.set(mThumbOffset.x, 0, mThumbOffset.x + mThumbWidth, mRv.getHeight()); + mRv.invalidate(mInvalidateRect); + } + + public int getTrackAlpha() { + return mTrackPaint.getAlpha(); + } + + public int getThumbHeight() { + return mThumbHeight; + } + + public int getThumbMaxWidth() { + return mThumbMaxWidth; + } + + public boolean isDragging() { + return mIsDragging; + } + + /** + * Handles the touch event and determines whether to show the fast scroller (or updates it if + * it is already showing). + */ + public void handleTouchEvent(MotionEvent ev, int downX, int downY, int lastY) { + ViewConfiguration config = ViewConfiguration.get(mRv.getContext()); + + int action = ev.getAction(); + int y = (int) ev.getY(); + switch (action) { + case MotionEvent.ACTION_DOWN: + if (isNearPoint(downX, downY)) { + mTouchOffset = downY - mThumbOffset.y; + } + break; + case MotionEvent.ACTION_MOVE: + // Check if we should start scrolling + if (!mIsDragging && isNearPoint(downX, downY) && + Math.abs(y - downY) > config.getScaledTouchSlop()) { + mRv.getParent().requestDisallowInterceptTouchEvent(true); + mIsDragging = true; + mTouchOffset += (lastY - downY); + mPopup.animateVisibility(true); + animateScrollbar(true); + } + if (mIsDragging) { + // Update the fastscroller section name at this touch position + int top = mRv.getBackgroundPadding().top; + int bottom = mRv.getHeight() - mRv.getBackgroundPadding().bottom - mThumbHeight; + float boundedY = (float) Math.max(top, Math.min(bottom, y - mTouchOffset)); + String sectionName = mRv.scrollToPositionAtProgress((boundedY - top) / + (bottom - top)); + mPopup.setSectionName(sectionName); + mPopup.animateVisibility(!sectionName.isEmpty()); + mRv.invalidate(mPopup.updateFastScrollerBounds(mRv, lastY)); + } + break; + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_CANCEL: + mIsDragging = false; + mTouchOffset = 0; + mPopup.animateVisibility(false); + animateScrollbar(false); + break; + } + } + + public void draw(Canvas canvas) { + if (mThumbOffset.x < 0 || mThumbOffset.y < 0) { + return; + } + + // Draw the scroll bar track and thumb + if (mTrackPaint.getAlpha() > 0) { + canvas.drawRect(mThumbOffset.x, 0, mThumbOffset.x + mThumbWidth, mRv.getHeight(), mTrackPaint); + } + canvas.drawRect(mThumbOffset.x, mThumbOffset.y, mThumbOffset.x + mThumbWidth, + mThumbOffset.y + mThumbHeight, mThumbPaint); + + // Draw the popup + mPopup.draw(canvas); + } + + /** + * Animates the width and color of the scrollbar. + */ + private void animateScrollbar(boolean isScrolling) { + if (mScrollbarAnimator != null) { + mScrollbarAnimator.cancel(); + } + ObjectAnimator trackAlphaAnim = ObjectAnimator.ofInt(this, "trackAlpha", + isScrolling ? MAX_TRACK_ALPHA : 0); + ObjectAnimator thumbWidthAnim = ObjectAnimator.ofInt(this, "width", + isScrolling ? mThumbMaxWidth : mThumbMinWidth); + ValueAnimator colorAnimation = ValueAnimator.ofObject(new ArgbEvaluator(), + mThumbPaint.getColor(), isScrolling ? mThumbActiveColor : mThumbInactiveColor); + colorAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animator) { + mThumbPaint.setColor((Integer) animator.getAnimatedValue()); + mRv.invalidate(mThumbOffset.x, mThumbOffset.y, mThumbOffset.x + mThumbWidth, + mThumbOffset.y + mThumbHeight); + } + }); + mScrollbarAnimator = new AnimatorSet(); + mScrollbarAnimator.playTogether(trackAlphaAnim, thumbWidthAnim, colorAnimation); + mScrollbarAnimator.setDuration(SCROLL_BAR_VIS_DURATION); + mScrollbarAnimator.start(); + } + + /** + * Returns whether the specified points are near the scroll bar bounds. + */ + private boolean isNearPoint(int x, int y) { + mTmpRect.set(mThumbOffset.x, mThumbOffset.y, mThumbOffset.x + mThumbWidth, + mThumbOffset.y + mThumbHeight); + mTmpRect.inset(mTouchInset, mTouchInset); + return mTmpRect.contains(x, y); + } +} diff --git a/src/com/android/launcher3/BaseRecyclerViewFastScrollPopup.java b/src/com/android/launcher3/BaseRecyclerViewFastScrollPopup.java new file mode 100644 index 000000000..aeeb5156d --- /dev/null +++ b/src/com/android/launcher3/BaseRecyclerViewFastScrollPopup.java @@ -0,0 +1,160 @@ +/* + * 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; + +import android.animation.Animator; +import android.animation.ObjectAnimator; +import android.content.res.Resources; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; + +/** + * The fast scroller popup that shows the section name the list will jump to. + */ +public class BaseRecyclerViewFastScrollPopup { + + private static final float FAST_SCROLL_OVERLAY_Y_OFFSET_FACTOR = 1.5f; + + private Resources mRes; + private BaseRecyclerView mRv; + + private Drawable mBg; + // The absolute bounds of the fast scroller bg + private Rect mBgBounds = new Rect(); + private int mBgOriginalSize; + private Rect mInvalidateRect = new Rect(); + private Rect mTmpRect = new Rect(); + + private String mSectionName; + private Paint mTextPaint; + private Rect mTextBounds = new Rect(); + private float mAlpha; + + private Animator mAlphaAnimator; + private boolean mVisible; + + public BaseRecyclerViewFastScrollPopup(BaseRecyclerView rv, Resources res) { + mRes = res; + mRv = rv; + mBgOriginalSize = res.getDimensionPixelSize(R.dimen.container_fastscroll_popup_size); + mBg = res.getDrawable(R.drawable.container_fastscroll_popup_bg); + mBg.setBounds(0, 0, mBgOriginalSize, mBgOriginalSize); + mTextPaint = new Paint(); + mTextPaint.setColor(Color.WHITE); + mTextPaint.setAntiAlias(true); + mTextPaint.setTextSize(res.getDimensionPixelSize(R.dimen.container_fastscroll_popup_text_size)); + } + + /** + * Sets the section name. + */ + public void setSectionName(String sectionName) { + if (!sectionName.equals(mSectionName)) { + mSectionName = sectionName; + mTextPaint.getTextBounds(sectionName, 0, sectionName.length(), mTextBounds); + // Update the width to use measureText since that is more accurate + mTextBounds.right = (int) (mTextBounds.left + mTextPaint.measureText(sectionName)); + } + } + + /** + * Updates the bounds for the fast scroller. + * @return the invalidation rect for this update. + */ + public Rect updateFastScrollerBounds(BaseRecyclerView rv, int lastTouchY) { + mInvalidateRect.set(mBgBounds); + + if (isVisible()) { + // Calculate the dimensions and position of the fast scroller popup + int edgePadding = rv.getMaxScrollbarWidth(); + int bgPadding = (mBgOriginalSize - mTextBounds.height()) / 2; + int bgHeight = mBgOriginalSize; + int bgWidth = Math.max(mBgOriginalSize, mTextBounds.width() + (2 * bgPadding)); + if (Utilities.isRtl(mRes)) { + mBgBounds.left = rv.getBackgroundPadding().left + (2 * rv.getMaxScrollbarWidth()); + mBgBounds.right = mBgBounds.left + bgWidth; + } else { + mBgBounds.right = rv.getWidth() - rv.getBackgroundPadding().right - + (2 * rv.getMaxScrollbarWidth()); + mBgBounds.left = mBgBounds.right - bgWidth; + } + mBgBounds.top = lastTouchY - (int) (FAST_SCROLL_OVERLAY_Y_OFFSET_FACTOR * bgHeight); + mBgBounds.top = Math.max(edgePadding, + Math.min(mBgBounds.top, rv.getHeight() - edgePadding - bgHeight)); + mBgBounds.bottom = mBgBounds.top + bgHeight; + } else { + mBgBounds.setEmpty(); + } + + // Combine the old and new fast scroller bounds to create the full invalidate rect + mInvalidateRect.union(mBgBounds); + return mInvalidateRect; + } + + /** + * Animates the visibility of the fast scroller popup. + */ + public void animateVisibility(boolean visible) { + if (mVisible != visible) { + mVisible = visible; + if (mAlphaAnimator != null) { + mAlphaAnimator.cancel(); + } + mAlphaAnimator = ObjectAnimator.ofFloat(this, "alpha", visible ? 1f : 0f); + mAlphaAnimator.setDuration(visible ? 200 : 150); + mAlphaAnimator.start(); + } + } + + // Setter/getter for the popup alpha for animations + public void setAlpha(float alpha) { + mAlpha = alpha; + mRv.invalidate(mBgBounds); + } + + public float getAlpha() { + return mAlpha; + } + + public int getHeight() { + return mBgOriginalSize; + } + + public void draw(Canvas c) { + if (isVisible()) { + // Draw the fast scroller popup + int restoreCount = c.save(Canvas.MATRIX_SAVE_FLAG); + c.translate(mBgBounds.left, mBgBounds.top); + mTmpRect.set(mBgBounds); + mTmpRect.offsetTo(0, 0); + mBg.setBounds(mTmpRect); + mBg.setAlpha((int) (mAlpha * 255)); + mBg.draw(c); + mTextPaint.setAlpha((int) (mAlpha * 255)); + c.drawText(mSectionName, (mBgBounds.width() - mTextBounds.width()) / 2, + mBgBounds.height() - (mBgBounds.height() - mTextBounds.height()) / 2, + mTextPaint); + c.restoreToCount(restoreCount); + } + } + + public boolean isVisible() { + return (mAlpha > 0f) && (mSectionName != null); + } +} diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java index 6c13b4a9b..a0be8ea2b 100644 --- a/src/com/android/launcher3/BubbleTextView.java +++ b/src/com/android/launcher3/BubbleTextView.java @@ -16,6 +16,7 @@ package com.android.launcher3; +import android.animation.ObjectAnimator; import android.annotation.TargetApi; import android.content.Context; import android.content.res.ColorStateList; @@ -24,6 +25,7 @@ import android.content.res.Resources.Theme; import android.content.res.TypedArray; import android.graphics.Bitmap; import android.graphics.Canvas; +import android.graphics.Paint; import android.graphics.Region; import android.graphics.drawable.Drawable; import android.os.Build; @@ -34,6 +36,8 @@ import android.view.KeyEvent; import android.view.MotionEvent; import android.view.ViewConfiguration; import android.view.ViewParent; +import android.view.animation.AccelerateInterpolator; +import android.view.animation.DecelerateInterpolator; import android.widget.TextView; import com.android.launcher3.IconCache.IconLoadRequest; import com.android.launcher3.model.PackageItemInfo; @@ -43,7 +47,8 @@ import com.android.launcher3.model.PackageItemInfo; * because we want to make the bubble taller than the text and TextView's clip is * too aggressive. */ -public class BubbleTextView extends TextView { +public class BubbleTextView extends TextView + implements BaseRecyclerViewFastScrollBar.FastScrollFocusableView { private static SparseArray sPreloaderThemes = new SparseArray(2); @@ -56,6 +61,13 @@ public class BubbleTextView extends TextView { private static final int DISPLAY_WORKSPACE = 0; private static final int DISPLAY_ALL_APPS = 1; + private static final float FAST_SCROLL_FOCUS_MAX_SCALE = 1.15f; + private static final int FAST_SCROLL_FOCUS_MODE_NONE = 0; + private static final int FAST_SCROLL_FOCUS_MODE_SCALE_ICON = 1; + private static final int FAST_SCROLL_FOCUS_MODE_DRAW_CIRCLE_BG = 2; + private static final int FAST_SCROLL_FOCUS_FADE_IN_DURATION = 175; + private static final int FAST_SCROLL_FOCUS_FADE_OUT_DURATION = 125; + private final Launcher mLauncher; private Drawable mIcon; private final Drawable mBackground; @@ -79,6 +91,12 @@ public class BubbleTextView extends TextView { private boolean mIgnorePressedStateChange; private boolean mDisableRelayout = false; + private ObjectAnimator mFastScrollFocusAnimator; + private Paint mFastScrollFocusBgPaint; + private float mFastScrollFocusFraction; + private boolean mFastScrollFocused; + private final int mFastScrollMode = FAST_SCROLL_FOCUS_MODE_SCALE_ICON; + private IconLoadRequest mIconLoadRequest; public BubbleTextView(Context context) { @@ -131,6 +149,13 @@ public class BubbleTextView extends TextView { setShadowLayer(SHADOW_LARGE_RADIUS, 0.0f, SHADOW_Y_OFFSET, SHADOW_LARGE_COLOUR); } + if (mFastScrollMode == FAST_SCROLL_FOCUS_MODE_DRAW_CIRCLE_BG) { + mFastScrollFocusBgPaint = new Paint(); + mFastScrollFocusBgPaint.setAntiAlias(true); + mFastScrollFocusBgPaint.setColor( + getResources().getColor(R.color.container_fastscroll_thumb_active_color)); + } + setAccessibilityDelegate(LauncherAppState.getInstance().getAccessibilityDelegate()); } @@ -335,7 +360,18 @@ public class BubbleTextView extends TextView { @Override public void draw(Canvas canvas) { if (!mCustomShadowsEnabled) { + // Draw the fast scroll focus bg if we have one + if (mFastScrollMode == FAST_SCROLL_FOCUS_MODE_DRAW_CIRCLE_BG && + mFastScrollFocusFraction > 0f) { + DeviceProfile grid = mLauncher.getDeviceProfile(); + int iconCenterX = getScrollX() + (getWidth() / 2); + int iconCenterY = getScrollY() + getPaddingTop() + (grid.iconSizePx / 2); + canvas.drawCircle(iconCenterX, iconCenterY, + mFastScrollFocusFraction * (getWidth() / 2), mFastScrollFocusBgPaint); + } + super.draw(canvas); + return; } @@ -538,6 +574,51 @@ public class BubbleTextView extends TextView { } } + // Setters & getters for the animation + public void setFastScrollFocus(float fraction) { + mFastScrollFocusFraction = fraction; + if (mFastScrollMode == FAST_SCROLL_FOCUS_MODE_SCALE_ICON) { + setScaleX(1f + fraction * (FAST_SCROLL_FOCUS_MAX_SCALE - 1f)); + setScaleY(1f + fraction * (FAST_SCROLL_FOCUS_MAX_SCALE - 1f)); + } else { + invalidate(); + } + } + + public float getFastScrollFocus() { + return mFastScrollFocusFraction; + } + + @Override + public void setFastScrollFocused(final boolean focused, boolean animated) { + if (mFastScrollMode == FAST_SCROLL_FOCUS_MODE_NONE) { + return; + } + + if (mFastScrollFocused != focused) { + mFastScrollFocused = focused; + + if (animated) { + // Clean up the previous focus animator + if (mFastScrollFocusAnimator != null) { + mFastScrollFocusAnimator.cancel(); + } + mFastScrollFocusAnimator = ObjectAnimator.ofFloat(this, "fastScrollFocus", + focused ? 1f : 0f); + if (focused) { + mFastScrollFocusAnimator.setInterpolator(new DecelerateInterpolator()); + } else { + mFastScrollFocusAnimator.setInterpolator(new AccelerateInterpolator()); + } + mFastScrollFocusAnimator.setDuration(focused ? + FAST_SCROLL_FOCUS_FADE_IN_DURATION : FAST_SCROLL_FOCUS_FADE_OUT_DURATION); + mFastScrollFocusAnimator.start(); + } else { + mFastScrollFocusFraction = focused ? 1f : 0f; + } + } + } + /** * Interface to be implemented by the grand parent to allow click shadow effect. */ diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java index a75a09af7..77c45407f 100644 --- a/src/com/android/launcher3/Launcher.java +++ b/src/com/android/launcher3/Launcher.java @@ -1125,27 +1125,6 @@ public class Launcher extends Activity public void forceExitFullImmersion(); } - public interface LauncherAppsCallbacks { - /** - * Updates launcher to the available space that AllApps can take so as not to overlap with - * any other views. - */ - @Deprecated - public void onAllAppsBoundsChanged(Rect bounds); - - /** - * Called to dismiss all apps if it is showing. - */ - @Deprecated - public void dismissAllApps(); - - /** - * Sets the search manager to be used for app search. - */ - @Deprecated - public void setSearchManager(Object manager); - } - public interface LauncherSearchCallbacks { /** * Called when the search overlay is shown. diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java index 83d90da97..0b7b1fdc4 100644 --- a/src/com/android/launcher3/LauncherAppState.java +++ b/src/com/android/launcher3/LauncherAppState.java @@ -143,13 +143,6 @@ public class LauncherAppState { return mModel; } - /** - * TODO(winsonc, hyunyoungs): We need to respect this - */ - boolean shouldShowAppOrWidgetProvider(ComponentName componentName) { - return mAppFilter == null || mAppFilter.shouldShowApp(componentName); - } - static void setLauncherProvider(LauncherProvider provider) { sLauncherProvider = new WeakReference(provider); } diff --git a/src/com/android/launcher3/LauncherCallbacks.java b/src/com/android/launcher3/LauncherCallbacks.java index 56db7747c..49d6d68e5 100644 --- a/src/com/android/launcher3/LauncherCallbacks.java +++ b/src/com/android/launcher3/LauncherCallbacks.java @@ -70,10 +70,12 @@ public interface LauncherCallbacks { /* * Extension points for replacing the search experience */ + @Deprecated public boolean forceDisableVoiceButtonProxy(); public boolean providesSearch(); public boolean startSearch(String initialQuery, boolean selectInitialQuery, Bundle appSearchData, Rect sourceBounds); + @Deprecated public void startVoice(); public boolean hasCustomContentToLeft(); public void populateCustomContentContainer(); diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java index 3b06c2093..af118d7f6 100644 --- a/src/com/android/launcher3/Utilities.java +++ b/src/com/android/launcher3/Utilities.java @@ -637,6 +637,25 @@ public final class Utilities { return -fm.top + fm.bottom; } + /** + * Convenience println with multiple args. + */ + public static void println(String key, Object... args) { + StringBuilder b = new StringBuilder(); + b.append(key); + b.append(": "); + boolean isFirstArgument = true; + for (Object arg : args) { + if (isFirstArgument) { + isFirstArgument = false; + } else { + b.append(", "); + } + b.append(arg); + } + System.out.println(b.toString()); + } + @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) public static boolean isRtl(Resources res) { return (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) && 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++) { diff --git a/src/com/android/launcher3/widget/WidgetsContainerView.java b/src/com/android/launcher3/widget/WidgetsContainerView.java index 500311add..5afd7c493 100644 --- a/src/com/android/launcher3/widget/WidgetsContainerView.java +++ b/src/com/android/launcher3/widget/WidgetsContainerView.java @@ -345,9 +345,11 @@ public class WidgetsContainerView extends BaseContainerView InsetDrawable background = new InsetDrawable( getResources().getDrawable(R.drawable.quantum_panel_shape_dark), padding.left, 0, padding.right, 0); + Rect bgPadding = new Rect(); + background.getPadding(bgPadding); mView.setBackground(background); getRevealView().setBackground(background.getConstantState().newDrawable()); - mView.updateBackgroundPadding(padding); + mView.updateBackgroundPadding(bgPadding); } /** diff --git a/src/com/android/launcher3/widget/WidgetsRecyclerView.java b/src/com/android/launcher3/widget/WidgetsRecyclerView.java index fa7e2f0a2..3101f3327 100644 --- a/src/com/android/launcher3/widget/WidgetsRecyclerView.java +++ b/src/com/android/launcher3/widget/WidgetsRecyclerView.java @@ -17,14 +17,14 @@ package com.android.launcher3.widget; import android.content.Context; -import android.graphics.Rect; +import android.graphics.Color; import android.support.v7.widget.LinearLayoutManager; import android.util.AttributeSet; import android.view.View; import com.android.launcher3.BaseRecyclerView; -import com.android.launcher3.Utilities; -import com.android.launcher3.model.WidgetsModel; +import com.android.launcher3.R; import com.android.launcher3.model.PackageItemInfo; +import com.android.launcher3.model.WidgetsModel; /** * The widgets recycler view. @@ -33,6 +33,7 @@ public class WidgetsRecyclerView extends BaseRecyclerView { private static final String TAG = "WidgetsRecyclerView"; private WidgetsModel mWidgets; + private ScrollPositionState mScrollPosState = new ScrollPositionState(); public WidgetsRecyclerView(Context context) { this(context, null); @@ -58,6 +59,14 @@ public class WidgetsRecyclerView extends BaseRecyclerView { addOnItemTouchListener(this); } + public int getFastScrollerTrackColor(int defaultTrackColor) { + return Color.WHITE; + } + + public int getFastScrollerThumbInactiveColor(int defaultInactiveThumbColor) { + return getResources().getColor(R.color.widgets_view_fastscroll_thumb_inactive_color); + } + /** * Sets the widget model in this view, used to determine the fast scroll position. */ @@ -70,15 +79,21 @@ public class WidgetsRecyclerView extends BaseRecyclerView { */ @Override public String scrollToPositionAtProgress(float touchFraction) { - float pos = mWidgets.getPackageSize() * touchFraction; + int rowCount = mWidgets.getPackageSize(); + if (rowCount == 0) { + return ""; + } - int posInt = (int) pos; + // Stop the scroller if it is scrolling + stopScroll(); + + getCurScrollState(mScrollPosState); + float pos = rowCount * touchFraction; + int availableScrollHeight = getAvailableScrollHeight(rowCount, mScrollPosState.rowHeight, 0); LinearLayoutManager layoutManager = ((LinearLayoutManager) getLayoutManager()); - getCurScrollState(scrollPosState); - layoutManager.scrollToPositionWithOffset((int) pos, - (int) (scrollPosState.rowHeight * ((float) posInt - pos))); + layoutManager.scrollToPositionWithOffset(0, (int) -(availableScrollHeight * touchFraction)); - posInt = (int) ((touchFraction == 1)? pos -1 : pos); + int posInt = (int) ((touchFraction == 1)? pos -1 : pos); PackageItemInfo p = mWidgets.getPackageItemInfo(posInt); return p.titleSectionName; } @@ -87,43 +102,23 @@ public class WidgetsRecyclerView extends BaseRecyclerView { * Updates the bounds for the scrollbar. */ @Override - public void updateVerticalScrollbarBounds() { + public void onUpdateScrollbar() { int rowCount = mWidgets.getPackageSize(); - verticalScrollbarBounds.setEmpty(); // Skip early if, there are no items. if (rowCount == 0) { + mScrollbar.setScrollbarThumbOffset(-1, -1); return; } // Skip early if, there no child laid out in the container. - getCurScrollState(scrollPosState); - if (scrollPosState.rowIndex < 0) { + getCurScrollState(mScrollPosState); + if (mScrollPosState.rowIndex < 0) { + mScrollbar.setScrollbarThumbOffset(-1, -1); return; } - int actualHeight = getHeight() - getPaddingTop() - getPaddingBottom(); - int totalScrollHeight = rowCount * scrollPosState.rowHeight; - // Skip early if the height of all the rows are actually less than the container height. - if (totalScrollHeight < actualHeight) { - verticalScrollbarBounds.setEmpty(); - return; - } - - int scrollbarHeight = (int) (actualHeight / ((float) totalScrollHeight / actualHeight)); - int availableY = totalScrollHeight - actualHeight; - int availableScrollY = actualHeight - scrollbarHeight; - int y = (scrollPosState.rowIndex * scrollPosState.rowHeight) - - scrollPosState.rowTopOffset; - y = getPaddingTop() + - (int) (((float) (getPaddingTop() + y) / availableY) * availableScrollY); - - // Calculate the position and size of the scroll bar. - int x = getWidth() - getScrollbarWidth() - mBackgroundPadding.right; - if (Utilities.isRtl(getResources())) { - x = mBackgroundPadding.left; - } - verticalScrollbarBounds.set(x, y, x + getScrollbarWidth(), y + scrollbarHeight); + synchronizeScrollBarThumbOffsetToViewScroll(mScrollPosState, rowCount, 0); } /** -- cgit v1.2.3