diff options
author | Selim Cinek <cinek@google.com> | 2014-05-13 15:45:22 +0000 |
---|---|---|
committer | Android (Google) Code Review <android-gerrit@google.com> | 2014-05-13 15:45:22 +0000 |
commit | 2f675acdb2f9b8476017cc8bb21b6c4d34577059 (patch) | |
tree | 3c368cf38b34f2cb575459652ad4eb246dbf4547 /packages/SystemUI/src | |
parent | d2cd3bcff4c7d24b847e9202e47c41ce44c3b22c (diff) | |
parent | 8d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dc (diff) | |
download | frameworks_base-2f675acdb2f9b8476017cc8bb21b6c4d34577059.zip frameworks_base-2f675acdb2f9b8476017cc8bb21b6c4d34577059.tar.gz frameworks_base-2f675acdb2f9b8476017cc8bb21b6c4d34577059.tar.bz2 |
Merge "Introduced overscrolling for the new notifications"
Diffstat (limited to 'packages/SystemUI/src')
4 files changed, 273 insertions, 63 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java index 4121a40..deab757 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java @@ -28,6 +28,8 @@ public class AmbientState { private int mScrollY; private boolean mDimmed; private View mActivatedChild; + private float mOverScrollTopAmount; + private float mOverScrollBottomAmount; public int getScrollY() { return mScrollY; @@ -72,4 +74,16 @@ public class AmbientState { public View getActivatedChild() { return mActivatedChild; } + + public void setOverScrollAmount(float amount, boolean onTop) { + if (onTop) { + mOverScrollTopAmount = amount; + } else { + mOverScrollBottomAmount = amount; + } + } + + public float getOverScrollAmount(boolean top) { + return top ? mOverScrollTopAmount : mOverScrollBottomAmount; + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java index 5849afb..109a2d8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java @@ -53,6 +53,7 @@ public class NotificationStackScrollLayout extends ViewGroup private static final String TAG = "NotificationStackScrollLayout"; private static final boolean DEBUG = false; + private static final float RUBBER_BAND_FACTOR = 0.35f; /** * Sentinel value for no current active pointer. Used by {@link #mActivePointerId}. @@ -70,8 +71,8 @@ public class NotificationStackScrollLayout extends ViewGroup private int mTouchSlop; private int mMinimumVelocity; private int mMaximumVelocity; - private int mOverscrollDistance; private int mOverflingDistance; + private float mMaxOverScroll; private boolean mIsBeingDragged; private int mLastMotionY; private int mActivePointerId; @@ -107,6 +108,16 @@ public class NotificationStackScrollLayout extends ViewGroup private ArrayList<View> mSwipedOutViews = new ArrayList<View>(); private final StackStateAnimator mStateAnimator = new StackStateAnimator(this); + /** + * The raw amount of the overScroll on the top, which is not rubber-banded. + */ + private float mOverScrolledTopPixels; + + /** + * The raw amount of the overScroll on the bottom, which is not rubber-banded. + */ + private float mOverScrolledBottomPixels; + private OnChildLocationsChangedListener mListener; private ExpandableView.OnHeightChangedListener mOnHeightChangedListener; private boolean mNeedsAnimation; @@ -175,7 +186,6 @@ public class NotificationStackScrollLayout extends ViewGroup mTouchSlop = configuration.getScaledTouchSlop(); mMinimumVelocity = configuration.getScaledMinimumFlingVelocity(); mMaximumVelocity = configuration.getScaledMaximumFlingVelocity(); - mOverscrollDistance = configuration.getScaledOverscrollDistance(); mOverflingDistance = configuration.getScaledOverflingDistance(); float densityScale = getResources().getDisplayMetrics().density; float pagingTouchSlop = ViewConfiguration.get(getContext()).getScaledPagingTouchSlop(); @@ -544,7 +554,7 @@ public class NotificationStackScrollLayout extends ViewGroup * will be false if being flinged. */ if (!mScroller.isFinished()) { - mScroller.abortAnimation(); + mScroller.forceFinished(true); } // Remember where the motion event started @@ -572,40 +582,23 @@ public class NotificationStackScrollLayout extends ViewGroup if (mIsBeingDragged) { // Scroll to follow the motion event mLastMotionY = y; - - final int oldX = mScrollX; - final int oldY = mOwnScrollY; final int range = getScrollRange(); - final int overscrollMode = getOverScrollMode(); - final boolean canOverscroll = overscrollMode == OVER_SCROLL_ALWAYS || - (overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS && range > 0); + + float scrollAmount; + if (deltaY < 0) { + scrollAmount = overScrollDown(deltaY); + } else { + scrollAmount = overScrollUp(deltaY, range); + } // Calling overScrollBy will call onOverScrolled, which // calls onScrollChanged if applicable. - if (overScrollBy(0, deltaY, 0, mOwnScrollY, - 0, range, 0, mOverscrollDistance, true)) { - // Break our velocity if we hit a scroll barrier. - mVelocityTracker.clear(); + if (scrollAmount != 0.0f) { + // The scrolling motion could not be compensated with the + // existing overScroll, we have to scroll the view + overScrollBy(0, (int) scrollAmount, 0, mOwnScrollY, + 0, range, 0, getHeight() / 2, true); } - // TODO: Overscroll -// if (canOverscroll) { -// final int pulledToY = oldY + deltaY; -// if (pulledToY < 0) { -// mEdgeGlowTop.onPull((float) deltaY / getHeight()); -// if (!mEdgeGlowBottom.isFinished()) { -// mEdgeGlowBottom.onRelease(); -// } -// } else if (pulledToY > range) { -// mEdgeGlowBottom.onPull((float) deltaY / getHeight()); -// if (!mEdgeGlowTop.isFinished()) { -// mEdgeGlowTop.onRelease(); -// } -// } -// if (mEdgeGlowTop != null -// && (!mEdgeGlowTop.isFinished() || !mEdgeGlowBottom.isFinished())){ -// postInvalidateOnAnimation(); -// } -// } } break; case MotionEvent.ACTION_UP: @@ -652,6 +645,68 @@ public class NotificationStackScrollLayout extends ViewGroup return true; } + /** + * Perform a scroll upwards and adapt the overscroll amounts accordingly + * + * @param deltaY The amount to scroll upwards, has to be positive. + * @return The amount of scrolling to be performed by the scroller, + * not handled by the overScroll amount. + */ + private float overScrollUp(int deltaY, int range) { + deltaY = Math.max(deltaY, 0); + float currentTopAmount = getCurrentOverScrollAmount(true); + float newTopAmount = currentTopAmount - deltaY; + if (currentTopAmount > 0) { + setOverScrollAmount(newTopAmount, true /* onTop */, + false /* animate */); + } + // Top overScroll might not grab all scrolling motion, + // we have to scroll as well. + float scrollAmount = newTopAmount < 0 ? -newTopAmount : 0.0f; + float newScrollY = mOwnScrollY + scrollAmount; + if (newScrollY > range) { + float currentBottomPixels = getCurrentOverScrolledPixels(false); + // We overScroll on the top + setOverScrolledPixels(currentBottomPixels + newScrollY - range, + false /* onTop */, + false /* animate */); + mOwnScrollY = range; + scrollAmount = 0.0f; + } + return scrollAmount; + } + + /** + * Perform a scroll downward and adapt the overscroll amounts accordingly + * + * @param deltaY The amount to scroll downwards, has to be negative. + * @return The amount of scrolling to be performed by the scroller, + * not handled by the overScroll amount. + */ + private float overScrollDown(int deltaY) { + deltaY = Math.min(deltaY, 0); + float currentBottomAmount = getCurrentOverScrollAmount(false); + float newBottomAmount = currentBottomAmount + deltaY; + if (currentBottomAmount > 0) { + setOverScrollAmount(newBottomAmount, false /* onTop */, + false /* animate */); + } + // Bottom overScroll might not grab all scrolling motion, + // we have to scroll as well. + float scrollAmount = newBottomAmount < 0 ? newBottomAmount : 0.0f; + float newScrollY = mOwnScrollY + scrollAmount; + if (newScrollY < 0) { + float currentTopPixels = getCurrentOverScrolledPixels(true); + // We overScroll on the top + setOverScrolledPixels(currentTopPixels - newScrollY, + true /* onTop */, + false /* animate */); + mOwnScrollY = 0; + scrollAmount = 0.0f; + } + return scrollAmount; + } + private void onSecondaryPointerUp(MotionEvent ev) { final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT; @@ -701,23 +756,16 @@ public class NotificationStackScrollLayout extends ViewGroup if (oldX != x || oldY != y) { final int range = getScrollRange(); - final int overscrollMode = getOverScrollMode(); - final boolean canOverscroll = overscrollMode == OVER_SCROLL_ALWAYS || - (overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS && range > 0); + if (y < 0 && oldY >= 0 || y > range && oldY <= range) { + float currVelocity = mScroller.getCurrVelocity(); + if (currVelocity >= mMinimumVelocity * 20) { + mMaxOverScroll = Math.abs(currVelocity) / 1000 * mOverflingDistance; + } + } overScrollBy(x - oldX, y - oldY, oldX, oldY, 0, range, - 0, mOverflingDistance, false); + 0, (int) (mMaxOverScroll), false); onScrollChanged(mScrollX, mOwnScrollY, oldX, oldY); - - if (canOverscroll) { - // TODO: Overscroll -// if (y < 0 && oldY >= 0) { -// mEdgeGlowTop.onAbsorb((int) mScroller.getCurrVelocity()); -// } else if (y > range && oldY <= range) { -// mEdgeGlowBottom.onAbsorb((int) mScroller.getCurrVelocity()); -// } - } - updateChildren(); } // Keep on drawing until the animation has finished. @@ -725,6 +773,81 @@ public class NotificationStackScrollLayout extends ViewGroup } } + @Override + protected int computeVerticalScrollRange() { + // needed for the overScroller + return mContentHeight; + } + + /** + * Set the amount of overScrolled pixels which will force the view to apply a rubber-banded + * overscroll effect based on numPixels. By default this will also cancel animations on the + * same overScroll edge. + * + * @param numPixels The amount of pixels to overScroll by. These will be scaled according to + * the rubber-banding logic. + * @param onTop Should the effect be applied on top of the scroller. + * @param animate Should an animation be performed. + */ + public void setOverScrolledPixels(float numPixels, boolean onTop, boolean animate) { + setOverScrollAmount(numPixels * RUBBER_BAND_FACTOR, onTop, animate, true); + } + + /** + * Set the effective overScroll amount which will be directly reflected in the layout. + * By default this will also cancel animations on the same overScroll edge. + * + * @param amount The amount to overScroll by. + * @param onTop Should the effect be applied on top of the scroller. + * @param animate Should an animation be performed. + */ + public void setOverScrollAmount(float amount, boolean onTop, boolean animate) { + setOverScrollAmount(amount, onTop, animate, true); + } + + /** + * Set the effective overScroll amount which will be directly reflected in the layout. + * + * @param amount The amount to overScroll by. + * @param onTop Should the effect be applied on top of the scroller. + * @param animate Should an animation be performed. + * @param cancelAnimators Should running animations be cancelled. + */ + public void setOverScrollAmount(float amount, boolean onTop, boolean animate, + boolean cancelAnimators) { + if (cancelAnimators) { + mStateAnimator.cancelOverScrollAnimators(onTop); + } + setOverScrollAmountInternal(amount, onTop, animate); + } + + private void setOverScrollAmountInternal(float amount, boolean onTop, boolean animate) { + amount = Math.max(0, amount); + if (animate) { + mStateAnimator.animateOverScrollToAmount(amount, onTop); + } else { + setOverScrolledPixels(amount / RUBBER_BAND_FACTOR, onTop); + mAmbientState.setOverScrollAmount(amount, onTop); + requestChildrenUpdate(); + } + } + + public float getCurrentOverScrollAmount(boolean top) { + return mAmbientState.getOverScrollAmount(top); + } + + public float getCurrentOverScrolledPixels(boolean top) { + return top? mOverScrolledTopPixels : mOverScrolledBottomPixels; + } + + private void setOverScrolledPixels(float amount, boolean onTop) { + if (onTop) { + mOverScrolledTopPixels = amount; + } else { + mOverScrolledBottomPixels = amount; + } + } + private void customScrollTo(int y) { mOwnScrollY = y; updateChildren(); @@ -738,18 +861,41 @@ public class NotificationStackScrollLayout extends ViewGroup final int oldY = mOwnScrollY; mScrollX = scrollX; mOwnScrollY = scrollY; - invalidateParentIfNeeded(); - onScrollChanged(mScrollX, mOwnScrollY, oldX, oldY); if (clampedY) { - mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0, getScrollRange()); + springBack(); + } else { + onScrollChanged(mScrollX, mOwnScrollY, oldX, oldY); + invalidateParentIfNeeded(); + updateChildren(); } - updateChildren(); } else { customScrollTo(scrollY); scrollTo(scrollX, mScrollY); } } + private void springBack() { + int scrollRange = getScrollRange(); + boolean overScrolledTop = mOwnScrollY <= 0; + boolean overScrolledBottom = mOwnScrollY >= scrollRange; + if (overScrolledTop || overScrolledBottom) { + boolean onTop; + float newAmount; + if (overScrolledTop) { + onTop = true; + newAmount = -mOwnScrollY; + mOwnScrollY = 0; + } else { + onTop = false; + newAmount = mOwnScrollY - scrollRange; + mOwnScrollY = scrollRange; + } + setOverScrollAmount(newAmount, onTop, false); + setOverScrollAmount(0.0f, onTop, true); + mScroller.forceFinished(true); + } + } + private int getScrollRange() { int scrollRange = 0; ExpandableView firstChild = (ExpandableView) getFirstChildNotGone(); @@ -841,7 +987,23 @@ public class NotificationStackScrollLayout extends ViewGroup int height = (int) getLayoutHeight(); int bottom = getContentHeight(); - mScroller.fling(mScrollX, mOwnScrollY, 0, velocityY, 0, 0, 0, + float topAmount = getCurrentOverScrollAmount(true); + float bottomAmount = getCurrentOverScrollAmount(false); + if (velocityY < 0 && topAmount > 0) { + mOwnScrollY -= (int) topAmount; + setOverScrollAmount(0, true, false); + mMaxOverScroll = Math.abs(velocityY) / 1000f * RUBBER_BAND_FACTOR + * mOverflingDistance + topAmount; + } else if (velocityY > 0 && bottomAmount > 0) { + mOwnScrollY += bottomAmount; + setOverScrollAmount(0, false, false); + mMaxOverScroll = Math.abs(velocityY) / 1000f * RUBBER_BAND_FACTOR + * mOverflingDistance + bottomAmount; + } else { + // it will be set once we reach the boundary + mMaxOverScroll = 0.0f; + } + mScroller.fling(mScrollX, mOwnScrollY, 1, velocityY, 0, 0, 0, Math.max(0, bottom - height), 0, height/2); postInvalidateOnAnimation(); @@ -853,11 +1015,12 @@ public class NotificationStackScrollLayout extends ViewGroup recycleVelocityTracker(); - // TODO: Overscroll -// if (mEdgeGlowTop != null) { -// mEdgeGlowTop.onRelease(); -// mEdgeGlowBottom.onRelease(); -// } + if (getCurrentOverScrollAmount(true /* onTop */) > 0) { + setOverScrollAmount(0, true /* onTop */, true /* animate */); + } + if (getCurrentOverScrollAmount(false /* onTop */) > 0) { + setOverScrollAmount(0, false /* onTop */, true /* animate */); + } } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java index bd9de82..d572ea5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java @@ -128,7 +128,10 @@ public class StackScrollAlgorithm { algorithmState.scrolledPixelsTop = 0; algorithmState.itemsInBottomStack = 0.0f; algorithmState.partialInBottom = 0.0f; - algorithmState.scrollY = ambientState.getScrollY() + mCollapsedSize; + float topOverScroll = ambientState.getOverScrollAmount(true /* onTop */); + float bottomOverScroll = ambientState.getOverScrollAmount(false /* onTop */); + algorithmState.scrollY = (int) (ambientState.getScrollY() + mCollapsedSize + + bottomOverScroll - topOverScroll); updateVisibleChildren(resultState, algorithmState); @@ -215,7 +218,6 @@ public class StackScrollAlgorithm { * * @param resultState The result state to update if a change to the properties of a child occurs * @param algorithmState The state in which the current pass of the algorithm is currently in - * and which will be updated */ private void updatePositionsForState(StackScrollState resultState, StackScrollAlgorithmState algorithmState) { @@ -295,7 +297,7 @@ public class StackScrollAlgorithm { // The first card is always rendered. if (i == 0) { childViewState.alpha = 1.0f; - childViewState.yTranslation = 0; + childViewState.yTranslation = Math.max(mCollapsedSize - algorithmState.scrollY, 0); childViewState.location = StackScrollState.ViewState.LOCATION_FIRST_CARD; } if (childViewState.location == StackScrollState.ViewState.LOCATION_UNKNOWN) { @@ -454,7 +456,6 @@ public class StackScrollAlgorithm { * * @param resultState The result state to update if a height change of an child occurs * @param algorithmState The state in which the current pass of the algorithm is currently in - * and which will be updated */ private void findNumberOfItemsInTopStackAndUpdateState(StackScrollState resultState, StackScrollAlgorithmState algorithmState) { @@ -472,7 +473,7 @@ public class StackScrollAlgorithm { + childHeight + mPaddingBetweenElements; if (yPositionInScrollView < algorithmState.scrollY) { - if (i == 0 && algorithmState.scrollY == mCollapsedSize) { + if (i == 0 && algorithmState.scrollY <= mCollapsedSize) { // The starting position of the bottom stack peek int bottomPeekStart = mInnerHeight - mBottomStackPeekSize; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java index ca383aa..5ac51f8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java @@ -18,7 +18,6 @@ package com.android.systemui.statusbar.stack; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; -import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.animation.PropertyValuesHolder; import android.animation.ValueAnimator; @@ -73,6 +72,9 @@ public class StackStateAnimator { private AnimationFilter mAnimationFilter = new AnimationFilter(); private long mCurrentLength; + private ValueAnimator mTopOverScrollAnimator; + private ValueAnimator mBottomOverScrollAnimator; + public StackStateAnimator(NotificationStackScrollLayout hostLayout) { mHostLayout = hostLayout; mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(hostLayout.getContext(), @@ -510,7 +512,7 @@ public class StackStateAnimator { ArrayList<NotificationStackScrollLayout.AnimationEvent> animationEvents, StackScrollState finalState) { mNewEvents.clear(); - for (NotificationStackScrollLayout.AnimationEvent event: animationEvents) { + for (NotificationStackScrollLayout.AnimationEvent event : animationEvents) { View changingView = event.changingView; if (!mHandledEvents.contains(event)) { if (event.animationType == NotificationStackScrollLayout.AnimationEvent @@ -532,4 +534,34 @@ public class StackStateAnimator { } } } + + public void animateOverScrollToAmount(float targetAmount, final boolean onTop) { + final float startOverScrollAmount = mHostLayout.getCurrentOverScrollAmount(onTop); + cancelOverScrollAnimators(onTop); + ValueAnimator overScrollAnimator = ValueAnimator.ofFloat(startOverScrollAmount, + targetAmount); + overScrollAnimator.setDuration(ANIMATION_DURATION_STANDARD); + overScrollAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + float currentOverScroll = (float) animation.getAnimatedValue(); + mHostLayout.setOverScrollAmount(currentOverScroll, onTop, false /* animate */, + false /* cancelAnimators */); + } + }); + overScrollAnimator.setInterpolator(mFastOutSlowInInterpolator); + overScrollAnimator.start(); + if (onTop) { + mTopOverScrollAnimator = overScrollAnimator; + } else { + mBottomOverScrollAnimator = overScrollAnimator; + } + } + + public void cancelOverScrollAnimators(boolean onTop) { + ValueAnimator currentAnimator = onTop ? mTopOverScrollAnimator : mBottomOverScrollAnimator; + if (currentAnimator != null) { + currentAnimator.cancel(); + } + } } |