From 708a6c120da6750d281195ef15a240a5627efed4 Mon Sep 17 00:00:00 2001 From: Selim Cinek Date: Wed, 28 May 2014 14:16:02 +0200 Subject: [PATCH] Introduced animations for the clipTopAmount of notifications. Bug: 14081264 Change-Id: I09ca8161807d9dea7ca118601ddff9a28c373de5 --- .../systemui/statusbar/stack/AnimationFilter.java | 8 ++ .../stack/NotificationStackScrollLayout.java | 5 + .../statusbar/stack/StackScrollAlgorithm.java | 64 ++++++++++++ .../systemui/statusbar/stack/StackScrollState.java | 108 ++++++++------------- .../statusbar/stack/StackStateAnimator.java | 66 ++++++++++++- 5 files changed, 180 insertions(+), 71 deletions(-) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AnimationFilter.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AnimationFilter.java index 5e2d06b70196..cf56fa57120d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AnimationFilter.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AnimationFilter.java @@ -27,6 +27,7 @@ public class AnimationFilter { boolean animateZ; boolean animateScale; boolean animateHeight; + boolean animateTopInset; boolean animateDimmed; boolean hasDelays; @@ -60,6 +61,11 @@ public class AnimationFilter { return this; } + public AnimationFilter animateTopInset() { + animateTopInset = true; + return this; + } + public AnimationFilter animateDimmed() { animateDimmed = true; return this; @@ -84,6 +90,7 @@ public class AnimationFilter { animateZ |= filter.animateZ; animateScale |= filter.animateScale; animateHeight |= filter.animateHeight; + animateTopInset |= filter.animateTopInset; animateDimmed |= filter.animateDimmed; hasDelays |= filter.hasDelays; } @@ -94,6 +101,7 @@ public class AnimationFilter { animateZ = false; animateScale = false; animateHeight = false; + animateTopInset = false; animateDimmed = false; hasDelays = false; } 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 079b184e510d..58176b9a7d56 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java @@ -1599,6 +1599,7 @@ public class NotificationStackScrollLayout extends ViewGroup new AnimationFilter() .animateAlpha() .animateHeight() + .animateTopInset() .animateY() .animateZ() .hasDelays(), @@ -1607,6 +1608,7 @@ public class NotificationStackScrollLayout extends ViewGroup new AnimationFilter() .animateAlpha() .animateHeight() + .animateTopInset() .animateY() .animateZ() .hasDelays(), @@ -1615,6 +1617,7 @@ public class NotificationStackScrollLayout extends ViewGroup new AnimationFilter() .animateAlpha() .animateHeight() + .animateTopInset() .animateY() .animateZ() .hasDelays(), @@ -1623,6 +1626,7 @@ public class NotificationStackScrollLayout extends ViewGroup new AnimationFilter() .animateAlpha() .animateHeight() + .animateTopInset() .animateY() .animateDimmed() .animateScale() @@ -1651,6 +1655,7 @@ public class NotificationStackScrollLayout extends ViewGroup new AnimationFilter() .animateAlpha() .animateHeight() + .animateTopInset() .animateY() .animateZ() }; 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 bd2541a5e5c4..2b52c7e5ef45 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java @@ -49,6 +49,7 @@ public class StackScrollAlgorithm { private int mBottomStackPeekSize; private int mZDistanceBetweenElements; private int mZBasicHeight; + private int mRoundedRectCornerRadius; private StackIndentationFunctor mTopStackIndentationFunctor; private StackIndentationFunctor mBottomStackIndentationFunctor; @@ -111,6 +112,8 @@ public class StackScrollAlgorithm { mZBasicHeight = (MAX_ITEMS_IN_BOTTOM_STACK + 1) * mZDistanceBetweenElements; mBottomStackSlowDownLength = context.getResources() .getDimensionPixelSize(R.dimen.bottom_stack_slow_down_length); + mRoundedRectCornerRadius = context.getResources().getDimensionPixelSize( + com.android.internal.R.dimen.notification_quantum_rounded_rect_radius); } @@ -146,6 +149,67 @@ public class StackScrollAlgorithm { handleDraggedViews(ambientState, resultState, algorithmState); updateDimmedActivated(ambientState, resultState, algorithmState); + updateClipping(resultState, algorithmState); + } + + private void updateClipping(StackScrollState resultState, + StackScrollAlgorithmState algorithmState) { + float previousNotificationEnd = 0; + float previousNotificationStart = 0; + boolean previousNotificationIsSwiped = false; + int childCount = algorithmState.visibleChildren.size(); + for (int i = 0; i < childCount; i++) { + ExpandableView child = algorithmState.visibleChildren.get(i); + StackScrollState.ViewState state = resultState.getViewStateForView(child); + float newYTranslation = state.yTranslation; + int newHeight = state.height; + // apply clipping and shadow + float newNotificationEnd = newYTranslation + newHeight; + + // In the unlocked shade we have to clip a little bit higher because of the rounded + // corners of the notifications. + float clippingCorrection = state.dimmed ? 0 : mRoundedRectCornerRadius; + + // When the previous notification is swiped, we don't clip the content to the + // bottom of it. + float clipHeight = previousNotificationIsSwiped + ? newHeight + : newNotificationEnd - (previousNotificationEnd - clippingCorrection); + + updateChildClippingAndBackground(state, newHeight, clipHeight, + (int) (newHeight - (previousNotificationStart - newYTranslation))); + + if (!child.isTransparent()) { + // Only update the previous values if we are not transparent, + // otherwise we would clip to a transparent view. + previousNotificationStart = newYTranslation + child.getClipTopAmount(); + previousNotificationEnd = newNotificationEnd; + previousNotificationIsSwiped = child.getTranslationX() != 0; + } + } + } + + /** + * Updates the shadow outline and the clipping for a view. + * + * @param state the viewState to update + * @param realHeight the currently applied height of the view + * @param clipHeight the desired clip height, the rest of the view will be clipped from the top + * @param backgroundHeight the desired background height. The shadows of the view will be + * based on this height and the content will be clipped from the top + */ + private void updateChildClippingAndBackground(StackScrollState.ViewState state, int realHeight, + float clipHeight, int backgroundHeight) { + if (realHeight > clipHeight) { + state.topOverLap = (int) (realHeight - clipHeight); + } else { + state.topOverLap = 0; + } + if (realHeight > backgroundHeight) { + state.clipTopAmount = (realHeight - backgroundHeight); + } else { + state.clipTopAmount = 0; + } } /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java index 44e10bea8a2d..94cb16da39f6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java @@ -16,7 +16,6 @@ package com.android.systemui.statusbar.stack; -import android.graphics.Outline; import android.graphics.Rect; import android.util.Log; import android.view.View; @@ -37,15 +36,12 @@ public class StackScrollState { private static final String CHILD_NOT_FOUND_TAG = "StackScrollStateNoSuchChild"; private final ViewGroup mHostView; - private final int mRoundedRectCornerRadius; private Map mStateMap; private final Rect mClipRect = new Rect(); public StackScrollState(ViewGroup hostView) { mHostView = hostView; mStateMap = new HashMap(); - mRoundedRectCornerRadius = mHostView.getResources().getDimensionPixelSize( - com.android.internal.R.dimen.notification_quantum_rounded_rect_radius); } public ViewGroup getHostView() { @@ -83,9 +79,6 @@ public class StackScrollState { */ public void apply() { int numChildren = mHostView.getChildCount(); - float previousNotificationEnd = 0; - float previousNotificationStart = 0; - boolean previousNotificationIsSwiped = false; for (int i = 0; i < numChildren; i++) { ExpandableView child = (ExpandableView) mHostView.getChildAt(i); ViewState state = mStateMap.get(child); @@ -155,39 +148,41 @@ public class StackScrollState { // apply dimming child.setDimmed(state.dimmed, false /* animate */); - // apply clipping and shadow - float newNotificationEnd = newYTranslation + newHeight; - - // In the unlocked shade we have to clip a little bit higher because of the rounded - // corners of the notifications. - float clippingCorrection = state.dimmed ? 0 : mRoundedRectCornerRadius; - - // When the previous notification is swiped, we don't clip the content to the - // bottom of it. - float clipHeight = previousNotificationIsSwiped - ? newHeight - : newNotificationEnd - (previousNotificationEnd - clippingCorrection); - - updateChildClippingAndBackground(child, newHeight, - clipHeight, - (int) (newHeight - (previousNotificationStart - newYTranslation))); - - if (!child.isTransparent()) { - // Only update the previous values if we are not transparent, - // otherwise we would clip to a transparent view. - previousNotificationStart = newYTranslation + child.getClipTopAmount(); - previousNotificationEnd = newNotificationEnd; - previousNotificationIsSwiped = child.getTranslationX() != 0; + float oldClipTopAmount = child.getClipTopAmount(); + if (oldClipTopAmount != state.clipTopAmount) { + child.setClipTopAmount(state.clipTopAmount); + } + + if (state.topOverLap != 0) { + updateChildClip(child, newHeight, state.topOverLap); + } else { + child.setClipBounds(null); } if(child instanceof SpeedBumpView) { - performSpeedBumpAnimation(i, (SpeedBumpView) child, newNotificationEnd, + float speedBumpEnd = newYTranslation + newHeight; + performSpeedBumpAnimation(i, (SpeedBumpView) child, speedBumpEnd, newYTranslation); } } } } + /** + * Updates the clipping of a view + * + * @param child the view to update + * @param height the currently applied height of the view + * @param clipInset how much should this view be clipped from the top + */ + private void updateChildClip(View child, int height, int clipInset) { + mClipRect.set(0, + clipInset, + child.getWidth(), + height); + child.setClipBounds(mClipRect); + } + private void performSpeedBumpAnimation(int i, SpeedBumpView speedBump, float speedBumpEnd, float speedBumpStart) { View nextChild = getNextChildNotGone(i); @@ -216,45 +211,6 @@ public class StackScrollState { return null; } - /** - * Updates the shadow outline and the clipping for a view. - * - * @param child the view to update - * @param realHeight the currently applied height of the view - * @param clipHeight the desired clip height, the rest of the view will be clipped from the top - * @param backgroundHeight the desired background height. The shadows of the view will be - * based on this height and the content will be clipped from the top - */ - private void updateChildClippingAndBackground(ExpandableView child, int realHeight, - float clipHeight, int backgroundHeight) { - if (realHeight > clipHeight) { - updateChildClip(child, realHeight, clipHeight); - } else { - child.setClipBounds(null); - } - if (realHeight > backgroundHeight) { - child.setClipTopAmount(realHeight - backgroundHeight); - } else { - child.setClipTopAmount(0); - } - } - - /** - * Updates the clipping of a view - * - * @param child the view to update - * @param height the currently applied height of the view - * @param clipHeight the desired clip height, the rest of the view will be clipped from the top - */ - private void updateChildClip(View child, int height, float clipHeight) { - int clipInset = (int) (height - clipHeight); - mClipRect.set(0, - clipInset, - child.getWidth(), - height); - child.setClipBounds(mClipRect); - } - public static class ViewState { // These are flags such that we can create masks for filtering. @@ -276,6 +232,18 @@ public class StackScrollState { boolean dimmed; /** + * The amount which the view should be clipped from the top. This is calculated to + * perceive consistent shadows. + */ + int clipTopAmount; + + /** + * How much does the child overlap with the previous view on the top? Can be used for + * a clipping optimization + */ + int topOverLap; + + /** * The index of the view, only accounting for views not equal to GONE */ int notGoneIndex; 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 f019e6cbffd7..f41ab3a03a91 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java @@ -133,10 +133,11 @@ public class StackStateAnimator { boolean scaleChanging = child.getScaleX() != viewState.scale; boolean alphaChanging = alpha != child.getAlpha(); boolean heightChanging = viewState.height != child.getActualHeight(); + boolean topInsetChanging = viewState.clipTopAmount != child.getClipTopAmount(); boolean wasAdded = mNewAddChildren.contains(child); boolean hasDelays = mAnimationFilter.hasDelays; boolean isDelayRelevant = yTranslationChanging || zTranslationChanging || scaleChanging || - alphaChanging || heightChanging; + alphaChanging || heightChanging || topInsetChanging; long delay = 0; if (hasDelays && isDelayRelevant || wasAdded) { delay = calculateChildAnimationDelay(viewState, finalState); @@ -167,6 +168,11 @@ public class StackStateAnimator { startHeightAnimation(child, viewState, delay); } + // start top inset animation + if (topInsetChanging) { + startInsetAnimation(child, viewState, delay); + } + // start dimmed animation child.setDimmed(viewState.dimmed, mAnimationFilter.animateDimmed); @@ -280,6 +286,64 @@ public class StackStateAnimator { child.setTag(TAG_END_HEIGHT, newEndValue); } + private void startInsetAnimation(final ExpandableView child, + StackScrollState.ViewState viewState, long delay) { + Integer previousStartValue = getChildTag(child, TAG_START_TOP_INSET); + Integer previousEndValue = getChildTag(child, TAG_END_TOP_INSET); + int newEndValue = viewState.clipTopAmount; + if (previousEndValue != null && previousEndValue == newEndValue) { + return; + } + ValueAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_TOP_INSET); + if (!mAnimationFilter.animateTopInset) { + // just a local update was performed + if (previousAnimator != null) { + // we need to increase all animation keyframes of the previous animator by the + // relative change to the end value + PropertyValuesHolder[] values = previousAnimator.getValues(); + int relativeDiff = newEndValue - previousEndValue; + int newStartValue = previousStartValue + relativeDiff; + values[0].setIntValues(newStartValue, newEndValue); + child.setTag(TAG_START_TOP_INSET, newStartValue); + child.setTag(TAG_END_TOP_INSET, newEndValue); + previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime()); + return; + } else { + // no new animation needed, let's just apply the value + child.setClipTopAmount(newEndValue); + return; + } + } + + ValueAnimator animator = ValueAnimator.ofInt(child.getClipTopAmount(), newEndValue); + animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + child.setClipTopAmount((int) animation.getAnimatedValue()); + } + }); + animator.setInterpolator(mFastOutSlowInInterpolator); + long newDuration = cancelAnimatorAndGetNewDuration(previousAnimator); + animator.setDuration(newDuration); + if (delay > 0 && (previousAnimator == null || !previousAnimator.isRunning())) { + animator.setStartDelay(delay); + } + animator.addListener(getGlobalAnimationFinishedListener()); + // remove the tag when the animation is finished + animator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + child.setTag(TAG_ANIMATOR_TOP_INSET, null); + child.setTag(TAG_START_TOP_INSET, null); + child.setTag(TAG_END_TOP_INSET, null); + } + }); + startAnimator(animator); + child.setTag(TAG_ANIMATOR_TOP_INSET, animator); + child.setTag(TAG_START_TOP_INSET, child.getClipTopAmount()); + child.setTag(TAG_END_TOP_INSET, newEndValue); + } + private void startAlphaAnimation(final ExpandableView child, final StackScrollState.ViewState viewState, long delay) { Float previousStartValue = getChildTag(child,TAG_START_ALPHA); -- 2.11.0