diff options
author | Selim Cinek <cinek@google.com> | 2014-05-07 13:43:43 +0000 |
---|---|---|
committer | Android (Google) Code Review <android-gerrit@google.com> | 2014-05-07 13:43:43 +0000 |
commit | 2c5d4a08d2c94420b989d5a0886fed3c83c0226c (patch) | |
tree | 1d0cb4aae3e96fc3de9bc0967be5b0034610d64b /packages/SystemUI | |
parent | 05ee2bd617f50fe129cf36ae306e6059335e02aa (diff) | |
parent | eb973565f3efc6417ca35363e4d6c642947775d8 (diff) | |
download | frameworks_base-2c5d4a08d2c94420b989d5a0886fed3c83c0226c.zip frameworks_base-2c5d4a08d2c94420b989d5a0886fed3c83c0226c.tar.gz frameworks_base-2c5d4a08d2c94420b989d5a0886fed3c83c0226c.tar.bz2 |
Merge "Refactored the notification animations, improved stack scroller"
Diffstat (limited to 'packages/SystemUI')
11 files changed, 458 insertions, 89 deletions
diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml new file mode 100644 index 0000000..e5168c4 --- /dev/null +++ b/packages/SystemUI/res/values/ids.xml @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2014 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 + --> + +<resources> + <item type="id" name="translation_y_animator_tag"/> + <item type="id" name="translation_z_animator_tag"/> + <item type="id" name="alpha_animator_tag"/> + <item type="id" name="top_inset_animator_tag"/> + <item type="id" name="height_animator_tag"/> + <item type="id" name="translation_y_animator_end_value_tag"/> + <item type="id" name="translation_z_animator_end_value_tag"/> + <item type="id" name="alpha_animator_end_value_tag"/> + <item type="id" name="top_inset_animator_end_value_tag"/> + <item type="id" name="height_animator_end_value_tag"/> +</resources> + diff --git a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java index d38d828..6387a92 100644 --- a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java +++ b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java @@ -322,6 +322,7 @@ public class SwipeHelper implements Gefingerpoken { anim.addListener(new AnimatorListenerAdapter() { public void onAnimationEnd(Animator animator) { updateAlphaFromOffset(animView, canAnimViewBeDismissed); + mCallback.onChildSnappedBack(animView); } }); anim.start(); @@ -407,5 +408,7 @@ public class SwipeHelper implements Gefingerpoken { void onChildDismissed(View v); void onDragCancelled(View v); + + void onChildSnappedBack(View animView); } } diff --git a/packages/SystemUI/src/com/android/systemui/recent/RecentsHorizontalScrollView.java b/packages/SystemUI/src/com/android/systemui/recent/RecentsHorizontalScrollView.java index 35c824b..0759b8e 100644 --- a/packages/SystemUI/src/com/android/systemui/recent/RecentsHorizontalScrollView.java +++ b/packages/SystemUI/src/com/android/systemui/recent/RecentsHorizontalScrollView.java @@ -217,6 +217,10 @@ public class RecentsHorizontalScrollView extends HorizontalScrollView public void onDragCancelled(View v) { } + @Override + public void onChildSnappedBack(View animView) { + } + public View getChildAtPosition(MotionEvent ev) { final float x = ev.getX() + getScrollX(); final float y = ev.getY() + getScrollY(); diff --git a/packages/SystemUI/src/com/android/systemui/recent/RecentsVerticalScrollView.java b/packages/SystemUI/src/com/android/systemui/recent/RecentsVerticalScrollView.java index 297fe0d..c2dde6a 100644 --- a/packages/SystemUI/src/com/android/systemui/recent/RecentsVerticalScrollView.java +++ b/packages/SystemUI/src/com/android/systemui/recent/RecentsVerticalScrollView.java @@ -225,6 +225,10 @@ public class RecentsVerticalScrollView extends ScrollView public void onDragCancelled(View v) { } + @Override + public void onChildSnappedBack(View animView) { + } + public View getChildAtPosition(MotionEvent ev) { final float x = ev.getX() + getScrollX(); final float y = ev.getY() + getScrollY(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java index 33e9051..169521d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java @@ -17,11 +17,6 @@ package com.android.systemui.statusbar; import android.content.Context; -import android.graphics.Canvas; -import android.graphics.Outline; -import android.graphics.Rect; -import android.graphics.drawable.Drawable; -import android.graphics.drawable.InsetDrawable; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; @@ -112,6 +107,10 @@ public abstract class ExpandableView extends FrameLayout { mClipTopAmount = clipTopAmount; } + public int getClipTopAmount() { + return mClipTopAmount; + } + public void setOnHeightChangedListener(OnHeightChangedListener listener) { mOnHeightChangedListener = listener; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java index 1f15eaf..379bd05 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java @@ -94,10 +94,6 @@ public class NotificationContentView extends ExpandableView { updateClipping(); } - public int getClipTopAmount() { - return mClipTopAmount; - } - private void updateClipping() { mClipBounds.set(0, mClipTopAmount, getWidth(), mActualHeight); setClipBounds(mClipBounds); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpNotificationView.java index 72e22e9..81e2cb3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpNotificationView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpNotificationView.java @@ -237,6 +237,10 @@ public class HeadsUpNotificationView extends FrameLayout implements SwipeHelper. } @Override + public void onChildSnappedBack(View animView) { + } + + @Override public View getChildAtPosition(MotionEvent ev) { return mContentHolder; } 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 e4e5fb1..b72909f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java @@ -97,6 +97,8 @@ public class NotificationStackScrollLayout extends ViewGroup private StackScrollState mCurrentStackScrollState = new StackScrollState(this); private ArrayList<View> mChildrenToAddAnimated = new ArrayList<View>(); private ArrayList<View> mChildrenToRemoveAnimated = new ArrayList<View>(); + private ArrayList<View> mSnappedBackChildren = new ArrayList<View>(); + private ArrayList<View> mDragAnimPendingChildren = new ArrayList<View>(); private ArrayList<AnimationEvent> mAnimationEvents = new ArrayList<AnimationEvent>(); private ArrayList<View> mSwipedOutViews = new ArrayList<View>(); @@ -377,11 +379,34 @@ public class NotificationStackScrollLayout extends ViewGroup veto.performClick(); } setSwipingInProgress(false); + if (mDragAnimPendingChildren.contains(v)) { + // We start the swipe and finish it in the same frame, we don't want any animation + // for the drag + mDragAnimPendingChildren.remove(v); + } mSwipedOutViews.add(v); + mStackScrollAlgorithm.onDragFinished(v); + } + + @Override + public void onChildSnappedBack(View animView) { + mStackScrollAlgorithm.onDragFinished(animView); + if (!mDragAnimPendingChildren.contains(animView)) { + mSnappedBackChildren.add(animView); + requestChildrenUpdate(); + mNeedsAnimation = true; + } else { + // We start the swipe and snap back in the same frame, we don't want any animation + mDragAnimPendingChildren.remove(animView); + } } public void onBeginDrag(View v) { setSwipingInProgress(true); + mDragAnimPendingChildren.add(v); + mStackScrollAlgorithm.onBeginDrag(v); + requestChildrenUpdate(); + mNeedsAnimation = true; } public void onDragCancelled(View v) { @@ -670,7 +695,7 @@ public class NotificationStackScrollLayout extends ViewGroup // mEdgeGlowBottom.onAbsorb((int) mScroller.getCurrVelocity()); // } } - updateChildren(); + requestChildrenUpdate(); } // Keep on drawing until the animation has finished. @@ -680,7 +705,7 @@ public class NotificationStackScrollLayout extends ViewGroup private void customScrollTo(int y) { mOwnScrollY = y; - updateChildren(); + requestChildrenUpdate(); } @Override @@ -696,7 +721,7 @@ public class NotificationStackScrollLayout extends ViewGroup if (clampedY) { mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0, getScrollRange()); } - updateChildren(); + requestChildrenUpdate(); } else { customScrollTo(scrollY); scrollTo(scrollX, mScrollY); @@ -934,12 +959,30 @@ public class NotificationStackScrollLayout extends ViewGroup private void generateChildHierarchyEvents() { generateChildAdditionEvents(); generateChildRemovalEvents(); + generateSnapBackEvents(); + generateDragEvents(); generateTopPaddingEvent(); mNeedsAnimation = false; } + private void generateSnapBackEvents() { + for (View child : mSnappedBackChildren) { + mAnimationEvents.add(new AnimationEvent(child, + AnimationEvent.ANIMATION_TYPE_SNAP_BACK)); + } + mSnappedBackChildren.clear(); + } + + private void generateDragEvents() { + for (View child : mDragAnimPendingChildren) { + mAnimationEvents.add(new AnimationEvent(child, + AnimationEvent.ANIMATION_TYPE_START_DRAG)); + } + mDragAnimPendingChildren.clear(); + } + private void generateChildRemovalEvents() { - for (View child : mChildrenToRemoveAnimated) { + for (View child : mChildrenToRemoveAnimated) { boolean childWasSwipedOut = mSwipedOutViews.contains(child); int animationType = childWasSwipedOut ? AnimationEvent.ANIMATION_TYPE_REMOVE_SWIPED_OUT @@ -951,7 +994,7 @@ public class NotificationStackScrollLayout extends ViewGroup } private void generateChildAdditionEvents() { - for (View child : mChildrenToAddAnimated) { + for (View child : mChildrenToAddAnimated) { mAnimationEvents.add(new AnimationEvent(child, AnimationEvent.ANIMATION_TYPE_ADD)); } @@ -1173,6 +1216,8 @@ public class NotificationStackScrollLayout extends ViewGroup static int ANIMATION_TYPE_REMOVE = 2; static int ANIMATION_TYPE_REMOVE_SWIPED_OUT = 3; static int ANIMATION_TYPE_TOP_PADDING_CHANGED = 4; + static int ANIMATION_TYPE_START_DRAG = 5; + static int ANIMATION_TYPE_SNAP_BACK = 6; final long eventStartTime; final View changingView; 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 09d8d50..f7818c0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java @@ -61,6 +61,7 @@ public class StackScrollAlgorithm { private ExpandableView mFirstChildWhileExpanding; private boolean mExpandedOnStart; private int mTopStackTotalSize; + private ArrayList<View> mDraggedViews = new ArrayList<View>(); public StackScrollAlgorithm(Context context) { initConstants(context); @@ -118,6 +119,34 @@ public class StackScrollAlgorithm { // Phase 3: updateZValuesForState(resultState, algorithmState); + + handleDraggedViews(resultState, algorithmState); + } + + /** + * Handle the special state when views are being dragged + */ + private void handleDraggedViews(StackScrollState resultState, + StackScrollAlgorithmState algorithmState) { + for (View draggedView : mDraggedViews) { + int childIndex = algorithmState.visibleChildren.indexOf(draggedView); + if (childIndex >= 0 && childIndex < algorithmState.visibleChildren.size() - 1) { + View nextChild = algorithmState.visibleChildren.get(childIndex + 1); + if (!mDraggedViews.contains(nextChild)) { + // only if the view is not dragged itself we modify its state to be fully + // visible + StackScrollState.ViewState viewState = resultState.getViewStateForView( + nextChild); + // The child below the dragged one must be fully visible + viewState.alpha = 1; + } + + // Lets set the alpha to the one it currently has, as its currently being dragged + StackScrollState.ViewState viewState = resultState.getViewStateForView(draggedView); + // The dragged child should keep the set alpha + viewState.alpha = draggedView.getAlpha(); + } + } } /** @@ -566,6 +595,14 @@ public class StackScrollAlgorithm { } } + public void onBeginDrag(View view) { + mDraggedViews.add(view); + } + + public void onDragFinished(View view) { + mDraggedViews.remove(view); + } + class StackScrollAlgorithmState { /** 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 26cef36..70126f5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java @@ -93,6 +93,7 @@ public class StackScrollState { 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); @@ -153,12 +154,20 @@ public class StackScrollState { // apply clipping and shadow float newNotificationEnd = newYTranslation + newHeight; + + // When the previous notification is swiped, we don't clip the content to the + // bottom of it. + float clipHeight = previousNotificationIsSwiped + ? newHeight + : newNotificationEnd - (previousNotificationEnd); + updateChildClippingAndBackground(child, newHeight, - newNotificationEnd - (previousNotificationEnd), + clipHeight, (int) (newHeight - (previousNotificationStart - newYTranslation))); - previousNotificationStart = newYTranslation; + previousNotificationStart = newYTranslation + child.getClipTopAmount(); previousNotificationEnd = newNotificationEnd; + previousNotificationIsSwiped = child.getTranslationX() != 0; } } } 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 4dce288..3281b67 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java @@ -16,13 +16,21 @@ package com.android.systemui.statusbar.stack; +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ObjectAnimator; import android.animation.ValueAnimator; import android.view.View; import android.view.animation.AnimationUtils; import android.view.animation.Interpolator; + +import com.android.systemui.R; import com.android.systemui.statusbar.ExpandableView; import java.util.ArrayList; +import java.util.HashSet; +import java.util.Set; +import java.util.Stack; /** * An stack state animator which handles animations to new StackScrollStates @@ -30,130 +38,360 @@ import java.util.ArrayList; public class StackStateAnimator { private static final int ANIMATION_DURATION = 360; + 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; + private static final int TAG_ANIMATOR_ALPHA = R.id.alpha_animator_tag; + private static final int TAG_ANIMATOR_HEIGHT = R.id.height_animator_tag; + private static final int TAG_ANIMATOR_TOP_INSET = R.id.top_inset_animator_tag; + private static final int TAG_END_TRANSLATION_Y = R.id.translation_y_animator_end_value_tag; + private static final int TAG_END_TRANSLATION_Z = R.id.translation_z_animator_end_value_tag; + private static final int TAG_END_ALPHA = R.id.alpha_animator_end_value_tag; + private static final int TAG_END_HEIGHT = R.id.height_animator_end_value_tag; + private static final int TAG_END_TOP_INSET = R.id.top_inset_animator_end_value_tag; private final Interpolator mFastOutSlowInInterpolator; public NotificationStackScrollLayout mHostLayout; - private boolean mAnimationIsRunning; private ArrayList<NotificationStackScrollLayout.AnimationEvent> mHandledEvents = new ArrayList<>(); + private ArrayList<NotificationStackScrollLayout.AnimationEvent> mNewEvents = + new ArrayList<>(); + private Set<Animator> mAnimatorSet = new HashSet<Animator>(); + private Stack<AnimatorListenerAdapter> mAnimationListenerPool + = new Stack<AnimatorListenerAdapter>(); public StackStateAnimator(NotificationStackScrollLayout hostLayout) { mHostLayout = hostLayout; mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(hostLayout.getContext(), - android.R.interpolator.fast_out_slow_in); + android.R.interpolator.fast_out_slow_in); } public boolean isRunning() { - return mAnimationIsRunning; + return !mAnimatorSet.isEmpty(); } public void startAnimationForEvents( ArrayList<NotificationStackScrollLayout.AnimationEvent> mAnimationEvents, StackScrollState finalState) { - int numEvents = mAnimationEvents.size(); - if (numEvents == 0) { - // No events, so we don't perform any animation - return; - } - long lastEventStartTime = mAnimationEvents.get(numEvents - 1).eventStartTime; - long eventEnd = lastEventStartTime + ANIMATION_DURATION; - long currentTime = AnimationUtils.currentAnimationTimeMillis(); - long newDuration = eventEnd - currentTime; - if (newDuration <= 0) { - // last event is long before this, so we don't do anything - return; - } - initializeAddedViewStates(mAnimationEvents, finalState); + + processAnimationEvents(mAnimationEvents, finalState); + + boolean hasNewEvents = !mNewEvents.isEmpty(); int childCount = mHostLayout.getChildCount(); - boolean isFirstAnimatingView = true; for (int i = 0; i < childCount; i++) { final ExpandableView child = (ExpandableView) mHostLayout.getChildAt(i); StackScrollState.ViewState viewState = finalState.getViewStateForView(child); if (viewState == null) { continue; } - int childVisibility = child.getVisibility(); - boolean wasVisible = childVisibility == View.VISIBLE; - final float alpha = viewState.alpha; - if (!wasVisible && alpha != 0 && !viewState.gone) { - child.setVisibility(View.VISIBLE); - } - startPropertyAnimation(newDuration, isFirstAnimatingView, child, viewState, alpha); + startAnimations(child, viewState, hasNewEvents); - // TODO: animate clipBounds child.setClipBounds(null); - int currentHeigth = child.getActualHeight(); - if (viewState.height != currentHeigth) { - startHeightAnimation(newDuration, child, viewState, currentHeigth); - } - isFirstAnimatingView = false; } - mAnimationIsRunning = true; + if (!isRunning()) { + // no child has preformed any animation, lets finish + onAnimationFinished(); + } } - private void startPropertyAnimation(long newDuration, final boolean hasFinishAction, - final ExpandableView child, StackScrollState.ViewState viewState, final float alpha) { - child.animate().setInterpolator(mFastOutSlowInInterpolator) - .translationY(viewState.yTranslation) - .translationZ(viewState.zTranslation) - .setDuration(newDuration) - .withEndAction(new Runnable() { - @Override - public void run() { - mAnimationIsRunning = false; - if (hasFinishAction) { - mHandledEvents.clear(); - mHostLayout.onChildAnimationFinished(); - } - if (alpha == 0) { - child.setVisibility(View.INVISIBLE); - } - } - }); + /** + * Start an animation to the given viewState + */ + private void startAnimations(final ExpandableView child, StackScrollState.ViewState viewState, + boolean hasNewEvents) { + int childVisibility = child.getVisibility(); + boolean wasVisible = childVisibility == View.VISIBLE; + final float alpha = viewState.alpha; + if (!wasVisible && alpha != 0 && !viewState.gone) { + child.setVisibility(View.VISIBLE); + } + // start translationY animation + if (child.getTranslationY() != viewState.yTranslation) { + startYTranslationAnimation(child, viewState, hasNewEvents); + } + // start translationZ animation + if (child.getTranslationZ() != viewState.zTranslation) { + startZTranslationAnimation(child, viewState, hasNewEvents); + } + // start alpha animation if (alpha != child.getAlpha()) { - child.animate().withLayer().alpha(alpha); + startAlphaAnimation(child, viewState, hasNewEvents); + } + // start height animation + if (viewState.height != child.getActualHeight()) { + startHeightAnimation(child, viewState, hasNewEvents); } } - private void startHeightAnimation(long newDuration, final ExpandableView child, - StackScrollState.ViewState viewState, int currentHeigth) { - ValueAnimator heightAnimator = ValueAnimator.ofInt(currentHeigth, viewState.height); - heightAnimator.setInterpolator(mFastOutSlowInInterpolator); - heightAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + private void startHeightAnimation(final ExpandableView child, + StackScrollState.ViewState viewState, boolean hasNewEvents) { + Integer previousEndValue = getChildTag(child,TAG_END_HEIGHT); + if (previousEndValue != null && previousEndValue == viewState.height) { + return; + } + ValueAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_HEIGHT); + long newDuration = cancelAnimatorAndGetNewDuration(previousAnimator, hasNewEvents); + if (newDuration <= 0) { + if (previousAnimator == null) { + // no animation was running, but also no new animation should be performed, + // lets just apply the value + child.setActualHeight(viewState.height); + } + return; + } + + ValueAnimator animator = ValueAnimator.ofInt(child.getActualHeight(), viewState.height); + animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { child.setActualHeight((int) animation.getAnimatedValue()); } }); - heightAnimator.setDuration(newDuration); - heightAnimator.start(); + animator.setInterpolator(mFastOutSlowInInterpolator); + animator.setDuration(newDuration); + 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_HEIGHT, null); + child.setTag(TAG_END_HEIGHT, null); + } + }); + animator.start(); + child.setTag(TAG_ANIMATOR_HEIGHT, animator); + child.setTag(TAG_END_HEIGHT, viewState.height); + } + + private void startAlphaAnimation(final ExpandableView child, + final StackScrollState.ViewState viewState, boolean hasNewEvents) { + final float endAlpha = viewState.alpha; + Float previousEndValue = getChildTag(child,TAG_END_ALPHA); + if (previousEndValue != null && previousEndValue == endAlpha) { + return; + } + ObjectAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_ALPHA); + long newDuration = cancelAnimatorAndGetNewDuration(previousAnimator, hasNewEvents); + if (newDuration <= 0) { + if (previousAnimator == null) { + // no animation was running, but also no new animation should be performed, + // lets just apply the value + child.setAlpha(endAlpha); + if (endAlpha == 0) { + child.setVisibility(View.INVISIBLE); + } + } + return; + } + + ObjectAnimator animator = ObjectAnimator.ofFloat(child, View.ALPHA, + child.getAlpha(), endAlpha); + animator.setInterpolator(mFastOutSlowInInterpolator); + // Handle layer type + final int currentLayerType = child.getLayerType(); + child.setLayerType(View.LAYER_TYPE_HARDWARE, null); + animator.addListener(new AnimatorListenerAdapter() { + public boolean mWasCancelled; + + @Override + public void onAnimationEnd(Animator animation) { + child.setLayerType(currentLayerType, null); + if (endAlpha == 0 && !mWasCancelled) { + child.setVisibility(View.INVISIBLE); + } + child.setTag(TAG_ANIMATOR_ALPHA, null); + child.setTag(TAG_END_ALPHA, null); + } + + @Override + public void onAnimationCancel(Animator animation) { + mWasCancelled = true; + } + + @Override + public void onAnimationStart(Animator animation) { + mWasCancelled = false; + } + }); + animator.addListener(getGlobalAnimationFinishedListener()); + // remove the tag when the animation is finished + animator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + + } + }); + animator.start(); + child.setTag(TAG_ANIMATOR_ALPHA, animator); + child.setTag(TAG_END_ALPHA, endAlpha); } /** - * Initialize the viewStates for the added children + * @return an adapter which ensures that onAnimationFinished is called once no animation is + * running anymore + */ + private AnimatorListenerAdapter getGlobalAnimationFinishedListener() { + if (!mAnimationListenerPool.empty()) { + return mAnimationListenerPool.pop(); + } + + // We need to create a new one, no reusable ones found + return new AnimatorListenerAdapter() { + private boolean mWasCancelled; + + @Override + public void onAnimationEnd(Animator animation) { + mAnimatorSet.remove(animation); + if (mAnimatorSet.isEmpty() && !mWasCancelled) { + onAnimationFinished(); + } + mAnimationListenerPool.push(this); + } + + @Override + public void onAnimationCancel(Animator animation) { + mWasCancelled = true; + } + + @Override + public void onAnimationStart(Animator animation) { + mAnimatorSet.add(animation); + mWasCancelled = false; + } + }; + + } + + private void startZTranslationAnimation(final ExpandableView child, + final StackScrollState.ViewState viewState, boolean hasNewEvents) { + Float previousEndValue = getChildTag(child,TAG_END_TRANSLATION_Z); + if (previousEndValue != null && previousEndValue == viewState.zTranslation) { + return; + } + ObjectAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_TRANSLATION_Z); + long newDuration = cancelAnimatorAndGetNewDuration(previousAnimator, hasNewEvents); + if (newDuration <= 0) { + if (previousAnimator == null) { + // no animation was running, but also no new animation should be performed, + // lets just apply the value + child.setTranslationZ(viewState.zTranslation); + } + return; + } + + ObjectAnimator animator = ObjectAnimator.ofFloat(child, View.TRANSLATION_Z, + child.getTranslationZ(), viewState.zTranslation); + animator.setInterpolator(mFastOutSlowInInterpolator); + 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_TRANSLATION_Z, null); + child.setTag(TAG_END_TRANSLATION_Z, null); + } + }); + animator.start(); + child.setTag(TAG_ANIMATOR_TRANSLATION_Z, animator); + child.setTag(TAG_END_TRANSLATION_Z, viewState.zTranslation); + } + + private void startYTranslationAnimation(final ExpandableView child, + StackScrollState.ViewState viewState, boolean hasNewEvents) { + Float previousEndValue = getChildTag(child,TAG_END_TRANSLATION_Y); + if (previousEndValue != null && previousEndValue == viewState.yTranslation) { + return; + } + ObjectAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_TRANSLATION_Y); + long newDuration = cancelAnimatorAndGetNewDuration(previousAnimator, hasNewEvents); + if (newDuration <= 0) { + if (previousAnimator == null) { + // no animation was running, but also no new animation should be performed, + // lets just apply the value + child.setTranslationY(viewState.yTranslation); + } + return; + } + + ObjectAnimator animator = ObjectAnimator.ofFloat(child, View.TRANSLATION_Y, + child.getTranslationY(), viewState.yTranslation); + animator.setInterpolator(mFastOutSlowInInterpolator); + 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_TRANSLATION_Y, null); + child.setTag(TAG_END_TRANSLATION_Y, null); + } + }); + animator.start(); + child.setTag(TAG_ANIMATOR_TRANSLATION_Y, animator); + child.setTag(TAG_END_TRANSLATION_Y, viewState.yTranslation); + } + + private <T> T getChildTag(View child, int tag) { + return (T) child.getTag(tag); + } + + /** + * Cancel the previous animator and get the duration of the new animation. * - * @param animationEvents the animation events who contain the added children + * @param previousAnimator the animator which was running before + * @param hasNewEvents indicating whether new events came in in this animation + * @return the new duration + */ + private long cancelAnimatorAndGetNewDuration(ValueAnimator previousAnimator, + boolean hasNewEvents) { + if (previousAnimator != null) { + previousAnimator.cancel(); + if (!hasNewEvents) { + // This is only an update, no new event came in. lets just take the remaining + // duration as the new duration + return (long) ((1.0f - previousAnimator.getAnimatedFraction()) * + previousAnimator.getDuration()); + } + } else if (!hasNewEvents){ + return 0; + } + return ANIMATION_DURATION; + } + + private void onAnimationFinished() { + mHandledEvents.clear(); + mNewEvents.clear(); + mHostLayout.onChildAnimationFinished(); + } + + /** + * Process the animationEvents for a new animation + * + * @param animationEvents the animation events for the animation to perform * @param finalState the final state to animate to */ - private void initializeAddedViewStates( + private void processAnimationEvents( ArrayList<NotificationStackScrollLayout.AnimationEvent> animationEvents, StackScrollState finalState) { + mNewEvents.clear(); for (NotificationStackScrollLayout.AnimationEvent event: animationEvents) { View changingView = event.changingView; - if (event.animationType == NotificationStackScrollLayout.AnimationEvent - .ANIMATION_TYPE_ADD && !mHandledEvents.contains(event)) { - - // 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 (!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); } - changingView.setAlpha(0); - changingView.setTranslationY(viewState.yTranslation); - changingView.setTranslationZ(viewState.zTranslation); mHandledEvents.add(event); + mNewEvents.add(event); } } } |