am 4ed3964f: Merge "Refactored the notification animations, improved stack scroller"

* commit '4ed3964fbba5716a9b43a9883a2f59db56cb5aa0':
  Refactored the notification animations, improved stack scroller
This commit is contained in:
Selim Cinek
2014-05-07 13:48:19 +00:00
committed by Android Git Automerger
11 changed files with 457 additions and 88 deletions

View File

@@ -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>

View File

@@ -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);
}
}

View File

@@ -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();

View File

@@ -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();

View File

@@ -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;
}

View File

@@ -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);

View File

@@ -236,6 +236,10 @@ public class HeadsUpNotificationView extends FrameLayout implements SwipeHelper.
mContentHolder.setAlpha(1f); // sometimes this isn't quite reset
}
@Override
public void onChildSnappedBack(View animView) {
}
@Override
public View getChildAtPosition(MotionEvent ev) {
return mContentHolder;

View File

@@ -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;

View File

@@ -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 {
/**

View File

@@ -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;
}
}
}

View File

@@ -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)) {
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;
// 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);
}
}
}