summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorWinson Chung <winsonc@google.com>2015-06-16 13:35:04 -0700
committerWinson Chung <winsonc@google.com>2015-06-23 14:24:15 -0700
commitb1777447d9b9700b48f8060f8b318f2363c43e8d (patch)
treea5b94ed9b83316765b73265b4c030e40ed0bf4c9
parente5106b687f6978bb8bb11ec90d7e8924a1b9e795 (diff)
downloadandroid_packages_apps_Trebuchet-b1777447d9b9700b48f8060f8b318f2363c43e8d.tar.gz
android_packages_apps_Trebuchet-b1777447d9b9700b48f8060f8b318f2363c43e8d.tar.bz2
android_packages_apps_Trebuchet-b1777447d9b9700b48f8060f8b318f2363c43e8d.zip
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
-rw-r--r--proguard.flags48
-rw-r--r--res/drawable-ldrtl/all_apps_fastscroll_bg.xml2
-rw-r--r--res/drawable/all_apps_scrollbar_thumb.xml21
-rw-r--r--res/drawable/container_fastscroll_popup_bg.xml (renamed from res/drawable/all_apps_fastscroll_bg.xml)2
-rw-r--r--res/layout/all_apps_container.xml4
-rw-r--r--res/layout/all_apps_search_bar.xml4
-rw-r--r--res/values-sw720dp/dimens.xml2
-rw-r--r--res/values/colors.xml6
-rw-r--r--res/values/dimens.xml24
-rw-r--r--src/com/android/launcher3/BaseRecyclerView.java289
-rw-r--r--src/com/android/launcher3/BaseRecyclerViewFastScrollBar.java232
-rw-r--r--src/com/android/launcher3/BaseRecyclerViewFastScrollPopup.java160
-rw-r--r--src/com/android/launcher3/BubbleTextView.java83
-rw-r--r--src/com/android/launcher3/Launcher.java21
-rw-r--r--src/com/android/launcher3/LauncherAppState.java7
-rw-r--r--src/com/android/launcher3/LauncherCallbacks.java2
-rw-r--r--src/com/android/launcher3/Utilities.java19
-rw-r--r--src/com/android/launcher3/allapps/AllAppsContainerView.java32
-rw-r--r--src/com/android/launcher3/allapps/AllAppsGridAdapter.java2
-rw-r--r--src/com/android/launcher3/allapps/AllAppsRecyclerView.java248
-rw-r--r--src/com/android/launcher3/allapps/AlphabeticalAppsList.java76
-rw-r--r--src/com/android/launcher3/widget/WidgetsContainerView.java4
-rw-r--r--src/com/android/launcher3/widget/WidgetsRecyclerView.java65
23 files changed, 902 insertions, 451 deletions
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 @@
-->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
- <solid android:color="@color/all_apps_scrollbar_thumb_color" />
+ <solid android:color="@color/container_fastscroll_thumb_active_color" />
<size
android:width="64dp"
android:height="64dp" />
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 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- 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.
--->
-<shape xmlns:android="http://schemas.android.com/apk/res/android"
- android:shape="rectangle">
- <solid android:color="@color/all_apps_scrollbar_thumb_color" />
- <size android:width="@dimen/all_apps_fast_scroll_bar_width" />
-</shape> \ No newline at end of file
diff --git a/res/drawable/all_apps_fastscroll_bg.xml b/res/drawable/container_fastscroll_popup_bg.xml
index 6b7448459..2ef07ab96 100644
--- a/res/drawable/all_apps_fastscroll_bg.xml
+++ b/res/drawable/container_fastscroll_popup_bg.xml
@@ -16,7 +16,7 @@
-->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
- <solid android:color="@color/all_apps_scrollbar_thumb_color" />
+ <solid android:color="@color/container_fastscroll_thumb_active_color" />
<size
android:width="64dp"
android:height="64dp" />
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 @@
<resources>
<!-- All Apps -->
<dimen name="all_apps_search_bar_height">54dp</dimen>
- <dimen name="all_apps_icon_top_bottom_padding">16dp</dimen>
+ <dimen name="all_apps_icon_top_bottom_padding">14dp</dimen>
<!-- QSB -->
<dimen name="toolbar_button_vertical_padding">8dip</dimen>
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 @@
<color name="outline_color">#FFFFFFFF</color>
<color name="widget_text_panel">#FF374248</color>
+ <!-- Containers -->
+ <color name="container_fastscroll_thumb_inactive_color">#42000000</color>
+ <color name="container_fastscroll_thumb_active_color">#009688</color>
+
<!-- All Apps -->
- <color name="all_apps_scrollbar_thumb_color">#009688</color>
<color name="all_apps_grid_section_text_color">#009688</color>
<!-- Widgets view -->
+ <color name="widgets_view_fastscroll_thumb_inactive_color">#42FFFFFF</color>
<color name="widgets_view_section_text_color">#FFFFFF</color>
<color name="widgets_view_item_text_color">#C4C4C4</color>
<color name="widgets_cell_color">#263238</color>
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 @@
<dimen name="toolbar_button_vertical_padding">4dip</dimen>
<dimen name="toolbar_button_horizontal_padding">12dip</dimen>
-<!-- All Apps -->
+<!-- Container -->
<!-- Note: This needs to match the fixed insets for the search box. -->
<dimen name="container_bounds_inset">8dp</dimen>
<!-- Notes: container_bounds_inset - quantum_panel_outer_padding -->
<dimen name="container_bounds_minus_quantum_panel_padding_inset">4dp</dimen>
+ <dimen name="container_fastscroll_thumb_min_width">4dp</dimen>
+ <dimen name="container_fastscroll_thumb_max_width">8dp</dimen>
+ <dimen name="container_fastscroll_thumb_height">64dp</dimen>
+ <dimen name="container_fastscroll_thumb_touch_inset">-24dp</dimen>
+ <dimen name="container_fastscroll_popup_size">72dp</dimen>
+ <dimen name="container_fastscroll_popup_text_size">48dp</dimen>
+
+<!-- All Apps -->
<dimen name="all_apps_grid_view_start_margin">0dp</dimen>
<dimen name="all_apps_grid_section_y_offset">8dp</dimen>
<dimen name="all_apps_grid_section_text_size">24sp</dimen>
@@ -62,16 +70,10 @@
<dimen name="all_apps_search_bar_prediction_bar_padding">8dp</dimen>
<dimen name="all_apps_icon_top_bottom_padding">8dp</dimen>
<dimen name="all_apps_icon_width_gap">24dp</dimen>
- <dimen name="all_apps_prediction_bar_top_bottom_padding">16dp</dimen>
-
- <dimen name="all_apps_fast_scroll_bar_width">4dp</dimen>
- <dimen name="all_apps_fast_scroll_scrubber_touch_inset">-24dp</dimen>
- <dimen name="all_apps_fast_scroll_popup_size">72dp</dimen>
- <dimen name="all_apps_fast_scroll_text_size">48dp</dimen>
-
- <dimen name="all_apps_header_max_elevation">4dp</dimen>
- <dimen name="all_apps_header_scroll_to_elevation">16dp</dimen>
- <dimen name="all_apps_header_shadow_height">6dp</dimen>
+ <!-- The top padding should account for the general all_apps_list_top_bottom_padding -->
+ <dimen name="all_apps_prediction_bar_top_padding">0dp</dimen>
+ <dimen name="all_apps_prediction_bar_bottom_padding">16dp</dimen>
+ <dimen name="all_apps_list_top_bottom_padding">8dp</dimen>
<!-- Widget tray -->
<dimen name="widget_container_inset">8dp</dimen>
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:
* <ul>
@@ -41,7 +32,7 @@ import com.android.launcher3.util.Thunk;
* <li> Enable fast scroller.
* </ul>
*/
-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.
- * <p>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.
- * <p>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.
+ * <p>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.
+ * <p>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.
+ * <p>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<Theme> sPreloaderThemes = new SparseArray<Theme>(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<LauncherProvider>(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<AllAppsGridAdapter.ViewHol
mPredictedAppsDividerPaint.setColor(0x1E000000);
mPredictedAppsDividerPaint.setAntiAlias(true);
mPredictionBarBottomPadding =
- res.getDimensionPixelSize(R.dimen.all_apps_prediction_bar_top_bottom_padding);
+ res.getDimensionPixelSize(R.dimen.all_apps_prediction_bar_bottom_padding);
}
/**
diff --git a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
index ff327dae3..a17f0e35d 100644
--- a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
@@ -22,10 +22,10 @@ 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.Launcher;
import com.android.launcher3.Stats;
-import com.android.launcher3.Utilities;
import java.util.List;
@@ -35,13 +35,25 @@ 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 int mNumAppsPerRow;
- private int mNumPredictedAppsPerRow;
private int mPredictionBarHeight;
- private int mLastFastscrollPosition = -1;
+
+ private BaseRecyclerViewFastScrollBar.FastScrollFocusableView mLastFastScrollFocusedView;
+ private int mPrevFastScrollFocusedPosition;
+ private int mFastScrollFrameIndex;
+ private 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 Launcher mLauncher;
+ private ScrollPositionState mScrollPosState = new ScrollPositionState();
public AllAppsRecyclerView(Context context) {
this(context, null);
@@ -59,6 +71,7 @@ public class AllAppsRecyclerView extends BaseRecyclerView
int defStyleRes) {
super(context, attrs, defStyleAttr);
mLauncher = (Launcher) context;
+ setOverScrollMode(View.OVER_SCROLL_NEVER);
}
/**
@@ -71,9 +84,8 @@ public class AllAppsRecyclerView extends BaseRecyclerView
/**
* Sets the number of apps per row in this recycler view.
*/
- public void setNumAppsPerRow(int numAppsPerRow, int numPredictedAppsPerRow) {
+ public void setNumAppsPerRow(int numAppsPerRow) {
mNumAppsPerRow = numAppsPerRow;
- mNumPredictedAppsPerRow = numPredictedAppsPerRow;
DeviceProfile grid = mLauncher.getDeviceProfile();
RecyclerView.RecycledViewPool pool = getRecycledViewPool();
@@ -103,11 +115,12 @@ public class AllAppsRecyclerView extends BaseRecyclerView
*/
public int getScrollPosition() {
List<AlphabeticalAppsList.AdapterItem> 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<AlphabeticalAppsList.FastScrollSectionInfo> 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<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;
}
+ } 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<AlphabeticalAppsList.SectionInfo> 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<AlphabeticalAppsList.SectionInfo> 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<AlphabeticalAppsList.AdapterItem> 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<AlphabeticalAppsList.AdapterItem> 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;
@@ -241,6 +244,13 @@ public class AlphabeticalAppsList {
}
/**
+ * Returns the number of rows of applications (not including predictions)
+ */
+ public int getNumAppRows() {
+ return mNumAppRowsInAdapter;
+ }
+
+ /**
* Returns whether there are is a filter set.
*/
public boolean hasFilter() {
@@ -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<AppInfo> 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);
}
/**