am 4ed3964f: Merge "Refactored the notification animations, improved stack scroller"
* commit '4ed3964fbba5716a9b43a9883a2f59db56cb5aa0': Refactored the notification animations, improved stack scroller
This commit is contained in:
30
packages/SystemUI/res/values/ids.xml
Normal file
30
packages/SystemUI/res/values/ids.xml
Normal 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>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
/**
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user