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
This commit is contained in:
Winson Chung 2015-06-16 13:35:04 -07:00
parent e5106b687f
commit b1777447d9
23 changed files with 933 additions and 482 deletions

View File

@ -1,19 +1,29 @@
-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 {
public void setWidth(int);
public int getWidth();
public void setHeight(int);
public int getHeight();
public void setX(int);
public int getX();
public void setY(int);
public int getY();
}
-keep class com.android.launcher3.CellLayout$LayoutParams {
public void setWidth(int);
public int getWidth();
@ -25,9 +35,20 @@
public int getY();
}
-keep class com.android.launcher3.Workspace {
public float getBackgroundAlpha();
public void setBackgroundAlpha(float);
-keep class com.android.launcher3.DragLayer$LayoutParams {
public void setWidth(int);
public int getWidth();
public void setHeight(int);
public int getHeight();
public void setX(int);
public int getX();
public void setY(int);
public int getY();
}
-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);
}

View File

@ -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" />

View File

@ -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>

View File

@ -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" />

View File

@ -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"

View File

@ -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"

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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);
}
public Rect getBackgroundPadding() {
return mBackgroundPadding;
}
/**
* Returns the scroll bar width when the user is scrolling.
*/
public int getMaxScrollbarWidth() {
return mScrollbar.getThumbMaxWidth();
}
/**
* 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.
*/
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 available scroll bar height:
* AvailableScrollBarHeight = Total height of the visible view - thumb height
*/
protected int getAvailableScrollBarHeight() {
int visibleHeight = getHeight() - mBackgroundPadding.top - mBackgroundPadding.bottom;
int availableScrollBarHeight = visibleHeight - mScrollbar.getThumbHeight();
return availableScrollBarHeight;
}
/**
* Returns the track color (ignoring alpha), can be overridden by each subclass.
*/
public int getFastScrollerTrackColor(int defaultTrackColor) {
return defaultTrackColor;
}
/**
* Returns the inactive thumb color, can be overridden by each subclass.
*/
public int getFastScrollerThumbInactiveColor(int defaultInactiveThumbColor) {
return defaultInactiveThumbColor;
}
@Override
protected void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
drawVerticalScrubber(canvas);
drawFastScrollerPopup(canvas);
}
/**
* Draws the vertical scrollbar.
*/
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());
onUpdateScrollbar();
mScrollbar.draw(canvas);
canvas.restoreToCount(restoreCount);
}
/**
* Draws 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 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 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;
}
}
/**
* Returns the scroll bar width.
*/
public int getScrollbarWidth() {
return mScrollbarWidth;
}
// 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);
/**
* Sets the fast scroller alpha.
*/
public void setFastScrollerAlpha(float alpha) {
mFastScrollAlpha = alpha;
invalidateFastScroller(mFastScrollerBounds);
}
/**
* Returns the fast scroller alpha.
*/
public float getFastScrollerAlpha() {
return mFastScrollAlpha;
// 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);
}
/**
* 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;
}
public abstract String scrollToPositionAtProgress(float touchFraction);
/**
* Updates the bounds for the scrollbar.
* <p>Override in each subclass of this base class.
*/
public void updateVerticalScrollbarBounds() {};
public abstract void onUpdateScrollbar();
/**
* Animates the visibility of the fast scroller popup.
* <p>Override in each subclass of this base class.
*/
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();
}
}
@Override
public void onAnimationEnd(Animator animation) {
if (!visible) {
onFastScrollingEnd();
}
}
});
anim.start();
}
/**
* To be overridden by subclasses.
*/
protected void onFastScrollingStart() {}
/**
* To be overridden by subclasses.
*/
protected void onFastScrollingEnd() {}
/**
* Invalidates the fast scroller popup.
*/
protected void invalidateFastScroller(Rect bounds) {
invalidate(bounds.left, bounds.top, bounds.right, bounds.bottom);
}
/**
* Returns whether a given point is near the scrollbar.
*/
private boolean isPointNearScrollbar(int x, int y) {
// Check if we are scrolling
updateVerticalScrollbarBounds();
verticalScrollbarBounds.inset(mScrollbarInset, mScrollbarInset);
return verticalScrollbarBounds.contains(x, y);
}
/**
* Updates the bounds for the fast scroller.
*/
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() {}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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.
*/

View File

@ -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.

View File

@ -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);
}

View File

@ -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();

View File

@ -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) &&

View File

@ -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();
}

View File

@ -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);
}
/**

View File

@ -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");
}
// 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));
}
if (mPrevFastScrollFocusedPosition != lastInfo.fastScrollToItem.position) {
mPrevFastScrollFocusedPosition = lastInfo.fastScrollToItem.position;
// Reset the last focused view
if (mLastFastScrollFocusedView != null) {
mLastFastScrollFocusedView.setFastScrollFocused(false, true);
mLastFastScrollFocusedView = null;
}
if (mFastScrollMode == FAST_SCROLL_MODE_JUMP_TO_FIRST_ICON) {
smoothSnapToPosition(mPrevFastScrollFocusedPosition, mScrollPosState);
} else if (mFastScrollMode == FAST_SCROLL_MODE_FREE_SCROLL) {
final ViewHolder vh = findViewHolderForPosition(mPrevFastScrollFocusedPosition);
if (vh != null &&
vh.itemView instanceof BaseRecyclerViewFastScrollBar.FastScrollFocusableView) {
mLastFastScrollFocusedView =
(BaseRecyclerViewFastScrollBar.FastScrollFocusableView) vh.itemView;
mLastFastScrollFocusedView.setFastScrollFocused(true, true);
}
} else {
throw new RuntimeException("Unexpected fast scroll mode");
}
}
// 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;
}
// 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);
}
return lastScrollSection.sectionName;
return lastInfo.sectionName;
}
/**
* 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);
}
appIndex += info.numApps;
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 appIndex;
mPrevFastScrollFocusedPosition = -1;
}
/**
* 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;
}
return rowCount;
}
/**
* 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 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();
}
// 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;
}
int rowCount = mApps.getNumAppRows();
getCurScrollState(mScrollPosState, items);
if (mScrollPosState.rowIndex < 0) {
mScrollbar.setScrollbarThumbOffset(-1, -1);
return;
}
verticalScrollbarBounds.setEmpty();
int predictionBarHeight = mApps.getPredictedApps().isEmpty() ? 0 : mPredictionBarHeight;
synchronizeScrollBarThumbOffsetToViewScroll(mScrollPosState, rowCount, predictionBarHeight);
}
/**
* Returns the current scroll state.
* 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);
}
}
}
};
/**
* 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 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;
}
}
}

View File

@ -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<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++) {

View File

@ -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);
}
/**

View File

@ -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);
}
/**