mirror of
https://github.com/LawnchairLauncher/lawnchair.git
synced 2026-02-27 15:26:58 +00:00
am c0880491: Highlighting sectioned apps on fast-scroll.
* commit 'c088049113c261331b5685e64050d14a31cd72df': Highlighting sectioned apps on fast-scroll.
This commit is contained in:
@@ -19,11 +19,6 @@
|
|||||||
public float getAlpha();
|
public float getAlpha();
|
||||||
}
|
}
|
||||||
|
|
||||||
-keep class com.android.launcher3.BubbleTextView {
|
|
||||||
public void setFastScrollFocus(float);
|
|
||||||
public float getFastScrollFocus();
|
|
||||||
}
|
|
||||||
|
|
||||||
-keep class com.android.launcher3.ButtonDropTarget {
|
-keep class com.android.launcher3.ButtonDropTarget {
|
||||||
public int getTextColor();
|
public int getTextColor();
|
||||||
}
|
}
|
||||||
@@ -56,8 +51,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
-keep class com.android.launcher3.FastBitmapDrawable {
|
-keep class com.android.launcher3.FastBitmapDrawable {
|
||||||
public int getBrightness();
|
public void setDesaturation(float);
|
||||||
public void setBrightness(int);
|
public float getDesaturation();
|
||||||
|
public void setBrightness(float);
|
||||||
|
public float getBrightness();
|
||||||
}
|
}
|
||||||
|
|
||||||
-keep class com.android.launcher3.MemoryDumpActivity {
|
-keep class com.android.launcher3.MemoryDumpActivity {
|
||||||
|
|||||||
@@ -52,8 +52,8 @@ public abstract class BaseRecyclerView extends RecyclerView
|
|||||||
public int rowIndex;
|
public int rowIndex;
|
||||||
// The offset of the first visible row
|
// The offset of the first visible row
|
||||||
public int rowTopOffset;
|
public int rowTopOffset;
|
||||||
// The height of a given row (they are currently all the same height)
|
// The adapter position of the first visible item
|
||||||
public int rowHeight;
|
public int itemPos;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected BaseRecyclerViewFastScrollBar mScrollbar;
|
protected BaseRecyclerViewFastScrollBar mScrollbar;
|
||||||
@@ -186,16 +186,22 @@ public abstract class BaseRecyclerView extends RecyclerView
|
|||||||
return mScrollbar.getThumbMaxWidth();
|
return mScrollbar.getThumbMaxWidth();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the visible height of the recycler view:
|
||||||
|
* VisibleHeight = View height - top padding - bottom padding
|
||||||
|
*/
|
||||||
|
protected int getVisibleHeight() {
|
||||||
|
int visibleHeight = getHeight() - mBackgroundPadding.top - mBackgroundPadding.bottom;
|
||||||
|
return visibleHeight;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the available scroll height:
|
* Returns the available scroll height:
|
||||||
* AvailableScrollHeight = Total height of the all items - last page height
|
* AvailableScrollHeight = Total height of the all items - last page height
|
||||||
*
|
|
||||||
* This assumes that all rows are the same height.
|
|
||||||
*/
|
*/
|
||||||
protected int getAvailableScrollHeight(int rowCount, int rowHeight) {
|
protected int getAvailableScrollHeight(int rowCount) {
|
||||||
int visibleHeight = getHeight() - mBackgroundPadding.top - mBackgroundPadding.bottom;
|
int totalHeight = getPaddingTop() + getTop(rowCount) + getPaddingBottom();
|
||||||
int scrollHeight = getPaddingTop() + rowCount * rowHeight + getPaddingBottom();
|
int availableScrollHeight = totalHeight - getVisibleHeight();
|
||||||
int availableScrollHeight = scrollHeight - visibleHeight;
|
|
||||||
return availableScrollHeight;
|
return availableScrollHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -204,8 +210,7 @@ public abstract class BaseRecyclerView extends RecyclerView
|
|||||||
* AvailableScrollBarHeight = Total height of the visible view - thumb height
|
* AvailableScrollBarHeight = Total height of the visible view - thumb height
|
||||||
*/
|
*/
|
||||||
protected int getAvailableScrollBarHeight() {
|
protected int getAvailableScrollBarHeight() {
|
||||||
int visibleHeight = getHeight() - mBackgroundPadding.top - mBackgroundPadding.bottom;
|
int availableScrollBarHeight = getVisibleHeight() - mScrollbar.getThumbHeight();
|
||||||
int availableScrollBarHeight = visibleHeight - mScrollbar.getThumbHeight();
|
|
||||||
return availableScrollBarHeight;
|
return availableScrollBarHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -223,6 +228,13 @@ public abstract class BaseRecyclerView extends RecyclerView
|
|||||||
return defaultInactiveThumbColor;
|
return defaultInactiveThumbColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the scrollbar for this recycler view.
|
||||||
|
*/
|
||||||
|
public BaseRecyclerViewFastScrollBar getScrollBar() {
|
||||||
|
return mScrollbar;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void dispatchDraw(Canvas canvas) {
|
protected void dispatchDraw(Canvas canvas) {
|
||||||
super.dispatchDraw(canvas);
|
super.dispatchDraw(canvas);
|
||||||
@@ -243,7 +255,7 @@ public abstract class BaseRecyclerView extends RecyclerView
|
|||||||
int rowCount) {
|
int rowCount) {
|
||||||
// Only show the scrollbar if there is height to be scrolled
|
// Only show the scrollbar if there is height to be scrolled
|
||||||
int availableScrollBarHeight = getAvailableScrollBarHeight();
|
int availableScrollBarHeight = getAvailableScrollBarHeight();
|
||||||
int availableScrollHeight = getAvailableScrollHeight(rowCount, scrollPosState.rowHeight);
|
int availableScrollHeight = getAvailableScrollHeight(rowCount);
|
||||||
if (availableScrollHeight <= 0) {
|
if (availableScrollHeight <= 0) {
|
||||||
mScrollbar.setThumbOffset(-1, -1);
|
mScrollbar.setThumbOffset(-1, -1);
|
||||||
return;
|
return;
|
||||||
@@ -252,8 +264,7 @@ public abstract class BaseRecyclerView extends RecyclerView
|
|||||||
// Calculate the current scroll position, the scrollY of the recycler view accounts for the
|
// 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
|
// view padding, while the scrollBarY is drawn right up to the background padding (ignoring
|
||||||
// padding)
|
// padding)
|
||||||
int scrollY = getPaddingTop() +
|
int scrollY = getScrollTop(scrollPosState);
|
||||||
(scrollPosState.rowIndex * scrollPosState.rowHeight) - scrollPosState.rowTopOffset;
|
|
||||||
int scrollBarY = mBackgroundPadding.top +
|
int scrollBarY = mBackgroundPadding.top +
|
||||||
(int) (((float) scrollY / availableScrollHeight) * availableScrollBarHeight);
|
(int) (((float) scrollY / availableScrollHeight) * availableScrollBarHeight);
|
||||||
|
|
||||||
@@ -268,7 +279,7 @@ public abstract class BaseRecyclerView extends RecyclerView
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns whether fast scrolling is supported in the current state.
|
* @return whether fast scrolling is supported in the current state.
|
||||||
*/
|
*/
|
||||||
protected boolean supportsFastScrolling() {
|
protected boolean supportsFastScrolling() {
|
||||||
return true;
|
return true;
|
||||||
@@ -277,22 +288,38 @@ public abstract class BaseRecyclerView extends RecyclerView
|
|||||||
/**
|
/**
|
||||||
* Maps the touch (from 0..1) to the adapter position that should be visible.
|
* Maps the touch (from 0..1) to the adapter position that should be visible.
|
||||||
* <p>Override in each subclass of this base class.
|
* <p>Override in each subclass of this base class.
|
||||||
|
*
|
||||||
|
* @return the scroll top of this recycler view.
|
||||||
*/
|
*/
|
||||||
public abstract String scrollToPositionAtProgress(float touchFraction);
|
protected int getScrollTop(ScrollPositionState scrollPosState) {
|
||||||
|
return getPaddingTop() + getTop(scrollPosState.rowIndex) -
|
||||||
|
scrollPosState.rowTopOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns information about the item that the recycler view is currently scrolled to.
|
||||||
|
*/
|
||||||
|
protected abstract void getCurScrollState(ScrollPositionState stateOut, int viewTypeMask);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the top (or y position) of the row at the specified index.
|
||||||
|
*/
|
||||||
|
protected abstract int getTop(int rowIndex);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps the touch (from 0..1) to the adapter position that should be visible.
|
||||||
|
* <p>Override in each subclass of this base class.
|
||||||
|
*/
|
||||||
|
protected abstract String scrollToPositionAtProgress(float touchFraction);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates the bounds for the scrollbar.
|
* Updates the bounds for the scrollbar.
|
||||||
* <p>Override in each subclass of this base class.
|
* <p>Override in each subclass of this base class.
|
||||||
*/
|
*/
|
||||||
public abstract void onUpdateScrollbar(int dy);
|
protected abstract void onUpdateScrollbar(int dy);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <p>Override in each subclass of this base class.
|
* <p>Override in each subclass of this base class.
|
||||||
*/
|
*/
|
||||||
public void onFastScrollCompleted() {}
|
protected void onFastScrollCompleted() {}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns information about the item that the recycler view is currently scrolled to.
|
|
||||||
*/
|
|
||||||
protected abstract void getCurScrollState(ScrollPositionState stateOut);
|
|
||||||
}
|
}
|
||||||
@@ -27,6 +27,7 @@ import android.graphics.Path;
|
|||||||
import android.graphics.Point;
|
import android.graphics.Point;
|
||||||
import android.graphics.Rect;
|
import android.graphics.Rect;
|
||||||
import android.view.MotionEvent;
|
import android.view.MotionEvent;
|
||||||
|
import android.view.VelocityTracker;
|
||||||
import android.view.ViewConfiguration;
|
import android.view.ViewConfiguration;
|
||||||
|
|
||||||
import com.android.launcher3.util.Thunk;
|
import com.android.launcher3.util.Thunk;
|
||||||
@@ -37,7 +38,7 @@ import com.android.launcher3.util.Thunk;
|
|||||||
public class BaseRecyclerViewFastScrollBar {
|
public class BaseRecyclerViewFastScrollBar {
|
||||||
|
|
||||||
public interface FastScrollFocusableView {
|
public interface FastScrollFocusableView {
|
||||||
void setFastScrollFocused(boolean focused, boolean animated);
|
void setFastScrollFocusState(final FastBitmapDrawable.State focusState, boolean animated);
|
||||||
}
|
}
|
||||||
|
|
||||||
private final static int MAX_TRACK_ALPHA = 30;
|
private final static int MAX_TRACK_ALPHA = 30;
|
||||||
@@ -199,7 +200,7 @@ public class BaseRecyclerViewFastScrollBar {
|
|||||||
}
|
}
|
||||||
mTouchOffset += (lastY - downY);
|
mTouchOffset += (lastY - downY);
|
||||||
mPopup.animateVisibility(true);
|
mPopup.animateVisibility(true);
|
||||||
animateScrollbar(true);
|
showActiveScrollbar(true);
|
||||||
}
|
}
|
||||||
if (mIsDragging) {
|
if (mIsDragging) {
|
||||||
// Update the fastscroller section name at this touch position
|
// Update the fastscroller section name at this touch position
|
||||||
@@ -210,7 +211,7 @@ public class BaseRecyclerViewFastScrollBar {
|
|||||||
(bottom - top));
|
(bottom - top));
|
||||||
mPopup.setSectionName(sectionName);
|
mPopup.setSectionName(sectionName);
|
||||||
mPopup.animateVisibility(!sectionName.isEmpty());
|
mPopup.animateVisibility(!sectionName.isEmpty());
|
||||||
mRv.invalidate(mPopup.updateFastScrollerBounds(mRv, lastY));
|
mRv.invalidate(mPopup.updateFastScrollerBounds(lastY));
|
||||||
mLastTouchY = boundedY;
|
mLastTouchY = boundedY;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -222,7 +223,7 @@ public class BaseRecyclerViewFastScrollBar {
|
|||||||
if (mIsDragging) {
|
if (mIsDragging) {
|
||||||
mIsDragging = false;
|
mIsDragging = false;
|
||||||
mPopup.animateVisibility(false);
|
mPopup.animateVisibility(false);
|
||||||
animateScrollbar(false);
|
showActiveScrollbar(false);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -246,7 +247,7 @@ public class BaseRecyclerViewFastScrollBar {
|
|||||||
/**
|
/**
|
||||||
* Animates the width and color of the scrollbar.
|
* Animates the width and color of the scrollbar.
|
||||||
*/
|
*/
|
||||||
private void animateScrollbar(boolean isScrolling) {
|
private void showActiveScrollbar(boolean isScrolling) {
|
||||||
if (mScrollbarAnimator != null) {
|
if (mScrollbarAnimator != null) {
|
||||||
mScrollbarAnimator.cancel();
|
mScrollbarAnimator.cancel();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -77,26 +77,26 @@ public class BaseRecyclerViewFastScrollPopup {
|
|||||||
* Updates the bounds for the fast scroller.
|
* Updates the bounds for the fast scroller.
|
||||||
* @return the invalidation rect for this update.
|
* @return the invalidation rect for this update.
|
||||||
*/
|
*/
|
||||||
public Rect updateFastScrollerBounds(BaseRecyclerView rv, int lastTouchY) {
|
public Rect updateFastScrollerBounds(int lastTouchY) {
|
||||||
mInvalidateRect.set(mBgBounds);
|
mInvalidateRect.set(mBgBounds);
|
||||||
|
|
||||||
if (isVisible()) {
|
if (isVisible()) {
|
||||||
// Calculate the dimensions and position of the fast scroller popup
|
// Calculate the dimensions and position of the fast scroller popup
|
||||||
int edgePadding = rv.getMaxScrollbarWidth();
|
int edgePadding = mRv.getMaxScrollbarWidth();
|
||||||
int bgPadding = (mBgOriginalSize - mTextBounds.height()) / 2;
|
int bgPadding = (mBgOriginalSize - mTextBounds.height()) / 2;
|
||||||
int bgHeight = mBgOriginalSize;
|
int bgHeight = mBgOriginalSize;
|
||||||
int bgWidth = Math.max(mBgOriginalSize, mTextBounds.width() + (2 * bgPadding));
|
int bgWidth = Math.max(mBgOriginalSize, mTextBounds.width() + (2 * bgPadding));
|
||||||
if (Utilities.isRtl(mRes)) {
|
if (Utilities.isRtl(mRes)) {
|
||||||
mBgBounds.left = rv.getBackgroundPadding().left + (2 * rv.getMaxScrollbarWidth());
|
mBgBounds.left = mRv.getBackgroundPadding().left + (2 * mRv.getMaxScrollbarWidth());
|
||||||
mBgBounds.right = mBgBounds.left + bgWidth;
|
mBgBounds.right = mBgBounds.left + bgWidth;
|
||||||
} else {
|
} else {
|
||||||
mBgBounds.right = rv.getWidth() - rv.getBackgroundPadding().right -
|
mBgBounds.right = mRv.getWidth() - mRv.getBackgroundPadding().right -
|
||||||
(2 * rv.getMaxScrollbarWidth());
|
(2 * mRv.getMaxScrollbarWidth());
|
||||||
mBgBounds.left = mBgBounds.right - bgWidth;
|
mBgBounds.left = mBgBounds.right - bgWidth;
|
||||||
}
|
}
|
||||||
mBgBounds.top = lastTouchY - (int) (FAST_SCROLL_OVERLAY_Y_OFFSET_FACTOR * bgHeight);
|
mBgBounds.top = lastTouchY - (int) (FAST_SCROLL_OVERLAY_Y_OFFSET_FACTOR * bgHeight);
|
||||||
mBgBounds.top = Math.max(edgePadding,
|
mBgBounds.top = Math.max(edgePadding,
|
||||||
Math.min(mBgBounds.top, rv.getHeight() - edgePadding - bgHeight));
|
Math.min(mBgBounds.top, mRv.getHeight() - edgePadding - bgHeight));
|
||||||
mBgBounds.bottom = mBgBounds.top + bgHeight;
|
mBgBounds.bottom = mBgBounds.top + bgHeight;
|
||||||
} else {
|
} else {
|
||||||
mBgBounds.setEmpty();
|
mBgBounds.setEmpty();
|
||||||
|
|||||||
@@ -16,7 +16,6 @@
|
|||||||
|
|
||||||
package com.android.launcher3;
|
package com.android.launcher3;
|
||||||
|
|
||||||
import android.animation.ObjectAnimator;
|
|
||||||
import android.annotation.TargetApi;
|
import android.annotation.TargetApi;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.res.ColorStateList;
|
import android.content.res.ColorStateList;
|
||||||
@@ -25,7 +24,6 @@ import android.content.res.Resources.Theme;
|
|||||||
import android.content.res.TypedArray;
|
import android.content.res.TypedArray;
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
import android.graphics.Canvas;
|
import android.graphics.Canvas;
|
||||||
import android.graphics.Paint;
|
|
||||||
import android.graphics.Region;
|
import android.graphics.Region;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
@@ -37,8 +35,6 @@ import android.view.MotionEvent;
|
|||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewConfiguration;
|
import android.view.ViewConfiguration;
|
||||||
import android.view.ViewParent;
|
import android.view.ViewParent;
|
||||||
import android.view.animation.AccelerateInterpolator;
|
|
||||||
import android.view.animation.DecelerateInterpolator;
|
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import com.android.launcher3.IconCache.IconLoadRequest;
|
import com.android.launcher3.IconCache.IconLoadRequest;
|
||||||
@@ -63,13 +59,6 @@ public class BubbleTextView extends TextView
|
|||||||
private static final int DISPLAY_WORKSPACE = 0;
|
private static final int DISPLAY_WORKSPACE = 0;
|
||||||
private static final int DISPLAY_ALL_APPS = 1;
|
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 final Launcher mLauncher;
|
||||||
private Drawable mIcon;
|
private Drawable mIcon;
|
||||||
private final Drawable mBackground;
|
private final Drawable mBackground;
|
||||||
@@ -93,12 +82,6 @@ public class BubbleTextView extends TextView
|
|||||||
private boolean mIgnorePressedStateChange;
|
private boolean mIgnorePressedStateChange;
|
||||||
private boolean mDisableRelayout = false;
|
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;
|
private IconLoadRequest mIconLoadRequest;
|
||||||
|
|
||||||
public BubbleTextView(Context context) {
|
public BubbleTextView(Context context) {
|
||||||
@@ -151,13 +134,6 @@ public class BubbleTextView extends TextView
|
|||||||
setShadowLayer(SHADOW_LARGE_RADIUS, 0.0f, SHADOW_Y_OFFSET, SHADOW_LARGE_COLOUR);
|
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());
|
setAccessibilityDelegate(LauncherAppState.getInstance().getAccessibilityDelegate());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -170,8 +146,9 @@ public class BubbleTextView extends TextView
|
|||||||
Bitmap b = info.getIcon(iconCache);
|
Bitmap b = info.getIcon(iconCache);
|
||||||
|
|
||||||
FastBitmapDrawable iconDrawable = mLauncher.createIconDrawable(b);
|
FastBitmapDrawable iconDrawable = mLauncher.createIconDrawable(b);
|
||||||
iconDrawable.setGhostModeEnabled(info.isDisabled != 0);
|
if (info.isDisabled != 0) {
|
||||||
|
iconDrawable.setState(FastBitmapDrawable.State.DISABLED);
|
||||||
|
}
|
||||||
setIcon(iconDrawable, mIconSize);
|
setIcon(iconDrawable, mIconSize);
|
||||||
if (info.contentDescription != null) {
|
if (info.contentDescription != null) {
|
||||||
setContentDescription(info.contentDescription);
|
setContentDescription(info.contentDescription);
|
||||||
@@ -259,7 +236,12 @@ public class BubbleTextView extends TextView
|
|||||||
|
|
||||||
private void updateIconState() {
|
private void updateIconState() {
|
||||||
if (mIcon instanceof FastBitmapDrawable) {
|
if (mIcon instanceof FastBitmapDrawable) {
|
||||||
((FastBitmapDrawable) mIcon).setPressed(isPressed() || mStayPressed);
|
FastBitmapDrawable d = (FastBitmapDrawable) mIcon;
|
||||||
|
if (isPressed() || mStayPressed) {
|
||||||
|
d.animateState(FastBitmapDrawable.State.PRESSED);
|
||||||
|
} else {
|
||||||
|
d.animateState(FastBitmapDrawable.State.NORMAL);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -362,18 +344,7 @@ public class BubbleTextView extends TextView
|
|||||||
@Override
|
@Override
|
||||||
public void draw(Canvas canvas) {
|
public void draw(Canvas canvas) {
|
||||||
if (!mCustomShadowsEnabled) {
|
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);
|
super.draw(canvas);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -533,8 +504,13 @@ public class BubbleTextView extends TextView
|
|||||||
*/
|
*/
|
||||||
public void reapplyItemInfo(final ItemInfo info) {
|
public void reapplyItemInfo(final ItemInfo info) {
|
||||||
if (getTag() == info) {
|
if (getTag() == info) {
|
||||||
|
FastBitmapDrawable.State prevState = FastBitmapDrawable.State.NORMAL;
|
||||||
|
if (mIcon instanceof FastBitmapDrawable) {
|
||||||
|
prevState = ((FastBitmapDrawable) mIcon).getCurrentState();
|
||||||
|
}
|
||||||
mIconLoadRequest = null;
|
mIconLoadRequest = null;
|
||||||
mDisableRelayout = true;
|
mDisableRelayout = true;
|
||||||
|
|
||||||
if (info instanceof AppInfo) {
|
if (info instanceof AppInfo) {
|
||||||
applyFromApplicationInfo((AppInfo) info);
|
applyFromApplicationInfo((AppInfo) info);
|
||||||
} else if (info instanceof ShortcutInfo) {
|
} else if (info instanceof ShortcutInfo) {
|
||||||
@@ -550,6 +526,13 @@ public class BubbleTextView extends TextView
|
|||||||
} else if (info instanceof PackageItemInfo) {
|
} else if (info instanceof PackageItemInfo) {
|
||||||
applyFromPackageItemInfo((PackageItemInfo) info);
|
applyFromPackageItemInfo((PackageItemInfo) info);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If we are reapplying over an old icon, then we should update the new icon to the same
|
||||||
|
// state as the old icon
|
||||||
|
if (mIcon instanceof FastBitmapDrawable) {
|
||||||
|
((FastBitmapDrawable) mIcon).setState(prevState);
|
||||||
|
}
|
||||||
|
|
||||||
mDisableRelayout = false;
|
mDisableRelayout = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -583,55 +566,53 @@ 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
|
@Override
|
||||||
public void setFastScrollFocused(final boolean focused, boolean animated) {
|
public void setFastScrollFocusState(final FastBitmapDrawable.State focusState, boolean animated) {
|
||||||
if (mFastScrollMode == FAST_SCROLL_FOCUS_MODE_NONE) {
|
// We can only set the fast scroll focus state on a FastBitmapDrawable
|
||||||
|
if (!(mIcon instanceof FastBitmapDrawable)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mFastScrollFocused != focused) {
|
FastBitmapDrawable d = (FastBitmapDrawable) mIcon;
|
||||||
mFastScrollFocused = focused;
|
if (animated) {
|
||||||
|
FastBitmapDrawable.State prevState = d.getCurrentState();
|
||||||
if (animated) {
|
if (d.animateState(focusState)) {
|
||||||
// Clean up the previous focus animator
|
// If the state was updated, then update the view accordingly
|
||||||
if (mFastScrollFocusAnimator != null) {
|
animate().scaleX(focusState.viewScale)
|
||||||
mFastScrollFocusAnimator.cancel();
|
.scaleY(focusState.viewScale)
|
||||||
}
|
.setStartDelay(getStartDelayForStateChange(prevState, focusState))
|
||||||
mFastScrollFocusAnimator = ObjectAnimator.ofFloat(this, "fastScrollFocus",
|
.setDuration(d.getDurationForStateChange(prevState, focusState))
|
||||||
focused ? 1f : 0f);
|
.start();
|
||||||
if (focused) {
|
}
|
||||||
mFastScrollFocusAnimator.setInterpolator(new DecelerateInterpolator());
|
} else {
|
||||||
} else {
|
if (d.setState(focusState)) {
|
||||||
mFastScrollFocusAnimator.setInterpolator(new AccelerateInterpolator());
|
// If the state was updated, then update the view accordingly
|
||||||
}
|
animate().cancel();
|
||||||
mFastScrollFocusAnimator.setDuration(focused ?
|
setScaleX(focusState.viewScale);
|
||||||
FAST_SCROLL_FOCUS_FADE_IN_DURATION : FAST_SCROLL_FOCUS_FADE_OUT_DURATION);
|
setScaleY(focusState.viewScale);
|
||||||
mFastScrollFocusAnimator.start();
|
|
||||||
} else {
|
|
||||||
mFastScrollFocusFraction = focused ? 1f : 0f;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the start delay when animating between certain {@link FastBitmapDrawable} states.
|
||||||
|
*/
|
||||||
|
private static int getStartDelayForStateChange(final FastBitmapDrawable.State fromState,
|
||||||
|
final FastBitmapDrawable.State toState) {
|
||||||
|
switch (toState) {
|
||||||
|
case NORMAL:
|
||||||
|
switch (fromState) {
|
||||||
|
case FAST_SCROLL_HIGHLIGHTED:
|
||||||
|
return FastBitmapDrawable.FAST_SCROLL_INACTIVE_DURATION / 4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interface to be implemented by the grand parent to allow click shadow effect.
|
* Interface to be implemented by the grand parent to allow click shadow effect.
|
||||||
*/
|
*/
|
||||||
public static interface BubbleTextShadowHandler {
|
public interface BubbleTextShadowHandler {
|
||||||
void setPressedIcon(BubbleTextView icon, Bitmap background);
|
void setPressedIcon(BubbleTextView icon, Bitmap background);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
package com.android.launcher3;
|
package com.android.launcher3;
|
||||||
|
|
||||||
|
import android.animation.AnimatorSet;
|
||||||
import android.animation.ObjectAnimator;
|
import android.animation.ObjectAnimator;
|
||||||
import android.animation.TimeInterpolator;
|
import android.animation.TimeInterpolator;
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
@@ -28,13 +29,40 @@ import android.graphics.Paint;
|
|||||||
import android.graphics.PixelFormat;
|
import android.graphics.PixelFormat;
|
||||||
import android.graphics.PorterDuff;
|
import android.graphics.PorterDuff;
|
||||||
import android.graphics.PorterDuffColorFilter;
|
import android.graphics.PorterDuffColorFilter;
|
||||||
import android.graphics.Rect;
|
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.util.SparseArray;
|
import android.util.SparseArray;
|
||||||
|
import android.view.animation.DecelerateInterpolator;
|
||||||
|
|
||||||
public class FastBitmapDrawable extends Drawable {
|
public class FastBitmapDrawable extends Drawable {
|
||||||
|
|
||||||
static final TimeInterpolator CLICK_FEEDBACK_INTERPOLATOR = new TimeInterpolator() {
|
/**
|
||||||
|
* The possible states that a FastBitmapDrawable can be in.
|
||||||
|
*/
|
||||||
|
public enum State {
|
||||||
|
|
||||||
|
NORMAL (0f, 0f, 1f, new DecelerateInterpolator()),
|
||||||
|
PRESSED (0f, 100f / 255f, 1f, CLICK_FEEDBACK_INTERPOLATOR),
|
||||||
|
FAST_SCROLL_HIGHLIGHTED (0f, 0f, 1.1f, new DecelerateInterpolator()),
|
||||||
|
FAST_SCROLL_UNHIGHLIGHTED (0.8f, 0.35f, 1f, new DecelerateInterpolator()),
|
||||||
|
DISABLED (1f, 0.5f, 1f, new DecelerateInterpolator());
|
||||||
|
|
||||||
|
public final float desaturation;
|
||||||
|
public final float brightness;
|
||||||
|
/**
|
||||||
|
* Used specifically by the view drawing this FastBitmapDrawable.
|
||||||
|
*/
|
||||||
|
public final float viewScale;
|
||||||
|
public final TimeInterpolator interpolator;
|
||||||
|
|
||||||
|
State(float desaturation, float brightness, float viewScale, TimeInterpolator interpolator) {
|
||||||
|
this.desaturation = desaturation;
|
||||||
|
this.brightness = brightness;
|
||||||
|
this.viewScale = viewScale;
|
||||||
|
this.interpolator = interpolator;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final TimeInterpolator CLICK_FEEDBACK_INTERPOLATOR = new TimeInterpolator() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public float getInterpolation(float input) {
|
public float getInterpolation(float input) {
|
||||||
@@ -47,42 +75,46 @@ public class FastBitmapDrawable extends Drawable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
static final long CLICK_FEEDBACK_DURATION = 2000;
|
public static final int CLICK_FEEDBACK_DURATION = 2000;
|
||||||
|
public static final int FAST_SCROLL_HIGHLIGHT_DURATION = 225;
|
||||||
|
public static final int FAST_SCROLL_UNHIGHLIGHT_DURATION = 150;
|
||||||
|
public static final int FAST_SCROLL_UNHIGHLIGHT_FROM_NORMAL_DURATION = 225;
|
||||||
|
public static final int FAST_SCROLL_INACTIVE_DURATION = 275;
|
||||||
|
|
||||||
private static final int PRESSED_BRIGHTNESS = 100;
|
// Since we don't need 256^2 values for combinations of both the brightness and saturation, we
|
||||||
private static ColorMatrix sGhostModeMatrix;
|
// reduce the value space to a smaller value V, which reduces the number of cached
|
||||||
private static final ColorMatrix sTempMatrix = new ColorMatrix();
|
// ColorMatrixColorFilters that we need to keep to V^2
|
||||||
|
private static final int REDUCED_FILTER_VALUE_SPACE = 48;
|
||||||
|
|
||||||
/**
|
// A cache of ColorFilters for optimizing brightness and saturation animations
|
||||||
* Store the brightness colors filters to optimize animations during icon press. This
|
private static final SparseArray<ColorFilter> sCachedFilter = new SparseArray<>();
|
||||||
* only works for non-ghost-mode icons.
|
|
||||||
*/
|
|
||||||
private static final SparseArray<ColorFilter> sCachedBrightnessFilter =
|
|
||||||
new SparseArray<ColorFilter>();
|
|
||||||
|
|
||||||
private static final int GHOST_MODE_MIN_COLOR_RANGE = 130;
|
// Temporary matrices used for calculation
|
||||||
|
private static final ColorMatrix sTempBrightnessMatrix = new ColorMatrix();
|
||||||
|
private static final ColorMatrix sTempFilterMatrix = new ColorMatrix();
|
||||||
|
|
||||||
private final Paint mPaint = new Paint(Paint.FILTER_BITMAP_FLAG);
|
private final Paint mPaint = new Paint(Paint.FILTER_BITMAP_FLAG);
|
||||||
private final Bitmap mBitmap;
|
private final Bitmap mBitmap;
|
||||||
private int mAlpha;
|
private State mState = State.NORMAL;
|
||||||
|
|
||||||
|
// The saturation and brightness are values that are mapped to REDUCED_FILTER_VALUE_SPACE and
|
||||||
|
// as a result, can be used to compose the key for the cached ColorMatrixColorFilters
|
||||||
|
private int mDesaturation = 0;
|
||||||
private int mBrightness = 0;
|
private int mBrightness = 0;
|
||||||
private boolean mGhostModeEnabled = false;
|
private int mAlpha = 255;
|
||||||
|
private int mPrevUpdateKey = Integer.MAX_VALUE;
|
||||||
|
|
||||||
private boolean mPressed = false;
|
// Animators for the fast bitmap drawable's properties
|
||||||
private ObjectAnimator mPressedAnimator;
|
private AnimatorSet mPropertyAnimator;
|
||||||
|
|
||||||
public FastBitmapDrawable(Bitmap b) {
|
public FastBitmapDrawable(Bitmap b) {
|
||||||
mAlpha = 255;
|
|
||||||
mBitmap = b;
|
mBitmap = b;
|
||||||
setBounds(0, 0, b.getWidth(), b.getHeight());
|
setBounds(0, 0, b.getWidth(), b.getHeight());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void draw(Canvas canvas) {
|
public void draw(Canvas canvas) {
|
||||||
final Rect r = getBounds();
|
canvas.drawBitmap(mBitmap, null, getBounds(), mPaint);
|
||||||
// Draw the bitmap into the bounding rect
|
|
||||||
canvas.drawBitmap(mBitmap, null, r, mPaint);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -136,96 +168,191 @@ public class FastBitmapDrawable extends Drawable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* When enabled, the icon is grayed out and the contrast is increased to give it a 'ghost'
|
* Animates this drawable to a new state.
|
||||||
* appearance.
|
*
|
||||||
|
* @return whether the state has changed.
|
||||||
*/
|
*/
|
||||||
public void setGhostModeEnabled(boolean enabled) {
|
public boolean animateState(State newState) {
|
||||||
if (mGhostModeEnabled != enabled) {
|
State prevState = mState;
|
||||||
mGhostModeEnabled = enabled;
|
if (mState != newState) {
|
||||||
|
mState = newState;
|
||||||
|
|
||||||
|
mPropertyAnimator = cancelAnimator(mPropertyAnimator);
|
||||||
|
mPropertyAnimator = new AnimatorSet();
|
||||||
|
mPropertyAnimator.playTogether(
|
||||||
|
ObjectAnimator
|
||||||
|
.ofFloat(this, "desaturation", newState.desaturation),
|
||||||
|
ObjectAnimator
|
||||||
|
.ofFloat(this, "brightness", newState.brightness));
|
||||||
|
mPropertyAnimator.setInterpolator(newState.interpolator);
|
||||||
|
mPropertyAnimator.setDuration(getDurationForStateChange(prevState, newState));
|
||||||
|
mPropertyAnimator.setStartDelay(getStartDelayForStateChange(prevState, newState));
|
||||||
|
mPropertyAnimator.start();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Immediately sets this drawable to a new state.
|
||||||
|
*
|
||||||
|
* @return whether the state has changed.
|
||||||
|
*/
|
||||||
|
public boolean setState(State newState) {
|
||||||
|
if (mState != newState) {
|
||||||
|
mState = newState;
|
||||||
|
|
||||||
|
mPropertyAnimator = cancelAnimator(mPropertyAnimator);
|
||||||
|
|
||||||
|
setDesaturation(newState.desaturation);
|
||||||
|
setBrightness(newState.brightness);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the current state.
|
||||||
|
*/
|
||||||
|
public State getCurrentState() {
|
||||||
|
return mState;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the duration for the state change animation.
|
||||||
|
*/
|
||||||
|
public static int getDurationForStateChange(State fromState, State toState) {
|
||||||
|
switch (toState) {
|
||||||
|
case NORMAL:
|
||||||
|
switch (fromState) {
|
||||||
|
case PRESSED:
|
||||||
|
return 0;
|
||||||
|
case FAST_SCROLL_HIGHLIGHTED:
|
||||||
|
case FAST_SCROLL_UNHIGHLIGHTED:
|
||||||
|
return FAST_SCROLL_INACTIVE_DURATION;
|
||||||
|
}
|
||||||
|
case PRESSED:
|
||||||
|
return CLICK_FEEDBACK_DURATION;
|
||||||
|
case FAST_SCROLL_HIGHLIGHTED:
|
||||||
|
return FAST_SCROLL_HIGHLIGHT_DURATION;
|
||||||
|
case FAST_SCROLL_UNHIGHLIGHTED:
|
||||||
|
switch (fromState) {
|
||||||
|
case NORMAL:
|
||||||
|
// When animating from normal state, take a little longer
|
||||||
|
return FAST_SCROLL_UNHIGHLIGHT_FROM_NORMAL_DURATION;
|
||||||
|
default:
|
||||||
|
return FAST_SCROLL_UNHIGHLIGHT_DURATION;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the start delay when animating between certain fast scroll states.
|
||||||
|
*/
|
||||||
|
public static int getStartDelayForStateChange(State fromState, State toState) {
|
||||||
|
switch (toState) {
|
||||||
|
case FAST_SCROLL_UNHIGHLIGHTED:
|
||||||
|
switch (fromState) {
|
||||||
|
case NORMAL:
|
||||||
|
return FAST_SCROLL_UNHIGHLIGHT_DURATION / 4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the saturation of this icon, 0 [full color] -> 1 [desaturated]
|
||||||
|
*/
|
||||||
|
public void setDesaturation(float desaturation) {
|
||||||
|
int newDesaturation = (int) Math.floor(desaturation * REDUCED_FILTER_VALUE_SPACE);
|
||||||
|
if (mDesaturation != newDesaturation) {
|
||||||
|
mDesaturation = newDesaturation;
|
||||||
updateFilter();
|
updateFilter();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setPressed(boolean pressed) {
|
public float getDesaturation() {
|
||||||
if (mPressed != pressed) {
|
return (float) mDesaturation / REDUCED_FILTER_VALUE_SPACE;
|
||||||
mPressed = pressed;
|
|
||||||
if (mPressed) {
|
|
||||||
mPressedAnimator = ObjectAnimator
|
|
||||||
.ofInt(this, "brightness", PRESSED_BRIGHTNESS)
|
|
||||||
.setDuration(CLICK_FEEDBACK_DURATION);
|
|
||||||
mPressedAnimator.setInterpolator(CLICK_FEEDBACK_INTERPOLATOR);
|
|
||||||
mPressedAnimator.start();
|
|
||||||
} else if (mPressedAnimator != null) {
|
|
||||||
mPressedAnimator.cancel();
|
|
||||||
setBrightness(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
invalidateSelf();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isGhostModeEnabled() {
|
/**
|
||||||
return mGhostModeEnabled;
|
* Sets the brightness of this icon, 0 [no add. brightness] -> 1 [2bright2furious]
|
||||||
}
|
*/
|
||||||
|
public void setBrightness(float brightness) {
|
||||||
public int getBrightness() {
|
int newBrightness = (int) Math.floor(brightness * REDUCED_FILTER_VALUE_SPACE);
|
||||||
return mBrightness;
|
if (mBrightness != newBrightness) {
|
||||||
}
|
mBrightness = newBrightness;
|
||||||
|
|
||||||
public void setBrightness(int brightness) {
|
|
||||||
if (mBrightness != brightness) {
|
|
||||||
mBrightness = brightness;
|
|
||||||
updateFilter();
|
updateFilter();
|
||||||
invalidateSelf();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public float getBrightness() {
|
||||||
|
return (float) mBrightness / REDUCED_FILTER_VALUE_SPACE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the paint to reflect the current brightness and saturation.
|
||||||
|
*/
|
||||||
private void updateFilter() {
|
private void updateFilter() {
|
||||||
if (mGhostModeEnabled) {
|
boolean usePorterDuffFilter = false;
|
||||||
if (sGhostModeMatrix == null) {
|
int key = -1;
|
||||||
sGhostModeMatrix = new ColorMatrix();
|
if (mDesaturation > 0) {
|
||||||
sGhostModeMatrix.setSaturation(0);
|
key = (mDesaturation << 16) | mBrightness;
|
||||||
|
} else if (mBrightness > 0) {
|
||||||
|
// Compose a key with a fully saturated icon if we are just animating brightness
|
||||||
|
key = (1 << 16) | mBrightness;
|
||||||
|
|
||||||
// For ghost mode, set the color range to [GHOST_MODE_MIN_COLOR_RANGE, 255]
|
// We found that in L, ColorFilters cause drawing artifacts with shadows baked into
|
||||||
float range = (255 - GHOST_MODE_MIN_COLOR_RANGE) / 255.0f;
|
// icons, so just use a PorterDuff filter when we aren't animating saturation
|
||||||
sTempMatrix.set(new float[] {
|
usePorterDuffFilter = true;
|
||||||
range, 0, 0, 0, GHOST_MODE_MIN_COLOR_RANGE,
|
}
|
||||||
0, range, 0, 0, GHOST_MODE_MIN_COLOR_RANGE,
|
|
||||||
0, 0, range, 0, GHOST_MODE_MIN_COLOR_RANGE,
|
|
||||||
0, 0, 0, 1, 0 });
|
|
||||||
sGhostModeMatrix.preConcat(sTempMatrix);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mBrightness == 0) {
|
// Debounce multiple updates on the same frame
|
||||||
mPaint.setColorFilter(new ColorMatrixColorFilter(sGhostModeMatrix));
|
if (key == mPrevUpdateKey) {
|
||||||
} else {
|
return;
|
||||||
setBrightnessMatrix(sTempMatrix, mBrightness);
|
}
|
||||||
sTempMatrix.postConcat(sGhostModeMatrix);
|
mPrevUpdateKey = key;
|
||||||
mPaint.setColorFilter(new ColorMatrixColorFilter(sTempMatrix));
|
|
||||||
}
|
if (key != -1) {
|
||||||
} else if (mBrightness != 0) {
|
ColorFilter filter = sCachedFilter.get(key);
|
||||||
ColorFilter filter = sCachedBrightnessFilter.get(mBrightness);
|
|
||||||
if (filter == null) {
|
if (filter == null) {
|
||||||
filter = new PorterDuffColorFilter(Color.argb(mBrightness, 255, 255, 255),
|
float brightnessF = getBrightness();
|
||||||
PorterDuff.Mode.SRC_ATOP);
|
int brightnessI = (int) (255 * brightnessF);
|
||||||
sCachedBrightnessFilter.put(mBrightness, filter);
|
if (usePorterDuffFilter) {
|
||||||
|
filter = new PorterDuffColorFilter(Color.argb(brightnessI, 255, 255, 255),
|
||||||
|
PorterDuff.Mode.SRC_ATOP);
|
||||||
|
} else {
|
||||||
|
float saturationF = 1f - getDesaturation();
|
||||||
|
sTempFilterMatrix.setSaturation(saturationF);
|
||||||
|
if (mBrightness > 0) {
|
||||||
|
// Brightness: C-new = C-old*(1-amount) + amount
|
||||||
|
float scale = 1f - brightnessF;
|
||||||
|
float[] mat = sTempBrightnessMatrix.getArray();
|
||||||
|
mat[0] = scale;
|
||||||
|
mat[6] = scale;
|
||||||
|
mat[12] = scale;
|
||||||
|
mat[4] = brightnessI;
|
||||||
|
mat[9] = brightnessI;
|
||||||
|
mat[14] = brightnessI;
|
||||||
|
sTempFilterMatrix.preConcat(sTempBrightnessMatrix);
|
||||||
|
}
|
||||||
|
filter = new ColorMatrixColorFilter(sTempFilterMatrix);
|
||||||
|
}
|
||||||
|
sCachedFilter.append(key, filter);
|
||||||
}
|
}
|
||||||
mPaint.setColorFilter(filter);
|
mPaint.setColorFilter(filter);
|
||||||
} else {
|
} else {
|
||||||
mPaint.setColorFilter(null);
|
mPaint.setColorFilter(null);
|
||||||
}
|
}
|
||||||
|
invalidateSelf();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void setBrightnessMatrix(ColorMatrix matrix, int brightness) {
|
private AnimatorSet cancelAnimator(AnimatorSet animator) {
|
||||||
// Brightness: C-new = C-old*(1-amount) + amount
|
if (animator != null) {
|
||||||
float scale = 1 - brightness / 255.0f;
|
animator.removeAllListeners();
|
||||||
matrix.setScale(scale, scale, scale, 1);
|
animator.cancel();
|
||||||
float[] array = matrix.getArray();
|
}
|
||||||
|
return null;
|
||||||
// Add the amount to RGB components of the matrix, as per the above formula.
|
|
||||||
// Fifth elements in the array correspond to the constant being added to
|
|
||||||
// red, blue, green, and alpha channel respectively.
|
|
||||||
array[4] = brightness;
|
|
||||||
array[9] = brightness;
|
|
||||||
array[14] = brightness;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -521,7 +521,7 @@ public class FolderIcon extends FrameLayout implements FolderListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class PreviewItemDrawingParams {
|
class PreviewItemDrawingParams {
|
||||||
PreviewItemDrawingParams(float transX, float transY, float scale, int overlayAlpha) {
|
PreviewItemDrawingParams(float transX, float transY, float scale, float overlayAlpha) {
|
||||||
this.transX = transX;
|
this.transX = transX;
|
||||||
this.transY = transY;
|
this.transY = transY;
|
||||||
this.scale = scale;
|
this.scale = scale;
|
||||||
@@ -530,7 +530,7 @@ public class FolderIcon extends FrameLayout implements FolderListener {
|
|||||||
float transX;
|
float transX;
|
||||||
float transY;
|
float transY;
|
||||||
float scale;
|
float scale;
|
||||||
int overlayAlpha;
|
float overlayAlpha;
|
||||||
Drawable drawable;
|
Drawable drawable;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -562,7 +562,7 @@ public class FolderIcon extends FrameLayout implements FolderListener {
|
|||||||
float transY = mAvailableSpaceInPreview - (offset + scaledSize + scaleOffsetCorrection) + getPaddingTop();
|
float transY = mAvailableSpaceInPreview - (offset + scaledSize + scaleOffsetCorrection) + getPaddingTop();
|
||||||
float transX = (mAvailableSpaceInPreview - scaledSize) / 2;
|
float transX = (mAvailableSpaceInPreview - scaledSize) / 2;
|
||||||
float totalScale = mBaselineIconScale * scale;
|
float totalScale = mBaselineIconScale * scale;
|
||||||
final int overlayAlpha = (int) (80 * (1 - r));
|
final float overlayAlpha = (80 * (1 - r)) / 255f;
|
||||||
|
|
||||||
if (params == null) {
|
if (params == null) {
|
||||||
params = new PreviewItemDrawingParams(transX, transY, totalScale, overlayAlpha);
|
params = new PreviewItemDrawingParams(transX, transY, totalScale, overlayAlpha);
|
||||||
@@ -586,12 +586,12 @@ public class FolderIcon extends FrameLayout implements FolderListener {
|
|||||||
d.setBounds(0, 0, mIntrinsicIconSize, mIntrinsicIconSize);
|
d.setBounds(0, 0, mIntrinsicIconSize, mIntrinsicIconSize);
|
||||||
if (d instanceof FastBitmapDrawable) {
|
if (d instanceof FastBitmapDrawable) {
|
||||||
FastBitmapDrawable fd = (FastBitmapDrawable) d;
|
FastBitmapDrawable fd = (FastBitmapDrawable) d;
|
||||||
int oldBrightness = fd.getBrightness();
|
float oldBrightness = fd.getBrightness();
|
||||||
fd.setBrightness(params.overlayAlpha);
|
fd.setBrightness(params.overlayAlpha);
|
||||||
d.draw(canvas);
|
d.draw(canvas);
|
||||||
fd.setBrightness(oldBrightness);
|
fd.setBrightness(oldBrightness);
|
||||||
} else {
|
} else {
|
||||||
d.setColorFilter(Color.argb(params.overlayAlpha, 255, 255, 255),
|
d.setColorFilter(Color.argb((int) (params.overlayAlpha * 255), 255, 255, 255),
|
||||||
PorterDuff.Mode.SRC_ATOP);
|
PorterDuff.Mode.SRC_ATOP);
|
||||||
d.draw(canvas);
|
d.draw(canvas);
|
||||||
d.clearColorFilter();
|
d.clearColorFilter();
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ import android.content.pm.ActivityInfo;
|
|||||||
import android.content.pm.ApplicationInfo;
|
import android.content.pm.ApplicationInfo;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.content.pm.PackageManager.NameNotFoundException;
|
import android.content.pm.PackageManager.NameNotFoundException;
|
||||||
|
import android.content.pm.ResolveInfo;
|
||||||
import android.content.res.Configuration;
|
import android.content.res.Configuration;
|
||||||
import android.database.sqlite.SQLiteDatabase;
|
import android.database.sqlite.SQLiteDatabase;
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
@@ -4573,6 +4574,18 @@ public class Launcher extends Activity
|
|||||||
UserHandleCompat.myUserHandle());
|
UserHandleCompat.myUserHandle());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a dummy AppInfo for us to use to calculate BubbleTextView sizes.
|
||||||
|
*/
|
||||||
|
public AppInfo createDummyAppInfo() {
|
||||||
|
Intent intent = new Intent();
|
||||||
|
intent.setComponent(new ComponentName(this, Launcher.class));
|
||||||
|
PackageManager pm = getPackageManager();
|
||||||
|
ResolveInfo info = pm.resolveActivity(intent, 0);
|
||||||
|
return new AppInfo(this, LauncherActivityInfoCompat.fromResolveInfo(info, this),
|
||||||
|
UserHandleCompat.myUserHandle(), mIconCache);
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: This method should be a part of LauncherSearchCallback
|
// TODO: This method should be a part of LauncherSearchCallback
|
||||||
public void startDrag(View dragView, ItemInfo dragInfo, DragSource source) {
|
public void startDrag(View dragView, ItemInfo dragInfo, DragSource source) {
|
||||||
dragView.setTag(dragInfo);
|
dragView.setTag(dragInfo);
|
||||||
|
|||||||
@@ -133,7 +133,7 @@ public class PendingAppWidgetHostView extends LauncherAppWidgetHostView implemen
|
|||||||
// 3) Setup icon in the center and app icon in the top right corner.
|
// 3) Setup icon in the center and app icon in the top right corner.
|
||||||
if (mDisabledForSafeMode) {
|
if (mDisabledForSafeMode) {
|
||||||
FastBitmapDrawable disabledIcon = mLauncher.createIconDrawable(mIcon);
|
FastBitmapDrawable disabledIcon = mLauncher.createIconDrawable(mIcon);
|
||||||
disabledIcon.setGhostModeEnabled(true);
|
disabledIcon.setState(FastBitmapDrawable.State.DISABLED);
|
||||||
mCenterDrawable = disabledIcon;
|
mCenterDrawable = disabledIcon;
|
||||||
mSettingIconDrawable = null;
|
mSettingIconDrawable = null;
|
||||||
} else if (isReadyForClickSetup()) {
|
} else if (isReadyForClickSetup()) {
|
||||||
|
|||||||
@@ -179,7 +179,8 @@ class PreloadIconDrawable extends Drawable {
|
|||||||
mPaint.setColor(getIndicatorColor());
|
mPaint.setColor(getIndicatorColor());
|
||||||
}
|
}
|
||||||
if (mIcon instanceof FastBitmapDrawable) {
|
if (mIcon instanceof FastBitmapDrawable) {
|
||||||
((FastBitmapDrawable) mIcon).setGhostModeEnabled(level <= 0);
|
((FastBitmapDrawable) mIcon).setState(level <= 0 ?
|
||||||
|
FastBitmapDrawable.State.DISABLED : FastBitmapDrawable.State.NORMAL);
|
||||||
}
|
}
|
||||||
|
|
||||||
invalidateSelf();
|
invalidateSelf();
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import android.text.SpannableStringBuilder;
|
|||||||
import android.text.method.TextKeyListener;
|
import android.text.method.TextKeyListener;
|
||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
import android.view.KeyEvent;
|
import android.view.KeyEvent;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
import android.view.MotionEvent;
|
import android.view.MotionEvent;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewConfiguration;
|
import android.view.ViewConfiguration;
|
||||||
@@ -35,6 +36,7 @@ import android.view.ViewGroup;
|
|||||||
import android.widget.LinearLayout;
|
import android.widget.LinearLayout;
|
||||||
import com.android.launcher3.AppInfo;
|
import com.android.launcher3.AppInfo;
|
||||||
import com.android.launcher3.BaseContainerView;
|
import com.android.launcher3.BaseContainerView;
|
||||||
|
import com.android.launcher3.BubbleTextView;
|
||||||
import com.android.launcher3.CellLayout;
|
import com.android.launcher3.CellLayout;
|
||||||
import com.android.launcher3.DeleteDropTarget;
|
import com.android.launcher3.DeleteDropTarget;
|
||||||
import com.android.launcher3.DeviceProfile;
|
import com.android.launcher3.DeviceProfile;
|
||||||
@@ -332,6 +334,18 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc
|
|||||||
mAppsRecyclerView.addItemDecoration(mItemDecoration);
|
mAppsRecyclerView.addItemDecoration(mItemDecoration);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Precalculate the prediction icon and normal icon sizes
|
||||||
|
LayoutInflater layoutInflater = LayoutInflater.from(getContext());
|
||||||
|
BubbleTextView icon = (BubbleTextView) layoutInflater.inflate(R.layout.all_apps_icon, this, false);
|
||||||
|
icon.applyFromApplicationInfo(mLauncher.createDummyAppInfo());
|
||||||
|
icon.measure(MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE, MeasureSpec.AT_MOST),
|
||||||
|
MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE, MeasureSpec.AT_MOST));
|
||||||
|
BubbleTextView predIcon = (BubbleTextView) layoutInflater.inflate(R.layout.all_apps_prediction_bar_icon, this, false);
|
||||||
|
predIcon.applyFromApplicationInfo(mLauncher.createDummyAppInfo());
|
||||||
|
predIcon.measure(MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE, MeasureSpec.AT_MOST),
|
||||||
|
MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE, MeasureSpec.AT_MOST));
|
||||||
|
mAppsRecyclerView.setPremeasuredIconHeights(predIcon.getMeasuredHeight(), icon.getMeasuredHeight());
|
||||||
|
|
||||||
updateBackgroundAndPaddings();
|
updateBackgroundAndPaddings();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
228
src/com/android/launcher3/allapps/AllAppsFastScrollHelper.java
Normal file
228
src/com/android/launcher3/allapps/AllAppsFastScrollHelper.java
Normal file
@@ -0,0 +1,228 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2015 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package com.android.launcher3.allapps;
|
||||||
|
|
||||||
|
import android.support.v7.widget.RecyclerView;
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
|
import com.android.launcher3.BaseRecyclerView;
|
||||||
|
import com.android.launcher3.BaseRecyclerViewFastScrollBar;
|
||||||
|
import com.android.launcher3.FastBitmapDrawable;
|
||||||
|
import com.android.launcher3.util.Thunk;
|
||||||
|
|
||||||
|
import java.util.HashSet;
|
||||||
|
|
||||||
|
public class AllAppsFastScrollHelper implements AllAppsGridAdapter.BindViewCallback {
|
||||||
|
|
||||||
|
private static final int INITIAL_TOUCH_SETTLING_DURATION = 300;
|
||||||
|
private static final int REPEAT_TOUCH_SETTLING_DURATION = 200;
|
||||||
|
private static final float FAST_SCROLL_TOUCH_VELOCITY_BARRIER = 1900f;
|
||||||
|
|
||||||
|
private AllAppsRecyclerView mRv;
|
||||||
|
private AlphabeticalAppsList mApps;
|
||||||
|
|
||||||
|
// Keeps track of the current and targetted fast scroll section (the section to scroll to after
|
||||||
|
// the initial delay)
|
||||||
|
int mTargetFastScrollPosition = -1;
|
||||||
|
@Thunk String mCurrentFastScrollSection;
|
||||||
|
@Thunk String mTargetFastScrollSection;
|
||||||
|
|
||||||
|
// The settled states affect the delay before the fast scroll animation is applied
|
||||||
|
private boolean mHasFastScrollTouchSettled;
|
||||||
|
private boolean mHasFastScrollTouchSettledAtLeastOnce;
|
||||||
|
|
||||||
|
// Set of all views animated during fast scroll. We keep track of these ourselves since there
|
||||||
|
// is no way to reset a view once it gets scrapped or recycled without other hacks
|
||||||
|
private HashSet<BaseRecyclerViewFastScrollBar.FastScrollFocusableView> mTrackedFastScrollViews =
|
||||||
|
new HashSet<>();
|
||||||
|
|
||||||
|
// Smooth fast-scroll animation frames
|
||||||
|
@Thunk int mFastScrollFrameIndex;
|
||||||
|
@Thunk final int[] mFastScrollFrames = new int[10];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This runnable runs a single frame of the smooth scroll animation and posts the next frame
|
||||||
|
* if necessary.
|
||||||
|
*/
|
||||||
|
@Thunk Runnable mSmoothSnapNextFrameRunnable = new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
if (mFastScrollFrameIndex < mFastScrollFrames.length) {
|
||||||
|
mRv.scrollBy(0, mFastScrollFrames[mFastScrollFrameIndex]);
|
||||||
|
mFastScrollFrameIndex++;
|
||||||
|
mRv.postOnAnimation(mSmoothSnapNextFrameRunnable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This runnable updates the current fast scroll section to the target fastscroll section.
|
||||||
|
*/
|
||||||
|
Runnable mFastScrollToTargetSectionRunnable = new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
// Update to the target section
|
||||||
|
mCurrentFastScrollSection = mTargetFastScrollSection;
|
||||||
|
mHasFastScrollTouchSettled = true;
|
||||||
|
mHasFastScrollTouchSettledAtLeastOnce = true;
|
||||||
|
updateTrackedViewsFastScrollFocusState();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public AllAppsFastScrollHelper(AllAppsRecyclerView rv, AlphabeticalAppsList apps) {
|
||||||
|
mRv = rv;
|
||||||
|
mApps = apps;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onSetAdapter(AllAppsGridAdapter adapter) {
|
||||||
|
adapter.setBindViewCallback(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Smooth scrolls the recycler view to the given section.
|
||||||
|
*
|
||||||
|
* @return whether the fastscroller can scroll to the new section.
|
||||||
|
*/
|
||||||
|
public boolean smoothScrollToSection(int scrollY, int availableScrollHeight,
|
||||||
|
AlphabeticalAppsList.FastScrollSectionInfo info) {
|
||||||
|
if (mTargetFastScrollPosition != info.fastScrollToItem.position) {
|
||||||
|
mTargetFastScrollPosition = info.fastScrollToItem.position;
|
||||||
|
smoothSnapToPosition(scrollY, availableScrollHeight, info);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Smoothly snaps to a given position. We do this manually by calculating the keyframes
|
||||||
|
* ourselves and animating the scroll on the recycler view.
|
||||||
|
*/
|
||||||
|
private void smoothSnapToPosition(int scrollY, int availableScrollHeight,
|
||||||
|
AlphabeticalAppsList.FastScrollSectionInfo info) {
|
||||||
|
mRv.removeCallbacks(mSmoothSnapNextFrameRunnable);
|
||||||
|
mRv.removeCallbacks(mFastScrollToTargetSectionRunnable);
|
||||||
|
|
||||||
|
trackAllChildViews();
|
||||||
|
if (mHasFastScrollTouchSettled) {
|
||||||
|
// In this case, the user has already settled once (and the fast scroll state has
|
||||||
|
// animated) and they are just fine-tuning their section from the last section, so
|
||||||
|
// we should make it feel fast and update immediately.
|
||||||
|
mCurrentFastScrollSection = info.sectionName;
|
||||||
|
mTargetFastScrollSection = null;
|
||||||
|
updateTrackedViewsFastScrollFocusState();
|
||||||
|
} else {
|
||||||
|
// Otherwise, the user has scrubbed really far, and we don't want to distract the user
|
||||||
|
// with the flashing fast scroll state change animation in addition to the fast scroll
|
||||||
|
// section popup, so reset the views to normal, and wait for the touch to settle again
|
||||||
|
// before animating the fast scroll state.
|
||||||
|
mCurrentFastScrollSection = null;
|
||||||
|
mTargetFastScrollSection = info.sectionName;
|
||||||
|
mHasFastScrollTouchSettled = false;
|
||||||
|
updateTrackedViewsFastScrollFocusState();
|
||||||
|
|
||||||
|
// Delay scrolling to a new section until after some duration. If the user has been
|
||||||
|
// scrubbing a while and makes multiple big jumps, then reduce the time needed for the
|
||||||
|
// fast scroll to settle so it doesn't feel so long.
|
||||||
|
mRv.postDelayed(mFastScrollToTargetSectionRunnable,
|
||||||
|
mHasFastScrollTouchSettledAtLeastOnce ?
|
||||||
|
REPEAT_TOUCH_SETTLING_DURATION :
|
||||||
|
INITIAL_TOUCH_SETTLING_DURATION);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate the full animation from the current scroll position to the final scroll
|
||||||
|
// position, and then run the animation for the duration.
|
||||||
|
int newScrollY = Math.min(availableScrollHeight,
|
||||||
|
mRv.getPaddingTop() + mRv.getTop(info.fastScrollToItem.rowIndex));
|
||||||
|
int numFrames = mFastScrollFrames.length;
|
||||||
|
for (int i = 0; i < numFrames; i++) {
|
||||||
|
// TODO(winsonc): We can interpolate this as well.
|
||||||
|
mFastScrollFrames[i] = (newScrollY - scrollY) / numFrames;
|
||||||
|
}
|
||||||
|
mFastScrollFrameIndex = 0;
|
||||||
|
mRv.postOnAnimation(mSmoothSnapNextFrameRunnable);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onFastScrollCompleted() {
|
||||||
|
// TODO(winsonc): Handle the case when the user scrolls and releases before the animation
|
||||||
|
// runs
|
||||||
|
|
||||||
|
// Stop animating the fast scroll position and state
|
||||||
|
mRv.removeCallbacks(mSmoothSnapNextFrameRunnable);
|
||||||
|
mRv.removeCallbacks(mFastScrollToTargetSectionRunnable);
|
||||||
|
|
||||||
|
// Reset the tracking variables
|
||||||
|
mHasFastScrollTouchSettled = false;
|
||||||
|
mHasFastScrollTouchSettledAtLeastOnce = false;
|
||||||
|
mCurrentFastScrollSection = null;
|
||||||
|
mTargetFastScrollSection = null;
|
||||||
|
mTargetFastScrollPosition = -1;
|
||||||
|
|
||||||
|
updateTrackedViewsFastScrollFocusState();
|
||||||
|
mTrackedFastScrollViews.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBindView(AllAppsGridAdapter.ViewHolder holder) {
|
||||||
|
// Update newly bound views to the current fast scroll state if we are fast scrolling
|
||||||
|
if (mCurrentFastScrollSection != null || mTargetFastScrollSection != null) {
|
||||||
|
if (holder.mContent instanceof BaseRecyclerViewFastScrollBar.FastScrollFocusableView) {
|
||||||
|
BaseRecyclerViewFastScrollBar.FastScrollFocusableView v =
|
||||||
|
(BaseRecyclerViewFastScrollBar.FastScrollFocusableView) holder.mContent;
|
||||||
|
updateViewFastScrollFocusState(v, holder.getPosition(), false /* animated */);
|
||||||
|
mTrackedFastScrollViews.add(v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts tracking all the recycler view's children which are FastScrollFocusableViews.
|
||||||
|
*/
|
||||||
|
private void trackAllChildViews() {
|
||||||
|
int childCount = mRv.getChildCount();
|
||||||
|
for (int i = 0; i < childCount; i++) {
|
||||||
|
View v = mRv.getChildAt(i);
|
||||||
|
if (v instanceof BaseRecyclerViewFastScrollBar.FastScrollFocusableView) {
|
||||||
|
mTrackedFastScrollViews.add((BaseRecyclerViewFastScrollBar.FastScrollFocusableView) v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the fast scroll focus on all the children.
|
||||||
|
*/
|
||||||
|
private void updateTrackedViewsFastScrollFocusState() {
|
||||||
|
for (BaseRecyclerViewFastScrollBar.FastScrollFocusableView v : mTrackedFastScrollViews) {
|
||||||
|
RecyclerView.ViewHolder viewHolder = mRv.getChildViewHolder((View) v);
|
||||||
|
int pos = (viewHolder != null) ? viewHolder.getPosition() : -1;
|
||||||
|
updateViewFastScrollFocusState(v, pos, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the fast scroll focus on all a given view.
|
||||||
|
*/
|
||||||
|
private void updateViewFastScrollFocusState(BaseRecyclerViewFastScrollBar.FastScrollFocusableView v,
|
||||||
|
int pos, boolean animated) {
|
||||||
|
FastBitmapDrawable.State newState = FastBitmapDrawable.State.NORMAL;
|
||||||
|
if (mCurrentFastScrollSection != null && pos > -1) {
|
||||||
|
AlphabeticalAppsList.AdapterItem item = mApps.getAdapterItems().get(pos);
|
||||||
|
newState = item.sectionName.equals(mCurrentFastScrollSection) ?
|
||||||
|
FastBitmapDrawable.State.FAST_SCROLL_HIGHLIGHTED :
|
||||||
|
FastBitmapDrawable.State.FAST_SCROLL_UNHIGHLIGHTED;
|
||||||
|
}
|
||||||
|
v.setFastScrollFocusState(newState, animated);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -69,6 +69,10 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.
|
|||||||
// The message to continue to a market search when there are no filtered results
|
// The message to continue to a market search when there are no filtered results
|
||||||
public static final int SEARCH_MARKET_VIEW_TYPE = 5;
|
public static final int SEARCH_MARKET_VIEW_TYPE = 5;
|
||||||
|
|
||||||
|
public interface BindViewCallback {
|
||||||
|
public void onBindView(ViewHolder holder);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ViewHolder for each icon.
|
* ViewHolder for each icon.
|
||||||
*/
|
*/
|
||||||
@@ -328,6 +332,7 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.
|
|||||||
private View.OnTouchListener mTouchListener;
|
private View.OnTouchListener mTouchListener;
|
||||||
private View.OnClickListener mIconClickListener;
|
private View.OnClickListener mIconClickListener;
|
||||||
private View.OnLongClickListener mIconLongClickListener;
|
private View.OnLongClickListener mIconLongClickListener;
|
||||||
|
private BindViewCallback mBindViewCallback;
|
||||||
@Thunk final Rect mBackgroundPadding = new Rect();
|
@Thunk final Rect mBackgroundPadding = new Rect();
|
||||||
@Thunk int mPredictionBarDividerOffset;
|
@Thunk int mPredictionBarDividerOffset;
|
||||||
@Thunk int mAppsPerRow;
|
@Thunk int mAppsPerRow;
|
||||||
@@ -423,6 +428,13 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the callback for when views are bound.
|
||||||
|
*/
|
||||||
|
public void setBindViewCallback(BindViewCallback cb) {
|
||||||
|
mBindViewCallback = cb;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Notifies the adapter of the background padding so that it can draw things correctly in the
|
* Notifies the adapter of the background padding so that it can draw things correctly in the
|
||||||
* item decorator.
|
* item decorator.
|
||||||
@@ -528,6 +540,15 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
if (mBindViewCallback != null) {
|
||||||
|
mBindViewCallback.onBindView(holder);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onFailedToRecycleView(ViewHolder holder) {
|
||||||
|
// Always recycle and we will reset the view when it is bound
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -15,24 +15,20 @@
|
|||||||
*/
|
*/
|
||||||
package com.android.launcher3.allapps;
|
package com.android.launcher3.allapps;
|
||||||
|
|
||||||
import android.animation.ObjectAnimator;
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.res.Resources;
|
import android.content.res.Resources;
|
||||||
import android.graphics.Canvas;
|
import android.graphics.Canvas;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.support.v7.widget.LinearLayoutManager;
|
|
||||||
import android.support.v7.widget.RecyclerView;
|
import android.support.v7.widget.RecyclerView;
|
||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
|
||||||
import com.android.launcher3.BaseRecyclerView;
|
import com.android.launcher3.BaseRecyclerView;
|
||||||
import com.android.launcher3.BaseRecyclerViewFastScrollBar;
|
|
||||||
import com.android.launcher3.DeviceProfile;
|
import com.android.launcher3.DeviceProfile;
|
||||||
import com.android.launcher3.R;
|
import com.android.launcher3.R;
|
||||||
import com.android.launcher3.Stats;
|
import com.android.launcher3.Stats;
|
||||||
import com.android.launcher3.Utilities;
|
import com.android.launcher3.Utilities;
|
||||||
import com.android.launcher3.util.Thunk;
|
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -42,25 +38,17 @@ import java.util.List;
|
|||||||
public class AllAppsRecyclerView extends BaseRecyclerView
|
public class AllAppsRecyclerView extends BaseRecyclerView
|
||||||
implements Stats.LaunchSourceProvider {
|
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 AlphabeticalAppsList mApps;
|
||||||
|
private AllAppsFastScrollHelper mFastScrollHelper;
|
||||||
|
private BaseRecyclerView.ScrollPositionState mScrollPosState =
|
||||||
|
new BaseRecyclerView.ScrollPositionState();
|
||||||
private int mNumAppsPerRow;
|
private int mNumAppsPerRow;
|
||||||
|
|
||||||
@Thunk BaseRecyclerViewFastScrollBar.FastScrollFocusableView mLastFastScrollFocusedView;
|
// The specific icon heights that we use to calculate scroll
|
||||||
@Thunk int mPrevFastScrollFocusedPosition;
|
private int mPredictionIconHeight;
|
||||||
@Thunk int mFastScrollFrameIndex;
|
private int mIconHeight;
|
||||||
@Thunk final int[] mFastScrollFrames = new int[10];
|
|
||||||
|
|
||||||
private final int mFastScrollMode = FAST_SCROLL_MODE_JUMP_TO_FIRST_ICON;
|
|
||||||
private final int mScrollBarMode = FAST_SCROLL_BAR_MODE_DISTRIBUTE_BY_ROW;
|
|
||||||
|
|
||||||
private ScrollPositionState mScrollPosState = new ScrollPositionState();
|
|
||||||
|
|
||||||
|
// The empty-search result background
|
||||||
private AllAppsBackgroundDrawable mEmptySearchBackground;
|
private AllAppsBackgroundDrawable mEmptySearchBackground;
|
||||||
private int mEmptySearchBackgroundTopOffset;
|
private int mEmptySearchBackgroundTopOffset;
|
||||||
|
|
||||||
@@ -79,8 +67,8 @@ public class AllAppsRecyclerView extends BaseRecyclerView
|
|||||||
public AllAppsRecyclerView(Context context, AttributeSet attrs, int defStyleAttr,
|
public AllAppsRecyclerView(Context context, AttributeSet attrs, int defStyleAttr,
|
||||||
int defStyleRes) {
|
int defStyleRes) {
|
||||||
super(context, attrs, defStyleAttr);
|
super(context, attrs, defStyleAttr);
|
||||||
|
|
||||||
Resources res = getResources();
|
Resources res = getResources();
|
||||||
|
addOnItemTouchListener(this);
|
||||||
mScrollbar.setDetachThumbOnFastScroll();
|
mScrollbar.setDetachThumbOnFastScroll();
|
||||||
mEmptySearchBackgroundTopOffset = res.getDimensionPixelSize(
|
mEmptySearchBackgroundTopOffset = res.getDimensionPixelSize(
|
||||||
R.dimen.all_apps_empty_search_bg_top_offset);
|
R.dimen.all_apps_empty_search_bg_top_offset);
|
||||||
@@ -91,6 +79,7 @@ public class AllAppsRecyclerView extends BaseRecyclerView
|
|||||||
*/
|
*/
|
||||||
public void setApps(AlphabeticalAppsList apps) {
|
public void setApps(AlphabeticalAppsList apps) {
|
||||||
mApps = apps;
|
mApps = apps;
|
||||||
|
mFastScrollHelper = new AllAppsFastScrollHelper(this, apps);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -109,6 +98,14 @@ public class AllAppsRecyclerView extends BaseRecyclerView
|
|||||||
pool.setMaxRecycledViews(AllAppsGridAdapter.SECTION_BREAK_VIEW_TYPE, approxRows);
|
pool.setMaxRecycledViews(AllAppsGridAdapter.SECTION_BREAK_VIEW_TYPE, approxRows);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the heights of the icons in this view (for scroll calculations).
|
||||||
|
*/
|
||||||
|
public void setPremeasuredIconHeights(int predictionIconHeight, int iconHeight) {
|
||||||
|
mPredictionIconHeight = predictionIconHeight;
|
||||||
|
mIconHeight = iconHeight;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Scrolls this recycler view to the top.
|
* Scrolls this recycler view to the top.
|
||||||
*/
|
*/
|
||||||
@@ -126,6 +123,7 @@ public class AllAppsRecyclerView extends BaseRecyclerView
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected void dispatchDraw(Canvas canvas) {
|
protected void dispatchDraw(Canvas canvas) {
|
||||||
|
// Clip to ensure that we don't draw the overscroll effect beyond the background bounds
|
||||||
canvas.clipRect(mBackgroundPadding.left, mBackgroundPadding.top,
|
canvas.clipRect(mBackgroundPadding.left, mBackgroundPadding.top,
|
||||||
getWidth() - mBackgroundPadding.right,
|
getWidth() - mBackgroundPadding.right,
|
||||||
getHeight() - mBackgroundPadding.bottom);
|
getHeight() - mBackgroundPadding.bottom);
|
||||||
@@ -156,14 +154,6 @@ public class AllAppsRecyclerView extends BaseRecyclerView
|
|||||||
updateEmptySearchBackgroundBounds();
|
updateEmptySearchBackgroundBounds();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onFinishInflate() {
|
|
||||||
super.onFinishInflate();
|
|
||||||
|
|
||||||
// Bind event handlers
|
|
||||||
addOnItemTouchListener(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void fillInLaunchSourceData(Bundle sourceData) {
|
public void fillInLaunchSourceData(Bundle sourceData) {
|
||||||
sourceData.putString(Stats.SOURCE_EXTRA_CONTAINER, Stats.CONTAINER_ALL_APPS);
|
sourceData.putString(Stats.SOURCE_EXTRA_CONTAINER, Stats.CONTAINER_ALL_APPS);
|
||||||
@@ -212,63 +202,31 @@ public class AllAppsRecyclerView extends BaseRecyclerView
|
|||||||
List<AlphabeticalAppsList.FastScrollSectionInfo> fastScrollSections =
|
List<AlphabeticalAppsList.FastScrollSectionInfo> fastScrollSections =
|
||||||
mApps.getFastScrollerSections();
|
mApps.getFastScrollerSections();
|
||||||
AlphabeticalAppsList.FastScrollSectionInfo lastInfo = fastScrollSections.get(0);
|
AlphabeticalAppsList.FastScrollSectionInfo lastInfo = fastScrollSections.get(0);
|
||||||
if (mScrollBarMode == FAST_SCROLL_BAR_MODE_DISTRIBUTE_BY_ROW) {
|
for (int i = 1; i < fastScrollSections.size(); i++) {
|
||||||
for (int i = 1; i < fastScrollSections.size(); i++) {
|
AlphabeticalAppsList.FastScrollSectionInfo info = fastScrollSections.get(i);
|
||||||
AlphabeticalAppsList.FastScrollSectionInfo info = fastScrollSections.get(i);
|
if (info.touchFraction > touchFraction) {
|
||||||
if (info.touchFraction > touchFraction) {
|
break;
|
||||||
break;
|
|
||||||
}
|
|
||||||
lastInfo = info;
|
|
||||||
}
|
}
|
||||||
} else if (mScrollBarMode == FAST_SCROLL_BAR_MODE_DISTRIBUTE_BY_SECTIONS){
|
lastInfo = info;
|
||||||
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
|
// Update the fast scroll
|
||||||
getCurScrollState(mScrollPosState);
|
int scrollY = getScrollTop(mScrollPosState);
|
||||||
int availableScrollHeight = getAvailableScrollHeight(rowCount, mScrollPosState.rowHeight);
|
int availableScrollHeight = getAvailableScrollHeight(mApps.getNumAppRows());
|
||||||
LinearLayoutManager layoutManager = (LinearLayoutManager) getLayoutManager();
|
mFastScrollHelper.smoothScrollToSection(scrollY, availableScrollHeight, lastInfo);
|
||||||
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");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return lastInfo.sectionName;
|
return lastInfo.sectionName;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onFastScrollCompleted() {
|
public void onFastScrollCompleted() {
|
||||||
super.onFastScrollCompleted();
|
super.onFastScrollCompleted();
|
||||||
// Reset and clean up the last focused view
|
mFastScrollHelper.onFastScrollCompleted();
|
||||||
if (mLastFastScrollFocusedView != null) {
|
}
|
||||||
mLastFastScrollFocusedView.setFastScrollFocused(false, true);
|
|
||||||
mLastFastScrollFocusedView = null;
|
@Override
|
||||||
}
|
public void setAdapter(Adapter adapter) {
|
||||||
mPrevFastScrollFocusedPosition = -1;
|
super.setAdapter(adapter);
|
||||||
|
mFastScrollHelper.onSetAdapter((AllAppsGridAdapter) adapter);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -286,7 +244,7 @@ public class AllAppsRecyclerView extends BaseRecyclerView
|
|||||||
|
|
||||||
// Find the index and height of the first visible row (all rows have the same height)
|
// Find the index and height of the first visible row (all rows have the same height)
|
||||||
int rowCount = mApps.getNumAppRows();
|
int rowCount = mApps.getNumAppRows();
|
||||||
getCurScrollState(mScrollPosState);
|
getCurScrollState(mScrollPosState, -1);
|
||||||
if (mScrollPosState.rowIndex < 0) {
|
if (mScrollPosState.rowIndex < 0) {
|
||||||
mScrollbar.setThumbOffset(-1, -1);
|
mScrollbar.setThumbOffset(-1, -1);
|
||||||
return;
|
return;
|
||||||
@@ -294,7 +252,7 @@ public class AllAppsRecyclerView extends BaseRecyclerView
|
|||||||
|
|
||||||
// Only show the scrollbar if there is height to be scrolled
|
// Only show the scrollbar if there is height to be scrolled
|
||||||
int availableScrollBarHeight = getAvailableScrollBarHeight();
|
int availableScrollBarHeight = getAvailableScrollBarHeight();
|
||||||
int availableScrollHeight = getAvailableScrollHeight(mApps.getNumAppRows(), mScrollPosState.rowHeight);
|
int availableScrollHeight = getAvailableScrollHeight(mApps.getNumAppRows());
|
||||||
if (availableScrollHeight <= 0) {
|
if (availableScrollHeight <= 0) {
|
||||||
mScrollbar.setThumbOffset(-1, -1);
|
mScrollbar.setThumbOffset(-1, -1);
|
||||||
return;
|
return;
|
||||||
@@ -303,8 +261,7 @@ public class AllAppsRecyclerView extends BaseRecyclerView
|
|||||||
// Calculate the current scroll position, the scrollY of the recycler view accounts for the
|
// 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
|
// view padding, while the scrollBarY is drawn right up to the background padding (ignoring
|
||||||
// padding)
|
// padding)
|
||||||
int scrollY = getPaddingTop() +
|
int scrollY = getScrollTop(mScrollPosState);
|
||||||
(mScrollPosState.rowIndex * mScrollPosState.rowHeight) - mScrollPosState.rowTopOffset;
|
|
||||||
int scrollBarY = mBackgroundPadding.top +
|
int scrollBarY = mBackgroundPadding.top +
|
||||||
(int) (((float) scrollY / availableScrollHeight) * availableScrollBarHeight);
|
(int) (((float) scrollY / availableScrollHeight) * availableScrollBarHeight);
|
||||||
|
|
||||||
@@ -354,59 +311,13 @@ public class AllAppsRecyclerView extends BaseRecyclerView
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* This runnable runs a single frame of the smooth scroll animation and posts the next frame
|
|
||||||
* if necessary.
|
|
||||||
*/
|
|
||||||
@Thunk Runnable mSmoothSnapNextFrameRunnable = new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
if (mFastScrollFrameIndex < mFastScrollFrames.length) {
|
|
||||||
scrollBy(0, mFastScrollFrames[mFastScrollFrameIndex]);
|
|
||||||
mFastScrollFrameIndex++;
|
|
||||||
postOnAnimation(mSmoothSnapNextFrameRunnable);
|
|
||||||
} else {
|
|
||||||
// Animation completed, set the fast scroll state on the target view
|
|
||||||
final ViewHolder vh = findViewHolderForPosition(mPrevFastScrollFocusedPosition);
|
|
||||||
if (vh != null &&
|
|
||||||
vh.itemView instanceof BaseRecyclerViewFastScrollBar.FastScrollFocusableView &&
|
|
||||||
mLastFastScrollFocusedView != vh.itemView) {
|
|
||||||
mLastFastScrollFocusedView =
|
|
||||||
(BaseRecyclerViewFastScrollBar.FastScrollFocusableView) vh.itemView;
|
|
||||||
mLastFastScrollFocusedView.setFastScrollFocused(true, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Smoothly snaps to a given position. We do this manually by calculating the keyframes
|
|
||||||
* ourselves and animating the scroll on the recycler view.
|
|
||||||
*/
|
|
||||||
private void smoothSnapToPosition(final int position, ScrollPositionState scrollPosState) {
|
|
||||||
removeCallbacks(mSmoothSnapNextFrameRunnable);
|
|
||||||
|
|
||||||
// Calculate the full animation from the current scroll position to the final scroll
|
|
||||||
// position, and then run the animation for the duration.
|
|
||||||
int curScrollY = getPaddingTop() +
|
|
||||||
(scrollPosState.rowIndex * scrollPosState.rowHeight) - scrollPosState.rowTopOffset;
|
|
||||||
int newScrollY = getScrollAtPosition(position, scrollPosState.rowHeight);
|
|
||||||
int numFrames = mFastScrollFrames.length;
|
|
||||||
for (int i = 0; i < numFrames; i++) {
|
|
||||||
// TODO(winsonc): We can interpolate this as well.
|
|
||||||
mFastScrollFrames[i] = (newScrollY - curScrollY) / numFrames;
|
|
||||||
}
|
|
||||||
mFastScrollFrameIndex = 0;
|
|
||||||
postOnAnimation(mSmoothSnapNextFrameRunnable);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the current scroll state of the apps rows.
|
* Returns the current scroll state of the apps rows.
|
||||||
*/
|
*/
|
||||||
protected void getCurScrollState(ScrollPositionState stateOut) {
|
protected void getCurScrollState(ScrollPositionState stateOut, int viewTypeMask) {
|
||||||
stateOut.rowIndex = -1;
|
stateOut.rowIndex = -1;
|
||||||
stateOut.rowTopOffset = -1;
|
stateOut.rowTopOffset = -1;
|
||||||
stateOut.rowHeight = -1;
|
stateOut.itemPos = -1;
|
||||||
|
|
||||||
// Return early if there are no items or we haven't been measured
|
// Return early if there are no items or we haven't been measured
|
||||||
List<AlphabeticalAppsList.AdapterItem> items = mApps.getAdapterItems();
|
List<AlphabeticalAppsList.AdapterItem> items = mApps.getAdapterItems();
|
||||||
@@ -420,15 +331,15 @@ public class AllAppsRecyclerView extends BaseRecyclerView
|
|||||||
int position = getChildPosition(child);
|
int position = getChildPosition(child);
|
||||||
if (position != NO_POSITION) {
|
if (position != NO_POSITION) {
|
||||||
AlphabeticalAppsList.AdapterItem item = items.get(position);
|
AlphabeticalAppsList.AdapterItem item = items.get(position);
|
||||||
if (item.viewType == AllAppsGridAdapter.ICON_VIEW_TYPE ||
|
if ((item.viewType & viewTypeMask) != 0) {
|
||||||
item.viewType == AllAppsGridAdapter.PREDICTION_ICON_VIEW_TYPE) {
|
|
||||||
stateOut.rowIndex = item.rowIndex;
|
stateOut.rowIndex = item.rowIndex;
|
||||||
stateOut.rowTopOffset = getLayoutManager().getDecoratedTop(child);
|
stateOut.rowTopOffset = getLayoutManager().getDecoratedTop(child);
|
||||||
stateOut.rowHeight = child.getHeight();
|
stateOut.itemPos = position;
|
||||||
break;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -438,18 +349,13 @@ public class AllAppsRecyclerView extends BaseRecyclerView
|
|||||||
return !mApps.hasFilter();
|
return !mApps.hasFilter();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
protected int getTop(int rowIndex) {
|
||||||
* Returns the scrollY for the given position in the adapter.
|
if (getChildCount() == 0 || rowIndex <= 0) {
|
||||||
*/
|
|
||||||
private int getScrollAtPosition(int position, int rowHeight) {
|
|
||||||
AlphabeticalAppsList.AdapterItem item = mApps.getAdapterItems().get(position);
|
|
||||||
if (item.viewType == AllAppsGridAdapter.ICON_VIEW_TYPE ||
|
|
||||||
item.viewType == AllAppsGridAdapter.PREDICTION_ICON_VIEW_TYPE) {
|
|
||||||
int offset = item.rowIndex > 0 ? getPaddingTop() : 0;
|
|
||||||
return offset + item.rowIndex * rowHeight;
|
|
||||||
} else {
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The prediction bar icons have more padding, so account for that in the row offset
|
||||||
|
return mPredictionIconHeight + (rowIndex - 1) * mIconHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -102,9 +102,9 @@ public class WidgetsRecyclerView extends BaseRecyclerView {
|
|||||||
// Stop the scroller if it is scrolling
|
// Stop the scroller if it is scrolling
|
||||||
stopScroll();
|
stopScroll();
|
||||||
|
|
||||||
getCurScrollState(mScrollPosState);
|
getCurScrollState(mScrollPosState, -1);
|
||||||
float pos = rowCount * touchFraction;
|
float pos = rowCount * touchFraction;
|
||||||
int availableScrollHeight = getAvailableScrollHeight(rowCount, mScrollPosState.rowHeight);
|
int availableScrollHeight = getAvailableScrollHeight(rowCount);
|
||||||
LinearLayoutManager layoutManager = ((LinearLayoutManager) getLayoutManager());
|
LinearLayoutManager layoutManager = ((LinearLayoutManager) getLayoutManager());
|
||||||
layoutManager.scrollToPositionWithOffset(0, (int) -(availableScrollHeight * touchFraction));
|
layoutManager.scrollToPositionWithOffset(0, (int) -(availableScrollHeight * touchFraction));
|
||||||
|
|
||||||
@@ -131,7 +131,7 @@ public class WidgetsRecyclerView extends BaseRecyclerView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Skip early if, there no child laid out in the container.
|
// Skip early if, there no child laid out in the container.
|
||||||
getCurScrollState(mScrollPosState);
|
getCurScrollState(mScrollPosState, -1);
|
||||||
if (mScrollPosState.rowIndex < 0) {
|
if (mScrollPosState.rowIndex < 0) {
|
||||||
mScrollbar.setThumbOffset(-1, -1);
|
mScrollbar.setThumbOffset(-1, -1);
|
||||||
return;
|
return;
|
||||||
@@ -143,10 +143,10 @@ public class WidgetsRecyclerView extends BaseRecyclerView {
|
|||||||
/**
|
/**
|
||||||
* Returns the current scroll state.
|
* Returns the current scroll state.
|
||||||
*/
|
*/
|
||||||
protected void getCurScrollState(ScrollPositionState stateOut) {
|
protected void getCurScrollState(ScrollPositionState stateOut, int viewTypeMask) {
|
||||||
stateOut.rowIndex = -1;
|
stateOut.rowIndex = -1;
|
||||||
stateOut.rowTopOffset = -1;
|
stateOut.rowTopOffset = -1;
|
||||||
stateOut.rowHeight = -1;
|
stateOut.itemPos = -1;
|
||||||
|
|
||||||
// Skip early if widgets are not bound.
|
// Skip early if widgets are not bound.
|
||||||
if (mWidgets == null) {
|
if (mWidgets == null) {
|
||||||
@@ -163,6 +163,17 @@ public class WidgetsRecyclerView extends BaseRecyclerView {
|
|||||||
|
|
||||||
stateOut.rowIndex = position;
|
stateOut.rowIndex = position;
|
||||||
stateOut.rowTopOffset = getLayoutManager().getDecoratedTop(child);
|
stateOut.rowTopOffset = getLayoutManager().getDecoratedTop(child);
|
||||||
stateOut.rowHeight = child.getHeight();
|
stateOut.itemPos = position;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected int getTop(int rowIndex) {
|
||||||
|
if (getChildCount() == 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// All the rows are the same height, return any child height
|
||||||
|
View child = getChildAt(0);
|
||||||
|
return child.getMeasuredHeight() * rowIndex;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user