summaryrefslogtreecommitdiffstats
path: root/packages/SystemUI
diff options
context:
space:
mode:
authorSelim Cinek <cinek@google.com>2014-05-07 13:43:43 +0000
committerAndroid (Google) Code Review <android-gerrit@google.com>2014-05-07 13:43:43 +0000
commit2c5d4a08d2c94420b989d5a0886fed3c83c0226c (patch)
tree1d0cb4aae3e96fc3de9bc0967be5b0034610d64b /packages/SystemUI
parent05ee2bd617f50fe129cf36ae306e6059335e02aa (diff)
parenteb973565f3efc6417ca35363e4d6c642947775d8 (diff)
downloadframeworks_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')
-rw-r--r--packages/SystemUI/res/values/ids.xml30
-rw-r--r--packages/SystemUI/src/com/android/systemui/SwipeHelper.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/recent/RecentsHorizontalScrollView.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/recent/RecentsVerticalScrollView.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpNotificationView.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java55
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java37
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java13
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java384
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);
}
}
}