diff options
author | Selim Cinek <cinek@google.com> | 2014-05-19 16:27:37 +0200 |
---|---|---|
committer | Selim Cinek <cinek@google.com> | 2014-05-23 14:43:17 +0200 |
commit | 8efa6dde2b4f2cdbf046b87b7366404c3cc46219 (patch) | |
tree | 413eaf999db21d68e51e99757ca20693336ba514 /packages/SystemUI/src/com/android | |
parent | 2ecba24c48cf42b0c589d4390f7331314e57c320 (diff) | |
download | frameworks_base-8efa6dde2b4f2cdbf046b87b7366404c3cc46219.zip frameworks_base-8efa6dde2b4f2cdbf046b87b7366404c3cc46219.tar.gz frameworks_base-8efa6dde2b4f2cdbf046b87b7366404c3cc46219.tar.bz2 |
Improved the animation logic of the stack scroller.
Newly introduced appear and disappear animations when in the shade.
Also introduced individual child delays such that notifications
appear in a slightly more appealing quantum way.
Also fixed a racecondition, such that added notifications already
have their final visibility state when they are added to the scroller.
Bug: 14081264
Change-Id: I18f5c57c2206f8e05996253981f540e97521e102
Diffstat (limited to 'packages/SystemUI/src/com/android')
13 files changed, 634 insertions, 111 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java index ac16164..e3dac4a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java @@ -21,15 +21,25 @@ import android.animation.AnimatorListenerAdapter; import android.animation.ObjectAnimator; import android.animation.ValueAnimator; import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.BitmapShader; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffColorFilter; +import android.graphics.RectF; +import android.graphics.Shader; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; import android.view.ViewConfiguration; import android.view.animation.AnimationUtils; import android.view.animation.Interpolator; +import android.view.animation.LinearInterpolator; import android.view.animation.PathInterpolator; - import com.android.systemui.R; +import com.android.systemui.statusbar.stack.StackStateAnimator; /** * Base class for both {@link ExpandableNotificationRow} and {@link NotificationOverflowContainer} @@ -41,6 +51,36 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView private static final int BACKGROUND_ANIMATION_LENGTH_MS = 220; private static final int ACTIVATE_ANIMATION_LENGTH = 220; + /** + * The amount of width, which is kept in the end when performing a disappear animation (also + * the amount from which the horizontal appearing begins) + */ + private static final float HORIZONTAL_COLLAPSED_REST_PARTIAL = 0.05f; + + /** + * At which point from [0,1] does the horizontal collapse animation end (or start when + * expanding)? 1.0 meaning that it ends immediately and 0.0 that it is continuously animated. + */ + private static final float HORIZONTAL_ANIMATION_END = 0.2f; + + /** + * At which point from [0,1] does the alpha animation end (or start when + * expanding)? 1.0 meaning that it ends immediately and 0.0 that it is continuously animated. + */ + private static final float ALPHA_ANIMATION_END = 0.0f; + + /** + * At which point from [0,1] does the horizontal collapse animation start (or start when + * expanding)? 1.0 meaning that it starts immediately and 0.0 that it is animated at all. + */ + private static final float HORIZONTAL_ANIMATION_START = 1.0f; + + /** + * At which point from [0,1] does the vertical collapse animation start (or end when + * expanding) 1.0 meaning that it starts immediately and 0.0 that it is animated at all. + */ + private static final float VERTICAL_ANIMATION_START = 1.0f; + private static final Interpolator ACTIVATE_INVERSE_INTERPOLATOR = new PathInterpolator(0.6f, 0, 0.5f, 1); private static final Interpolator ACTIVATE_INVERSE_ALPHA_INTERPOLATOR @@ -53,6 +93,7 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView private int mBgTint = 0; private int mDimmedBgTint = 0; + private final int mRoundedRectCornerRadius; /** * Flag to indicate that the notification has been touched once and the second touch will @@ -66,22 +107,41 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView private OnActivatedListener mOnActivatedListener; - private Interpolator mLinearOutSlowInInterpolator; - private Interpolator mFastOutSlowInInterpolator; + private final Interpolator mLinearOutSlowInInterpolator; + private final Interpolator mFastOutSlowInInterpolator; + private final Interpolator mSlowOutFastInInterpolator; + private final Interpolator mSlowOutLinearInInterpolator; + private final Interpolator mLinearInterpolator; + private Interpolator mCurrentAppearInterpolator; + private Interpolator mCurrentAlphaInterpolator; private NotificationBackgroundView mBackgroundNormal; private NotificationBackgroundView mBackgroundDimmed; private ObjectAnimator mBackgroundAnimator; + private RectF mAppearAnimationRect = new RectF(); + private PorterDuffColorFilter mAppearAnimationFilter; + private float mAnimationTranslationY; + private boolean mDrawingAppearAnimation; + private Paint mAppearPaint = new Paint(); + private ValueAnimator mAppearAnimator; + private float mAppearAnimationFraction = -1.0f; + private float mAppearAnimationTranslation; public ActivatableNotificationView(Context context, AttributeSet attrs) { super(context, attrs); mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(context, android.R.interpolator.fast_out_slow_in); + mSlowOutFastInInterpolator = new PathInterpolator(0.8f, 0.0f, 0.6f, 1.0f); mLinearOutSlowInInterpolator = AnimationUtils.loadInterpolator(context, android.R.interpolator.linear_out_slow_in); + mSlowOutLinearInInterpolator = new PathInterpolator(0.8f, 0.0f, 1.0f, 1.0f); + mLinearInterpolator = new LinearInterpolator(); setClipChildren(false); setClipToPadding(false); + mAppearAnimationFilter = new PorterDuffColorFilter(0, PorterDuff.Mode.SRC_ATOP); + mRoundedRectCornerRadius = getResources().getDimensionPixelSize( + com.android.internal.R.dimen.notification_quantum_rounded_rect_radius); } @Override @@ -316,6 +376,202 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView mBackgroundDimmed.setClipTopAmount(clipTopAmount); } + @Override + public void performRemoveAnimation(float translationDirection, Runnable onFinishedRunnable) { + enableAppearDrawing(true); + if (mDrawingAppearAnimation) { + startAppearAnimation(false /* isAppearing */, translationDirection, + 0, onFinishedRunnable); + } + } + + @Override + public void performAddAnimation(long delay) { + enableAppearDrawing(true); + if (mDrawingAppearAnimation) { + startAppearAnimation(true /* isAppearing */, -1.0f, delay, null); + } + } + + private void startAppearAnimation(boolean isAppearing, + float translationDirection, long delay, final Runnable onFinishedRunnable) { + if (mAppearAnimator != null) { + mAppearAnimator.cancel(); + } + mAnimationTranslationY = translationDirection * mActualHeight; + if (mAppearAnimationFraction == -1.0f) { + // not initialized yet, we start anew + if (isAppearing) { + mAppearAnimationFraction = 0.0f; + mAppearAnimationTranslation = mAnimationTranslationY; + } else { + mAppearAnimationFraction = 1.0f; + mAppearAnimationTranslation = 0; + } + } + + float targetValue; + if (isAppearing) { + mCurrentAppearInterpolator = mSlowOutFastInInterpolator; + mCurrentAlphaInterpolator = mLinearOutSlowInInterpolator; + targetValue = 1.0f; + } else { + mCurrentAppearInterpolator = mFastOutSlowInInterpolator; + mCurrentAlphaInterpolator = mSlowOutLinearInInterpolator; + targetValue = 0.0f; + } + mAppearAnimator = ValueAnimator.ofFloat(mAppearAnimationFraction, + targetValue); + mAppearAnimator.setInterpolator(mLinearInterpolator); + mAppearAnimator.setDuration( + (long) (StackStateAnimator.ANIMATION_DURATION_APPEAR_DISAPPEAR + * Math.abs(mAppearAnimationFraction - targetValue))); + mAppearAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + mAppearAnimationFraction = (float) animation.getAnimatedValue(); + updateAppearAnimationAlpha(); + updateAppearRect(); + invalidate(); + } + }); + if (delay > 0) { + // we need to apply the initial state already to avoid drawn frames in the wrong state + updateAppearAnimationAlpha(); + updateAppearRect(); + mAppearAnimator.setStartDelay(delay); + } + mAppearAnimator.addListener(new AnimatorListenerAdapter() { + private boolean mWasCancelled; + + @Override + public void onAnimationEnd(Animator animation) { + if (onFinishedRunnable != null) { + onFinishedRunnable.run(); + } + if (!mWasCancelled) { + mAppearAnimationFraction = -1; + setOutlineRect(null); + enableAppearDrawing(false); + } + } + + @Override + public void onAnimationStart(Animator animation) { + mWasCancelled = false; + } + + @Override + public void onAnimationCancel(Animator animation) { + mWasCancelled = true; + } + }); + mAppearAnimator.start(); + } + + private void updateAppearRect() { + float inverseFraction = (1.0f - mAppearAnimationFraction); + float translationFraction = mCurrentAppearInterpolator.getInterpolation(inverseFraction); + float translateYTotalAmount = translationFraction * mAnimationTranslationY; + mAppearAnimationTranslation = translateYTotalAmount; + + // handle width animation + float widthFraction = (inverseFraction - (1.0f - HORIZONTAL_ANIMATION_START)) + / (HORIZONTAL_ANIMATION_START - HORIZONTAL_ANIMATION_END); + widthFraction = Math.min(1.0f, Math.max(0.0f, widthFraction)); + widthFraction = mCurrentAppearInterpolator.getInterpolation(widthFraction); + float left = (getWidth() * (0.5f - HORIZONTAL_COLLAPSED_REST_PARTIAL / 2.0f) * + widthFraction); + float right = getWidth() - left; + + // handle top animation + float heightFraction = (inverseFraction - (1.0f - VERTICAL_ANIMATION_START)) / + VERTICAL_ANIMATION_START; + heightFraction = Math.max(0.0f, heightFraction); + heightFraction = mCurrentAppearInterpolator.getInterpolation(heightFraction); + + float top; + float bottom; + if (mAnimationTranslationY > 0.0f) { + bottom = mActualHeight - heightFraction * mAnimationTranslationY * 0.1f + - translateYTotalAmount; + top = bottom * heightFraction; + } else { + top = heightFraction * (mActualHeight + mAnimationTranslationY) * 0.1f - + translateYTotalAmount; + bottom = mActualHeight * (1 - heightFraction) + top * heightFraction; + } + mAppearAnimationRect.set(left, top, right, bottom); + setOutlineRect(left, top + mAppearAnimationTranslation, right, + bottom + mAppearAnimationTranslation); + } + + private void updateAppearAnimationAlpha() { + int backgroundColor = getBackgroundColor(); + if (backgroundColor != -1) { + float contentAlphaProgress = mAppearAnimationFraction; + contentAlphaProgress = contentAlphaProgress / (1.0f - ALPHA_ANIMATION_END); + contentAlphaProgress = Math.min(1.0f, contentAlphaProgress); + contentAlphaProgress = mCurrentAlphaInterpolator.getInterpolation(contentAlphaProgress); + int sourceColor = Color.argb((int) (255 * (1.0f - contentAlphaProgress)), + Color.red(backgroundColor), Color.green(backgroundColor), + Color.blue(backgroundColor)); + mAppearAnimationFilter.setColor(sourceColor); + mAppearPaint.setColorFilter(mAppearAnimationFilter); + } + } + + private int getBackgroundColor() { + // TODO: get real color + return 0xfffafafa; + } + + /** + * When we draw the appear animation, we render the view in a bitmap and render this bitmap + * as a shader of a rect. This call creates the Bitmap and switches the drawing mode, + * such that the normal drawing of the views does not happen anymore. + * + * @param enable Should it be enabled. + */ + private void enableAppearDrawing(boolean enable) { + if (enable != mDrawingAppearAnimation) { + if (enable) { + if (getWidth() == 0 || getActualHeight() == 0) { + // TODO: This should not happen, but it can during expansion. Needs + // investigation + return; + } + Bitmap bitmap = Bitmap.createBitmap(getWidth(), getActualHeight(), + Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(bitmap); + draw(canvas); + mAppearPaint.setShader(new BitmapShader(bitmap, Shader.TileMode.CLAMP, + Shader.TileMode.CLAMP)); + } else { + mAppearPaint.setShader(null); + } + mDrawingAppearAnimation = enable; + invalidate(); + } + } + + @Override + protected void dispatchDraw(Canvas canvas) { + if (!mDrawingAppearAnimation) { + super.dispatchDraw(canvas); + } else { + drawAppearRect(canvas); + } + } + + private void drawAppearRect(Canvas canvas) { + canvas.save(); + canvas.translate(0, mAppearAnimationTranslation); + canvas.drawRoundRect(mAppearAnimationRect, mRoundedRectCornerRadius, + mRoundedRectCornerRadius, mAppearPaint); + canvas.restore(); + } + public void setOnActivatedListener(OnActivatedListener onActivatedListener) { mOnActivatedListener = onActivatedListener; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java index 457d32e..7bd4894 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java @@ -1039,6 +1039,7 @@ public abstract class BaseStatusBar extends SystemUI implements if (rowParent != null) rowParent.removeView(entry.row); updateRowStates(); updateNotificationIcons(); + updateSpeedBump(); return entry.notification; } @@ -1083,8 +1084,22 @@ public abstract class BaseStatusBar extends SystemUI implements if (DEBUG) { Log.d(TAG, "addNotificationViews: added at " + pos); } - updateNotificationIcons(); updateRowStates(); + updateNotificationIcons(); + updateSpeedBump(); + } + + protected void updateSpeedBump() { + int n = mNotificationData.size(); + int speedBumpIndex = -1; + for (int i = n-1; i >= 0; i--) { + NotificationData.Entry entry = mNotificationData.get(i); + if (entry.row.getVisibility() != View.GONE && speedBumpIndex == -1 + && entry.row.isBelowSpeedBump() ) { + speedBumpIndex = n - 1 - i; + } + } + mStackScroller.updateSpeedBumpIndex(speedBumpIndex); } private void addNotificationViews(IBinder key, StatusBarNotification notification) { @@ -1104,7 +1119,6 @@ public abstract class BaseStatusBar extends SystemUI implements mKeyguardIconOverflowContainer.getIconsView().removeAllViews(); int n = mNotificationData.size(); int visibleNotifications = 0; - int speedBumpIndex = -1; boolean onKeyguard = mState == StatusBarState.KEYGUARD; for (int i = n-1; i >= 0; i--) { NotificationData.Entry entry = mNotificationData.get(i); @@ -1125,17 +1139,14 @@ public abstract class BaseStatusBar extends SystemUI implements mKeyguardIconOverflowContainer.getIconsView().addNotification(entry); } } else { - if (entry.row.getVisibility() == View.GONE) { + boolean wasGone = entry.row.getVisibility() == View.GONE; + entry.row.setVisibility(View.VISIBLE); + if (wasGone) { // notify the scroller of a child addition mStackScroller.generateAddAnimation(entry.row); } - entry.row.setVisibility(View.VISIBLE); visibleNotifications++; } - if (entry.row.getVisibility() != View.GONE && speedBumpIndex == -1 - && entry.row.isBelowSpeedBump() ) { - speedBumpIndex = n - 1 - i; - } } if (onKeyguard && mKeyguardIconOverflowContainer.getIconsView().getChildCount() > 0) { @@ -1143,8 +1154,6 @@ public abstract class BaseStatusBar extends SystemUI implements } else { mKeyguardIconOverflowContainer.setVisibility(View.GONE); } - - mStackScroller.updateSpeedBumpIndex(speedBumpIndex); } private boolean shouldShowOnKeyguard(StatusBarNotification sbn) { @@ -1280,6 +1289,7 @@ public abstract class BaseStatusBar extends SystemUI implements return; } updateRowStates(); + updateSpeedBump(); } catch (RuntimeException e) { // It failed to add cleanly. Log, and remove the view from the panel. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableOutlineView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableOutlineView.java index a42c194..843db04 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableOutlineView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableOutlineView.java @@ -18,8 +18,8 @@ package com.android.systemui.statusbar; import android.content.Context; import android.graphics.Outline; +import android.graphics.RectF; import android.util.AttributeSet; -import android.widget.FrameLayout; /** * Like {@link ExpandableView}, but setting an outline for the height and clipping. @@ -27,9 +27,12 @@ import android.widget.FrameLayout; public abstract class ExpandableOutlineView extends ExpandableView { private final Outline mOutline = new Outline(); + private boolean mCustomOutline; + private float mDensity; public ExpandableOutlineView(Context context, AttributeSet attrs) { super(context, attrs); + mDensity = getResources().getDisplayMetrics().density; } @Override @@ -50,11 +53,37 @@ public abstract class ExpandableOutlineView extends ExpandableView { updateOutline(); } - private void updateOutline() { - mOutline.setRect(0, - mClipTopAmount, - getWidth(), - Math.max(mActualHeight, mClipTopAmount)); + protected void setOutlineRect(RectF rect) { + if (rect != null) { + setOutlineRect(rect.left, rect.top, rect.right, rect.bottom); + } else { + mCustomOutline = false; + updateOutline(); + } + } + + protected void setOutlineRect(float left, float top, float right, float bottom) { + mCustomOutline = true; + + int rectLeft = (int) left; + int rectTop = (int) top; + int rectRight = (int) right; + int rectBottom = (int) bottom; + + // Outlines need to be at least 1 dp + rectBottom = (int) Math.max(top + mDensity, rectBottom); + rectRight = (int) Math.max(left + mDensity, rectRight); + mOutline.setRect(rectLeft, rectTop, rectRight, rectBottom); setOutline(mOutline); } + + private void updateOutline() { + if (!mCustomOutline) { + mOutline.setRect(0, + mClipTopAmount, + getWidth(), + Math.max(mActualHeight, mClipTopAmount)); + setOutline(mOutline); + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java index eaaac10..088f076 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java @@ -205,6 +205,21 @@ public abstract class ExpandableView extends FrameLayout { } /** + * Perform a remove animation on this view. + * + * @param translationDirection The direction value from [-1 ... 1] indicating in which the + * animation should be performed. A value of -1 means that The + * remove animation should be performed upwards, + * such that the child appears to be going away to the top. 1 + * Should mean the opposite. + * @param onFinishedRunnable A runnable which should be run when the animation is finished. + */ + public abstract void performRemoveAnimation(float translationDirection, + Runnable onFinishedRunnable); + + public abstract void performAddAnimation(long delay); + + /** * A listener notifying when {@link #getActualHeight} changes. */ public interface OnHeightChangedListener { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationBackgroundView.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationBackgroundView.java index 3c080fe..1c2ca91 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationBackgroundView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationBackgroundView.java @@ -34,7 +34,6 @@ public class NotificationBackgroundView extends View { public NotificationBackgroundView(Context context, AttributeSet attrs) { super(context, attrs); - setWillNotDraw(false); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SpeedBumpView.java b/packages/SystemUI/src/com/android/systemui/statusbar/SpeedBumpView.java index 8ae503a..a2f8991 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/SpeedBumpView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/SpeedBumpView.java @@ -184,7 +184,7 @@ public class SpeedBumpView extends ExpandableView implements View.OnClickListene } public void performVisibilityAnimation(boolean nowVisible) { - animateDivider(nowVisible); + animateDivider(nowVisible, null /* onFinishedRunnable */); // Animate explanation Text if (mIsExpanded) { @@ -192,7 +192,14 @@ public class SpeedBumpView extends ExpandableView implements View.OnClickListene } } - public void animateDivider(boolean nowVisible) { + /** + * Animate the divider to a new visibility. + * + * @param nowVisible should it now be visible + * @param onFinishedRunnable A runnable which should be run when the animation is + * finished. + */ + public void animateDivider(boolean nowVisible, Runnable onFinishedRunnable) { if (nowVisible != mDividerVisible) { // Animate dividers float endValue = nowVisible ? 1.0f : 0.0f; @@ -204,7 +211,8 @@ public class SpeedBumpView extends ExpandableView implements View.OnClickListene .scaleX(endValue) .scaleY(endValue) .translationX(endTranslationXLeft) - .setInterpolator(mFastOutSlowInInterpolator); + .setInterpolator(mFastOutSlowInInterpolator) + .withEndAction(onFinishedRunnable); mLineRight.animate() .alpha(endValue) .withLayer() @@ -216,6 +224,10 @@ public class SpeedBumpView extends ExpandableView implements View.OnClickListene // Animate dots mDots.performVisibilityAnimation(nowVisible); mDividerVisible = nowVisible; + } else { + if (onFinishedRunnable != null) { + onFinishedRunnable.run(); + } } } @@ -250,6 +262,16 @@ public class SpeedBumpView extends ExpandableView implements View.OnClickListene } } + @Override + public void performRemoveAnimation(float translationDirection, Runnable onFinishedRunnable) { + performVisibilityAnimation(false); + } + + @Override + public void performAddAnimation(long delay) { + performVisibilityAnimation(true); + } + private void resetExplanationText() { mExplanationText.setTranslationY(0); mExplanationText.setVisibility(INVISIBLE); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java index fe7546d..142241c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java @@ -214,7 +214,7 @@ public class NotificationPanelView extends PanelView implements mClockAnimationTarget = -1; } }); - StackStateAnimator.startInstantly(mClockAnimator); + mClockAnimator.start(); return true; } }); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java index 54af2c5..3fed860 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java @@ -2766,6 +2766,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, updateStackScrollerState(); updatePublicMode(); updateRowStates(); + updateSpeedBump(); checkBarModes(); updateNotificationIcons(); updateCarrierLabelVisibility(false); 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 41914ed..5e2d06b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AnimationFilter.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AnimationFilter.java @@ -28,6 +28,7 @@ public class AnimationFilter { boolean animateScale; boolean animateHeight; boolean animateDimmed; + boolean hasDelays; public AnimationFilter animateAlpha() { animateAlpha = true; @@ -39,6 +40,11 @@ public class AnimationFilter { return this; } + public AnimationFilter hasDelays() { + hasDelays = true; + return this; + } + public AnimationFilter animateZ() { animateZ = true; return this; @@ -79,6 +85,7 @@ public class AnimationFilter { animateScale |= filter.animateScale; animateHeight |= filter.animateHeight; animateDimmed |= filter.animateDimmed; + hasDelays |= filter.hasDelays; } private void reset() { @@ -88,5 +95,6 @@ public class AnimationFilter { animateScale = false; animateHeight = 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 90f3d17..079b184 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java @@ -103,6 +103,7 @@ public class NotificationStackScrollLayout extends ViewGroup private ArrayList<View> mChildrenToRemoveAnimated = new ArrayList<View>(); private ArrayList<View> mSnappedBackChildren = new ArrayList<View>(); private ArrayList<View> mDragAnimPendingChildren = new ArrayList<View>(); + private ArrayList<View> mChildrenChangingPositions = new ArrayList<View>(); private ArrayList<AnimationEvent> mAnimationEvents = new ArrayList<AnimationEvent>(); private ArrayList<View> mSwipedOutViews = new ArrayList<View>(); @@ -969,9 +970,24 @@ public class NotificationStackScrollLayout extends ViewGroup } /** + * @return The first child which has visibility unequal to GONE which is currently below the + * given translationY or equal to it. + */ + private View getFirstChildBelowTranlsationY(float translationY) { + int childCount = getChildCount(); + for (int i = 0; i < childCount; i++) { + View child = getChildAt(i); + if (child.getVisibility() != View.GONE && child.getTranslationY() >= translationY) { + return child; + } + } + return null; + } + + /** * @return the last child which has visibility unequal to GONE */ - private View getLastChildNotGone() { + public View getLastChildNotGone() { int childCount = getChildCount(); for (int i = childCount - 1; i >= 0; i--) { View child = getChildAt(i); @@ -1094,23 +1110,41 @@ public class NotificationStackScrollLayout extends ViewGroup @Override protected void onViewRemoved(View child) { super.onViewRemoved(child); + mStackScrollAlgorithm.notifyChildrenChanged(this); + if (mChildrenChangingPositions.contains(child)) { + // This is only a position change, don't do anything special + return; + } ((ExpandableView) child).setOnHeightChangedListener(null); mCurrentStackScrollState.removeViewStateForView(child); - mStackScrollAlgorithm.notifyChildrenChanged(this); updateScrollStateForRemovedChild(child); - generateRemoveAnimation(child); + boolean animationGenerated = generateRemoveAnimation(child); + if (animationGenerated && !mSwipedOutViews.contains(child)) { + // Add this view to an overlay in order to ensure that it will still be temporary + // drawn when removed + getOverlay().add(child); + } } - private void generateRemoveAnimation(View child) { + /** + * Generate a remove animation for a child view. + * + * @param child The view to generate the remove animation for. + * @return Whether an animation was generated. + */ + private boolean generateRemoveAnimation(View child) { if (mIsExpanded && mAnimationsEnabled) { if (!mChildrenToAddAnimated.contains(child)) { // Generate Animations mChildrenToRemoveAnimated.add(child); mNeedsAnimation = true; + return true; } else { mChildrenToAddAnimated.remove(child); + return false; } } + return false; } /** @@ -1155,9 +1189,7 @@ public class NotificationStackScrollLayout extends ViewGroup super.onViewAdded(child); mStackScrollAlgorithm.notifyChildrenChanged(this); ((ExpandableView) child).setOnHeightChangedListener(this); - if (child.getVisibility() != View.GONE) { - generateAddAnimation(child); - } + generateAddAnimation(child); } public void setAnimationsEnabled(boolean animationsEnabled) { @@ -1168,10 +1200,13 @@ public class NotificationStackScrollLayout extends ViewGroup return mNeedsAnimation && (!mChildrenToAddAnimated.isEmpty() || !mChildrenToRemoveAnimated.isEmpty()); } - + /** + * Generate an animation for an added child view. + * + * @param child The view to be added. + */ public void generateAddAnimation(View child) { - if (mIsExpanded && mAnimationsEnabled) { - + if (mIsExpanded && mAnimationsEnabled && !mChildrenChangingPositions.contains(child)) { // Generate Animations mChildrenToAddAnimated.add(child); mNeedsAnimation = true; @@ -1186,9 +1221,10 @@ public class NotificationStackScrollLayout extends ViewGroup */ public void changeViewPosition(View child, int newIndex) { if (child != null && child.getParent() == this) { + mChildrenChangingPositions.add(child); removeView(child); addView(child, newIndex); - // TODO: handle events + mNeedsAnimation = true; } } @@ -1197,16 +1233,18 @@ public class NotificationStackScrollLayout extends ViewGroup generateChildHierarchyEvents(); mNeedsAnimation = false; } - if (!mAnimationEvents.isEmpty()) { + if (!mAnimationEvents.isEmpty() || isCurrentlyAnimating()) { mStateAnimator.startAnimationForEvents(mAnimationEvents, mCurrentStackScrollState); + mAnimationEvents.clear(); } else { applyCurrentState(); } } private void generateChildHierarchyEvents() { - generateChildAdditionEvents(); generateChildRemovalEvents(); + generateChildAdditionEvents(); + generatePositionChangeEvents(); generateSnapBackEvents(); generateDragEvents(); generateTopPaddingEvent(); @@ -1237,12 +1275,24 @@ public class NotificationStackScrollLayout extends ViewGroup int animationType = childWasSwipedOut ? AnimationEvent.ANIMATION_TYPE_REMOVE_SWIPED_OUT : AnimationEvent.ANIMATION_TYPE_REMOVE; - mAnimationEvents.add(new AnimationEvent(child, animationType)); + AnimationEvent event = new AnimationEvent(child, animationType); + + // we need to know the view after this one + event.viewAfterChangingView = getFirstChildBelowTranlsationY(child.getTranslationY()); + mAnimationEvents.add(event); } mSwipedOutViews.clear(); mChildrenToRemoveAnimated.clear(); } + private void generatePositionChangeEvents() { + for (View child : mChildrenChangingPositions) { + mAnimationEvents.add(new AnimationEvent(child, + AnimationEvent.ANIMATION_TYPE_CHANGE_POSITION)); + } + mChildrenChangingPositions.clear(); + } + private void generateChildAdditionEvents() { for (View child : mChildrenToAddAnimated) { mAnimationEvents.add(new AnimationEvent(child, @@ -1467,7 +1517,6 @@ public class NotificationStackScrollLayout extends ViewGroup public void onChildAnimationFinished() { requestChildrenUpdate(); - mAnimationEvents.clear(); } /** @@ -1513,9 +1562,9 @@ public class NotificationStackScrollLayout extends ViewGroup } private void updateSpeedBump(boolean visible) { - int newVisibility = visible ? VISIBLE : GONE; - int oldVisibility = mSpeedBumpView.getVisibility(); - if (newVisibility != oldVisibility) { + boolean notGoneBefore = mSpeedBumpView.getVisibility() != GONE; + if (visible != notGoneBefore) { + int newVisibility = visible ? VISIBLE : GONE; mSpeedBumpView.setVisibility(newVisibility); if (visible) { mSpeedBumpView.collapse(); @@ -1551,21 +1600,24 @@ public class NotificationStackScrollLayout extends ViewGroup .animateAlpha() .animateHeight() .animateY() - .animateZ(), + .animateZ() + .hasDelays(), // ANIMATION_TYPE_REMOVE new AnimationFilter() .animateAlpha() .animateHeight() .animateY() - .animateZ(), + .animateZ() + .hasDelays(), // ANIMATION_TYPE_REMOVE_SWIPED_OUT new AnimationFilter() .animateAlpha() .animateHeight() .animateY() - .animateZ(), + .animateZ() + .hasDelays(), // ANIMATION_TYPE_TOP_PADDING_CHANGED new AnimationFilter() @@ -1593,16 +1645,23 @@ public class NotificationStackScrollLayout extends ViewGroup new AnimationFilter() .animateY() .animateScale() - .animateDimmed() + .animateDimmed(), + + // ANIMATION_TYPE_CHANGE_POSITION + new AnimationFilter() + .animateAlpha() + .animateHeight() + .animateY() + .animateZ() }; static int[] LENGTHS = new int[] { // ANIMATION_TYPE_ADD - StackStateAnimator.ANIMATION_DURATION_STANDARD, + StackStateAnimator.ANIMATION_DURATION_APPEAR_DISAPPEAR, // ANIMATION_TYPE_REMOVE - StackStateAnimator.ANIMATION_DURATION_STANDARD, + StackStateAnimator.ANIMATION_DURATION_APPEAR_DISAPPEAR, // ANIMATION_TYPE_REMOVE_SWIPED_OUT StackStateAnimator.ANIMATION_DURATION_STANDARD, @@ -1621,22 +1680,27 @@ public class NotificationStackScrollLayout extends ViewGroup // ANIMATION_TYPE_DIMMED StackStateAnimator.ANIMATION_DURATION_DIMMED_ACTIVATED, + + // ANIMATION_TYPE_CHANGE_POSITION + StackStateAnimator.ANIMATION_DURATION_STANDARD, }; - static int ANIMATION_TYPE_ADD = 0; - static int ANIMATION_TYPE_REMOVE = 1; - static int ANIMATION_TYPE_REMOVE_SWIPED_OUT = 2; - static int ANIMATION_TYPE_TOP_PADDING_CHANGED = 3; - static int ANIMATION_TYPE_START_DRAG = 4; - static int ANIMATION_TYPE_SNAP_BACK = 5; - static int ANIMATION_TYPE_ACTIVATED_CHILD = 6; - static int ANIMATION_TYPE_DIMMED = 7; + static final int ANIMATION_TYPE_ADD = 0; + static final int ANIMATION_TYPE_REMOVE = 1; + static final int ANIMATION_TYPE_REMOVE_SWIPED_OUT = 2; + static final int ANIMATION_TYPE_TOP_PADDING_CHANGED = 3; + static final int ANIMATION_TYPE_START_DRAG = 4; + static final int ANIMATION_TYPE_SNAP_BACK = 5; + static final int ANIMATION_TYPE_ACTIVATED_CHILD = 6; + static final int ANIMATION_TYPE_DIMMED = 7; + static final int ANIMATION_TYPE_CHANGE_POSITION = 8; final long eventStartTime; final View changingView; final int animationType; final AnimationFilter filter; final long length; + View viewAfterChangingView; AnimationEvent(View view, int type) { eventStartTime = AnimationUtils.currentAnimationTimeMillis(); 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 d572ea5..bd2541a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java @@ -208,6 +208,8 @@ public class StackScrollAlgorithm { for (int i = 0; i < childCount; i++) { ExpandableView v = (ExpandableView) hostView.getChildAt(i); if (v.getVisibility() != View.GONE) { + StackScrollState.ViewState viewState = resultState.getViewStateForView(v); + viewState.notGoneIndex = state.visibleChildren.size(); state.visibleChildren.add(v); } } 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 011411c..ae2acab 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java @@ -39,14 +39,10 @@ public class StackScrollState { private final ViewGroup mHostView; private Map<ExpandableView, ViewState> mStateMap; private final Rect mClipRect = new Rect(); - private int mBackgroundRoundedRectCornerRadius; - private final Outline mChildOutline = new Outline(); public StackScrollState(ViewGroup hostView) { mHostView = hostView; mStateMap = new HashMap<ExpandableView, ViewState>(); - mBackgroundRoundedRectCornerRadius = hostView.getResources().getDimensionPixelSize( - com.android.internal.R.dimen.notification_quantum_rounded_rect_radius); } public ViewGroup getHostView() { @@ -66,6 +62,7 @@ public class StackScrollState { viewState.height = child.getIntrinsicHeight(); viewState.gone = child.getVisibility() == View.GONE; viewState.alpha = 1; + viewState.notGoneIndex = -1; } } @@ -190,7 +187,7 @@ public class StackScrollState { if (nextChild != null) { ViewState nextState = getViewStateForView(nextChild); boolean startIsAboveNext = nextState.yTranslation > speedBumpStart; - speedBump.animateDivider(startIsAboveNext); + speedBump.animateDivider(startIsAboveNext, null /* onFinishedRunnable */); // handle expanded case if (speedBump.isExpanded()) { @@ -272,6 +269,11 @@ public class StackScrollState { boolean dimmed; /** + * The index of the view, only accounting for views not equal to GONE + */ + int notGoneIndex; + + /** * The location this view is currently rendered at. * * <p>See <code>LOCATION_</code> flags.</p> 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 a9dcdd6..045a99d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java @@ -39,7 +39,11 @@ import java.util.Stack; public class StackStateAnimator { public static final int ANIMATION_DURATION_STANDARD = 360; + public static final int ANIMATION_DURATION_APPEAR_DISAPPEAR = 464; public static final int ANIMATION_DURATION_DIMMED_ACTIVATED = 220; + public static final int ANIMATION_DELAY_PER_ELEMENT_INTERRUPTING = 80; + public static final int ANIMATION_DELAY_PER_ELEMENT_MANUAL = 32; + private static final int DELAY_EFFECT_MAX_INDEX_DIFFERENCE = 2; private static final int TAG_ANIMATOR_TRANSLATION_Y = R.id.translation_y_animator_tag; private static final int TAG_ANIMATOR_TRANSLATION_Z = R.id.translation_z_animator_tag; @@ -62,10 +66,9 @@ public class StackStateAnimator { private final Interpolator mFastOutSlowInInterpolator; public NotificationStackScrollLayout mHostLayout; - private ArrayList<NotificationStackScrollLayout.AnimationEvent> mHandledEvents = - new ArrayList<>(); private ArrayList<NotificationStackScrollLayout.AnimationEvent> mNewEvents = new ArrayList<>(); + private ArrayList<View> mNewAddChildren = new ArrayList<>(); private Set<Animator> mAnimatorSet = new HashSet<Animator>(); private Stack<AnimatorListenerAdapter> mAnimationListenerPool = new Stack<AnimatorListenerAdapter>(); @@ -96,57 +99,130 @@ public class StackStateAnimator { mCurrentLength = NotificationStackScrollLayout.AnimationEvent.combineLength(mNewEvents); for (int i = 0; i < childCount; i++) { final ExpandableView child = (ExpandableView) mHostLayout.getChildAt(i); + StackScrollState.ViewState viewState = finalState.getViewStateForView(child); - if (viewState == null) { + if (viewState == null || child.getVisibility() == View.GONE) { continue; } - startAnimations(child, viewState); - child.setClipBounds(null); + startAnimations(child, viewState, finalState); } if (!isRunning()) { // no child has preformed any animation, lets finish onAnimationFinished(); } + mNewEvents.clear(); + mNewAddChildren.clear(); } /** * Start an animation to the given viewState */ - private void startAnimations(final ExpandableView child, StackScrollState.ViewState viewState) { + private void startAnimations(final ExpandableView child, StackScrollState.ViewState viewState, + StackScrollState finalState) { int childVisibility = child.getVisibility(); boolean wasVisible = childVisibility == View.VISIBLE; final float alpha = viewState.alpha; if (!wasVisible && alpha != 0 && !viewState.gone) { child.setVisibility(View.VISIBLE); } + + boolean yTranslationChanging = child.getTranslationY() != viewState.yTranslation; + boolean zTranslationChanging = child.getTranslationZ() != viewState.zTranslation; + boolean scaleChanging = child.getScaleX() != viewState.scale; + boolean alphaChanging = alpha != child.getAlpha(); + boolean heightChanging = viewState.height != child.getActualHeight(); + boolean wasAdded = mNewAddChildren.contains(child); + boolean hasDelays = mAnimationFilter.hasDelays; + boolean isDelayRelevant = yTranslationChanging || zTranslationChanging || scaleChanging || + alphaChanging || heightChanging; + long delay = 0; + if (hasDelays && isDelayRelevant || wasAdded) { + delay = calculateChildAnimationDelay(viewState, finalState); + } + // start translationY animation - if (child.getTranslationY() != viewState.yTranslation) { - startYTranslationAnimation(child, viewState); + if (yTranslationChanging) { + startYTranslationAnimation(child, viewState, delay); } + // start translationZ animation - if (child.getTranslationZ() != viewState.zTranslation) { - startZTranslationAnimation(child, viewState); + if (zTranslationChanging) { + startZTranslationAnimation(child, viewState, delay); } + // start scale animation - if (child.getScaleX() != viewState.scale) { + if (scaleChanging) { startScaleAnimation(child, viewState); } + // start alpha animation - if (alpha != child.getAlpha()) { - startAlphaAnimation(child, viewState); + if (alphaChanging) { + startAlphaAnimation(child, viewState, delay); } + // start height animation - if (viewState.height != child.getActualHeight()) { - startHeightAnimation(child, viewState); + if (heightChanging) { + startHeightAnimation(child, viewState, delay); } + // start dimmed animation child.setDimmed(viewState.dimmed, mAnimationFilter.animateDimmed); + + if (wasAdded) { + child.performAddAnimation(delay); + } + } + + private long calculateChildAnimationDelay(StackScrollState.ViewState viewState, + StackScrollState finalState) { + long minDelay = 0; + for (NotificationStackScrollLayout.AnimationEvent event : mNewEvents) { + long delayPerElement = ANIMATION_DELAY_PER_ELEMENT_INTERRUPTING; + switch (event.animationType) { + case NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_ADD: { + int ownIndex = viewState.notGoneIndex; + int changingIndex = finalState + .getViewStateForView(event.changingView).notGoneIndex; + int difference = Math.abs(ownIndex - changingIndex); + difference = Math.max(0, Math.min(DELAY_EFFECT_MAX_INDEX_DIFFERENCE, + difference - 1)); + long delay = (DELAY_EFFECT_MAX_INDEX_DIFFERENCE - difference) * delayPerElement; + minDelay = Math.max(delay, minDelay); + break; + } + case NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_REMOVE_SWIPED_OUT: + delayPerElement = ANIMATION_DELAY_PER_ELEMENT_MANUAL; + case NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_REMOVE: { + int ownIndex = viewState.notGoneIndex; + boolean noNextView = event.viewAfterChangingView == null; + View viewAfterChangingView = noNextView + ? mHostLayout.getLastChildNotGone() + : event.viewAfterChangingView; + + int nextIndex = finalState + .getViewStateForView(viewAfterChangingView).notGoneIndex; + if (ownIndex >= nextIndex) { + // we only have the view afterwards + ownIndex++; + } + int difference = Math.abs(ownIndex - nextIndex); + difference = Math.max(0, Math.min(DELAY_EFFECT_MAX_INDEX_DIFFERENCE, + difference - 1)); + long delay = difference * delayPerElement; + minDelay = Math.max(delay, minDelay); + break; + } + default: + break; + } + } + return minDelay; } private void startHeightAnimation(final ExpandableView child, - StackScrollState.ViewState viewState) { + StackScrollState.ViewState viewState, long delay) { Integer previousStartValue = getChildTag(child, TAG_START_HEIGHT); Integer previousEndValue = getChildTag(child, TAG_END_HEIGHT); int newEndValue = viewState.height; @@ -185,6 +261,9 @@ public class StackStateAnimator { 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() { @@ -195,14 +274,14 @@ public class StackStateAnimator { child.setTag(TAG_END_HEIGHT, null); } }); - startInstantly(animator); + startAnimator(animator); child.setTag(TAG_ANIMATOR_HEIGHT, animator); child.setTag(TAG_START_HEIGHT, child.getActualHeight()); child.setTag(TAG_END_HEIGHT, newEndValue); } private void startAlphaAnimation(final ExpandableView child, - final StackScrollState.ViewState viewState) { + final StackScrollState.ViewState viewState, long delay) { Float previousStartValue = getChildTag(child,TAG_START_ALPHA); Float previousEndValue = getChildTag(child,TAG_END_ALPHA); final float newEndValue = viewState.alpha; @@ -264,6 +343,9 @@ public class StackStateAnimator { }); 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() { @@ -272,14 +354,14 @@ public class StackStateAnimator { } }); - startInstantly(animator); + startAnimator(animator); child.setTag(TAG_ANIMATOR_ALPHA, animator); child.setTag(TAG_START_ALPHA, child.getAlpha()); child.setTag(TAG_END_ALPHA, newEndValue); } private void startZTranslationAnimation(final ExpandableView child, - final StackScrollState.ViewState viewState) { + final StackScrollState.ViewState viewState, long delay) { Float previousStartValue = getChildTag(child,TAG_START_TRANSLATION_Z); Float previousEndValue = getChildTag(child,TAG_END_TRANSLATION_Z); float newEndValue = viewState.zTranslation; @@ -311,6 +393,9 @@ public class StackStateAnimator { 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() { @@ -321,14 +406,14 @@ public class StackStateAnimator { child.setTag(TAG_END_TRANSLATION_Z, null); } }); - startInstantly(animator); + startAnimator(animator); child.setTag(TAG_ANIMATOR_TRANSLATION_Z, animator); child.setTag(TAG_START_TRANSLATION_Z, child.getTranslationZ()); child.setTag(TAG_END_TRANSLATION_Z, newEndValue); } private void startYTranslationAnimation(final ExpandableView child, - StackScrollState.ViewState viewState) { + StackScrollState.ViewState viewState, long delay) { Float previousStartValue = getChildTag(child,TAG_START_TRANSLATION_Y); Float previousEndValue = getChildTag(child,TAG_END_TRANSLATION_Y); float newEndValue = viewState.yTranslation; @@ -361,6 +446,9 @@ public class StackStateAnimator { 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() { @@ -371,7 +459,7 @@ public class StackStateAnimator { child.setTag(TAG_END_TRANSLATION_Y, null); } }); - startInstantly(animator); + startAnimator(animator); child.setTag(TAG_ANIMATOR_TRANSLATION_Y, animator); child.setTag(TAG_START_TRANSLATION_Y, child.getTranslationY()); child.setTag(TAG_END_TRANSLATION_Y, newEndValue); @@ -425,18 +513,15 @@ public class StackStateAnimator { child.setTag(TAG_END_SCALE, null); } }); - startInstantly(animator); + startAnimator(animator); child.setTag(TAG_ANIMATOR_SCALE, animator); child.setTag(TAG_START_SCALE, child.getScaleX()); child.setTag(TAG_END_SCALE, newEndValue); } - /** - * Start an animator instantly instead of waiting on the next synchronization frame - */ - public static void startInstantly(ValueAnimator animator) { + private void startAnimator(ValueAnimator animator) { + mAnimatorSet.add(animator); animator.start(); - animator.setCurrentPlayTime(0); } /** @@ -468,7 +553,6 @@ public class StackStateAnimator { @Override public void onAnimationStart(Animator animation) { - mAnimatorSet.add(animation); mWasCancelled = false; } }; @@ -497,8 +581,6 @@ public class StackStateAnimator { } private void onAnimationFinished() { - mHandledEvents.clear(); - mNewEvents.clear(); mHostLayout.onChildAnimationFinished(); } @@ -511,27 +593,60 @@ public class StackStateAnimator { private void processAnimationEvents( ArrayList<NotificationStackScrollLayout.AnimationEvent> animationEvents, StackScrollState finalState) { - mNewEvents.clear(); for (NotificationStackScrollLayout.AnimationEvent event : animationEvents) { - View changingView = event.changingView; - if (!mHandledEvents.contains(event)) { - if (event.animationType == NotificationStackScrollLayout.AnimationEvent - .ANIMATION_TYPE_ADD) { - - // This item is added, initialize it's properties. - StackScrollState.ViewState viewState = finalState - .getViewStateForView(changingView); - if (viewState == null) { - // The position for this child was never generated, let's continue. - continue; - } - changingView.setAlpha(0); - changingView.setTranslationY(viewState.yTranslation); - changingView.setTranslationZ(viewState.zTranslation); + final ExpandableView changingView = (ExpandableView) event.changingView; + if (event.animationType == + NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_ADD) { + + // This item is added, initialize it's properties. + StackScrollState.ViewState viewState = finalState + .getViewStateForView(changingView); + if (viewState == null) { + // The position for this child was never generated, let's continue. + continue; + } + if (changingView.getVisibility() == View.GONE) { + // The view was set to gone but the state never removed + finalState.removeViewStateForView(changingView); + continue; } - mHandledEvents.add(event); - mNewEvents.add(event); + changingView.setAlpha(viewState.alpha); + changingView.setTranslationY(viewState.yTranslation); + changingView.setTranslationZ(viewState.zTranslation); + changingView.setActualHeight(viewState.height, false); + mNewAddChildren.add(changingView); + + } else if (event.animationType == + NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_REMOVE) { + if (changingView.getVisibility() == View.GONE) { + continue; + } + + // Find the amount to translate up. This is needed in order to understand the + // direction of the remove animation (either downwards or upwards) + StackScrollState.ViewState viewState = finalState + .getViewStateForView(event.viewAfterChangingView); + int actualHeight = changingView.getActualHeight(); + // upwards by default + float translationDirection = -1.0f; + if (viewState != null) { + // there was a view after this one, Approximate the distance the next child + // travelled + translationDirection = ((viewState.yTranslation + - (changingView.getTranslationY() + actualHeight / 2.0f)) * 2 / + actualHeight); + translationDirection = Math.max(Math.min(translationDirection, 1.0f),-1.0f); + + } + changingView.performRemoveAnimation(translationDirection, new Runnable() { + @Override + public void run() { + // remove the temporary overlay + mHostLayout.getOverlay().remove(changingView); + } + }); } + mNewEvents.add(event); } } |