2 * Copyright (C) 2014 The Android Open Source Project
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License
17 package com.android.systemui.statusbar.stack;
19 import android.animation.Animator;
20 import android.animation.AnimatorListenerAdapter;
21 import android.animation.ObjectAnimator;
22 import android.animation.PropertyValuesHolder;
23 import android.animation.ValueAnimator;
24 import android.view.View;
25 import android.view.ViewGroup;
26 import android.view.animation.Interpolator;
28 import com.android.systemui.Interpolators;
29 import com.android.systemui.R;
30 import com.android.systemui.statusbar.ExpandableNotificationRow;
31 import com.android.systemui.statusbar.ExpandableView;
32 import com.android.systemui.statusbar.policy.HeadsUpManager;
34 import java.util.ArrayList;
35 import java.util.HashSet;
36 import java.util.Stack;
39 * An stack state animator which handles animations to new StackScrollStates
41 public class StackStateAnimator {
43 public static final int ANIMATION_DURATION_STANDARD = 360;
44 public static final int ANIMATION_DURATION_GO_TO_FULL_SHADE = 448;
45 public static final int ANIMATION_DURATION_APPEAR_DISAPPEAR = 464;
46 public static final int ANIMATION_DURATION_DIMMED_ACTIVATED = 220;
47 public static final int ANIMATION_DURATION_HEADS_UP_APPEAR = 650;
48 public static final int ANIMATION_DURATION_HEADS_UP_DISAPPEAR = 230;
49 public static final int ANIMATION_DELAY_PER_ELEMENT_INTERRUPTING = 80;
50 public static final int ANIMATION_DELAY_PER_ELEMENT_MANUAL = 32;
51 public static final int ANIMATION_DELAY_PER_ELEMENT_GO_TO_FULL_SHADE = 48;
52 public static final int ANIMATION_DELAY_PER_ELEMENT_DARK = 24;
53 public static final int DELAY_EFFECT_MAX_INDEX_DIFFERENCE = 2;
54 public static final int ANIMATION_DELAY_HEADS_UP = 120;
56 private static final int TAG_ANIMATOR_TRANSLATION_Y = R.id.translation_y_animator_tag;
57 private static final int TAG_ANIMATOR_TRANSLATION_Z = R.id.translation_z_animator_tag;
58 private static final int TAG_ANIMATOR_ALPHA = R.id.alpha_animator_tag;
59 private static final int TAG_ANIMATOR_HEIGHT = R.id.height_animator_tag;
60 private static final int TAG_ANIMATOR_TOP_INSET = R.id.top_inset_animator_tag;
61 private static final int TAG_ANIMATOR_SHADOW_ALPHA = R.id.shadow_alpha_animator_tag;
62 private static final int TAG_END_TRANSLATION_Y = R.id.translation_y_animator_end_value_tag;
63 private static final int TAG_END_TRANSLATION_Z = R.id.translation_z_animator_end_value_tag;
64 private static final int TAG_END_ALPHA = R.id.alpha_animator_end_value_tag;
65 private static final int TAG_END_HEIGHT = R.id.height_animator_end_value_tag;
66 private static final int TAG_END_TOP_INSET = R.id.top_inset_animator_end_value_tag;
67 private static final int TAG_END_SHADOW_ALPHA = R.id.shadow_alpha_animator_end_value_tag;
68 private static final int TAG_START_TRANSLATION_Y = R.id.translation_y_animator_start_value_tag;
69 private static final int TAG_START_TRANSLATION_Z = R.id.translation_z_animator_start_value_tag;
70 private static final int TAG_START_ALPHA = R.id.alpha_animator_start_value_tag;
71 private static final int TAG_START_HEIGHT = R.id.height_animator_start_value_tag;
72 private static final int TAG_START_TOP_INSET = R.id.top_inset_animator_start_value_tag;
73 private static final int TAG_START_SHADOW_ALPHA = R.id.shadow_alpha_animator_start_value_tag;
75 private final Interpolator mHeadsUpAppearInterpolator;
76 private final int mGoToFullShadeAppearingTranslation;
77 private final StackViewState mTmpState = new StackViewState();
78 public NotificationStackScrollLayout mHostLayout;
79 private ArrayList<NotificationStackScrollLayout.AnimationEvent> mNewEvents =
81 private ArrayList<View> mNewAddChildren = new ArrayList<>();
82 private HashSet<View> mHeadsUpAppearChildren = new HashSet<>();
83 private HashSet<View> mHeadsUpDisappearChildren = new HashSet<>();
84 private HashSet<Animator> mAnimatorSet = new HashSet<>();
85 private Stack<AnimatorListenerAdapter> mAnimationListenerPool = new Stack<>();
86 private AnimationFilter mAnimationFilter = new AnimationFilter();
87 private long mCurrentLength;
88 private long mCurrentAdditionalDelay;
90 /** The current index for the last child which was not added in this event set. */
91 private int mCurrentLastNotAddedIndex;
92 private ValueAnimator mTopOverScrollAnimator;
93 private ValueAnimator mBottomOverScrollAnimator;
94 private int mHeadsUpAppearHeightBottom;
95 private boolean mShadeExpanded;
96 private ArrayList<View> mChildrenToClearFromOverlay = new ArrayList<>();
98 public StackStateAnimator(NotificationStackScrollLayout hostLayout) {
99 mHostLayout = hostLayout;
100 mGoToFullShadeAppearingTranslation =
101 hostLayout.getContext().getResources().getDimensionPixelSize(
102 R.dimen.go_to_full_shade_appearing_translation);
103 mHeadsUpAppearInterpolator = new HeadsUpAppearInterpolator();
106 public boolean isRunning() {
107 return !mAnimatorSet.isEmpty();
110 public void startAnimationForEvents(
111 ArrayList<NotificationStackScrollLayout.AnimationEvent> mAnimationEvents,
112 StackScrollState finalState, long additionalDelay) {
114 processAnimationEvents(mAnimationEvents, finalState);
116 int childCount = mHostLayout.getChildCount();
117 mAnimationFilter.applyCombination(mNewEvents);
118 mCurrentAdditionalDelay = additionalDelay;
119 mCurrentLength = NotificationStackScrollLayout.AnimationEvent.combineLength(mNewEvents);
120 mCurrentLastNotAddedIndex = findLastNotAddedIndex(finalState);
121 for (int i = 0; i < childCount; i++) {
122 final ExpandableView child = (ExpandableView) mHostLayout.getChildAt(i);
124 StackViewState viewState = finalState.getViewStateForView(child);
125 if (viewState == null || child.getVisibility() == View.GONE
126 || applyWithoutAnimation(child, viewState, finalState)) {
130 startStackAnimations(child, viewState, finalState, i, -1 /* fixedDelay */);
133 // no child has preformed any animation, lets finish
134 onAnimationFinished();
136 mHeadsUpAppearChildren.clear();
137 mHeadsUpDisappearChildren.clear();
139 mNewAddChildren.clear();
143 * Determines if a view should not perform an animation and applies it directly.
145 * @return true if no animation should be performed
147 private boolean applyWithoutAnimation(ExpandableView child, StackViewState viewState,
148 StackScrollState finalState) {
149 if (mShadeExpanded) {
152 if (getChildTag(child, TAG_ANIMATOR_TRANSLATION_Y) != null) {
153 // A Y translation animation is running
156 if (mHeadsUpDisappearChildren.contains(child) || mHeadsUpAppearChildren.contains(child)) {
157 // This is a heads up animation
160 if (NotificationStackScrollLayout.isPinnedHeadsUp(child)) {
161 // This is another headsUp which might move. Let's animate!
164 finalState.applyState(child, viewState);
168 private int findLastNotAddedIndex(StackScrollState finalState) {
169 int childCount = mHostLayout.getChildCount();
170 for (int i = childCount - 1; i >= 0; i--) {
171 final ExpandableView child = (ExpandableView) mHostLayout.getChildAt(i);
173 StackViewState viewState = finalState.getViewStateForView(child);
174 if (viewState == null || child.getVisibility() == View.GONE) {
177 if (!mNewAddChildren.contains(child)) {
178 return viewState.notGoneIndex;
186 * Start an animation to the given {@link StackViewState}.
188 * @param child the child to start the animation on
189 * @param viewState the {@link StackViewState} of the view to animate to
190 * @param finalState the final state after the animation
191 * @param i the index of the view; only relevant if the view is the speed bump and is
193 * @param fixedDelay a fixed delay if desired or -1 if the delay should be calculated
195 public void startStackAnimations(final ExpandableView child, StackViewState viewState,
196 StackScrollState finalState, int i, long fixedDelay) {
197 boolean wasAdded = mNewAddChildren.contains(child);
198 long duration = mCurrentLength;
199 if (wasAdded && mAnimationFilter.hasGoToFullShadeEvent) {
200 child.setTranslationY(child.getTranslationY() + mGoToFullShadeAppearingTranslation);
201 float longerDurationFactor = viewState.notGoneIndex - mCurrentLastNotAddedIndex;
202 longerDurationFactor = (float) Math.pow(longerDurationFactor, 0.7f);
203 duration = ANIMATION_DURATION_APPEAR_DISAPPEAR + 50 +
204 (long) (100 * longerDurationFactor);
206 boolean yTranslationChanging = child.getTranslationY() != viewState.yTranslation;
207 boolean zTranslationChanging = child.getTranslationZ() != viewState.zTranslation;
208 boolean alphaChanging = viewState.alpha != child.getAlpha();
209 boolean heightChanging = viewState.height != child.getActualHeight();
210 boolean shadowAlphaChanging = viewState.shadowAlpha != child.getShadowAlpha();
211 boolean darkChanging = viewState.dark != child.isDark();
212 boolean topInsetChanging = viewState.clipTopAmount != child.getClipTopAmount();
213 boolean hasDelays = mAnimationFilter.hasDelays;
214 boolean isDelayRelevant = yTranslationChanging || zTranslationChanging || alphaChanging
215 || heightChanging || topInsetChanging || darkChanging || shadowAlphaChanging;
217 if (fixedDelay != -1) {
219 } else if (hasDelays && isDelayRelevant || wasAdded) {
220 delay = mCurrentAdditionalDelay + calculateChildAnimationDelay(viewState, finalState);
223 startViewAnimations(child, viewState, delay, duration);
225 // start height animation
226 if (heightChanging) {
227 startHeightAnimation(child, viewState, duration, delay);
229 abortAnimation(child, TAG_ANIMATOR_HEIGHT);
232 // start shadow alpha animation
233 if (shadowAlphaChanging) {
234 startShadowAlphaAnimation(child, viewState, duration, delay);
236 abortAnimation(child, TAG_ANIMATOR_SHADOW_ALPHA);
239 // start top inset animation
240 if (topInsetChanging) {
241 startInsetAnimation(child, viewState, duration, delay);
243 abortAnimation(child, TAG_ANIMATOR_TOP_INSET);
246 // start dimmed animation
247 child.setDimmed(viewState.dimmed, mAnimationFilter.animateDimmed);
249 // apply speed bump state
250 child.setBelowSpeedBump(viewState.belowSpeedBump);
252 // start hiding sensitive animation
253 child.setHideSensitive(viewState.hideSensitive, mAnimationFilter.animateHideSensitive,
256 // start dark animation
257 child.setDark(viewState.dark, mAnimationFilter.animateDark, delay);
260 child.performAddAnimation(delay, mCurrentLength);
262 if (child instanceof ExpandableNotificationRow) {
263 ExpandableNotificationRow row = (ExpandableNotificationRow) child;
264 row.startChildAnimation(finalState, this, delay, duration);
269 * Start an animation to a new {@link ViewState}.
271 * @param child the child to start the animation on
272 * @param viewState the {@link StackViewState} of the view to animate to
273 * @param delay a fixed delay
274 * @param duration the duration of the animation
276 public void startViewAnimations(View child, ViewState viewState, long delay, long duration) {
277 boolean wasVisible = child.getVisibility() == View.VISIBLE;
278 final float alpha = viewState.alpha;
279 if (!wasVisible && (alpha != 0 || child.getAlpha() != 0)
280 && !viewState.gone && !viewState.hidden) {
281 child.setVisibility(View.VISIBLE);
283 boolean yTranslationChanging = child.getTranslationY() != viewState.yTranslation;
284 boolean zTranslationChanging = child.getTranslationZ() != viewState.zTranslation;
285 float childAlpha = child.getAlpha();
286 boolean alphaChanging = viewState.alpha != childAlpha;
287 if (child instanceof ExpandableView) {
288 // We don't want views to change visibility when they are animating to GONE
289 alphaChanging &= !((ExpandableView) child).willBeGone();
292 // start translationY animation
293 if (yTranslationChanging) {
294 startYTranslationAnimation(child, viewState, duration, delay);
296 abortAnimation(child, TAG_ANIMATOR_TRANSLATION_Y);
299 // start translationZ animation
300 if (zTranslationChanging) {
301 startZTranslationAnimation(child, viewState, duration, delay);
303 abortAnimation(child, TAG_ANIMATOR_TRANSLATION_Z);
306 // start alpha animation
307 if (alphaChanging && child.getTranslationX() == 0) {
308 startAlphaAnimation(child, viewState, duration, delay);
310 abortAnimation(child, TAG_ANIMATOR_ALPHA);
314 private void abortAnimation(View child, int animatorTag) {
315 Animator previousAnimator = getChildTag(child, animatorTag);
316 if (previousAnimator != null) {
317 previousAnimator.cancel();
321 private long calculateChildAnimationDelay(StackViewState viewState,
322 StackScrollState finalState) {
323 if (mAnimationFilter.hasDarkEvent) {
324 return calculateDelayDark(viewState);
326 if (mAnimationFilter.hasGoToFullShadeEvent) {
327 return calculateDelayGoToFullShade(viewState);
329 if (mAnimationFilter.hasHeadsUpDisappearClickEvent) {
330 return ANIMATION_DELAY_HEADS_UP;
333 for (NotificationStackScrollLayout.AnimationEvent event : mNewEvents) {
334 long delayPerElement = ANIMATION_DELAY_PER_ELEMENT_INTERRUPTING;
335 switch (event.animationType) {
336 case NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_ADD: {
337 int ownIndex = viewState.notGoneIndex;
338 int changingIndex = finalState
339 .getViewStateForView(event.changingView).notGoneIndex;
340 int difference = Math.abs(ownIndex - changingIndex);
341 difference = Math.max(0, Math.min(DELAY_EFFECT_MAX_INDEX_DIFFERENCE,
343 long delay = (DELAY_EFFECT_MAX_INDEX_DIFFERENCE - difference) * delayPerElement;
344 minDelay = Math.max(delay, minDelay);
347 case NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_REMOVE_SWIPED_OUT:
348 delayPerElement = ANIMATION_DELAY_PER_ELEMENT_MANUAL;
349 case NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_REMOVE: {
350 int ownIndex = viewState.notGoneIndex;
351 boolean noNextView = event.viewAfterChangingView == null;
352 View viewAfterChangingView = noNextView
353 ? mHostLayout.getLastChildNotGone()
354 : event.viewAfterChangingView;
356 int nextIndex = finalState
357 .getViewStateForView(viewAfterChangingView).notGoneIndex;
358 if (ownIndex >= nextIndex) {
359 // we only have the view afterwards
362 int difference = Math.abs(ownIndex - nextIndex);
363 difference = Math.max(0, Math.min(DELAY_EFFECT_MAX_INDEX_DIFFERENCE,
365 long delay = difference * delayPerElement;
366 minDelay = Math.max(delay, minDelay);
376 private long calculateDelayDark(StackViewState viewState) {
378 if (mAnimationFilter.darkAnimationOriginIndex ==
379 NotificationStackScrollLayout.AnimationEvent.DARK_ANIMATION_ORIGIN_INDEX_ABOVE) {
381 } else if (mAnimationFilter.darkAnimationOriginIndex ==
382 NotificationStackScrollLayout.AnimationEvent.DARK_ANIMATION_ORIGIN_INDEX_BELOW) {
383 referenceIndex = mHostLayout.getNotGoneChildCount() - 1;
385 referenceIndex = mAnimationFilter.darkAnimationOriginIndex;
387 return Math.abs(referenceIndex - viewState.notGoneIndex) * ANIMATION_DELAY_PER_ELEMENT_DARK;
390 private long calculateDelayGoToFullShade(StackViewState viewState) {
391 float index = viewState.notGoneIndex;
392 index = (float) Math.pow(index, 0.7f);
393 return (long) (index * ANIMATION_DELAY_PER_ELEMENT_GO_TO_FULL_SHADE);
396 private void startShadowAlphaAnimation(final ExpandableView child,
397 StackViewState viewState, long duration, long delay) {
398 Float previousStartValue = getChildTag(child, TAG_START_SHADOW_ALPHA);
399 Float previousEndValue = getChildTag(child, TAG_END_SHADOW_ALPHA);
400 float newEndValue = viewState.shadowAlpha;
401 if (previousEndValue != null && previousEndValue == newEndValue) {
404 ValueAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_SHADOW_ALPHA);
405 if (!mAnimationFilter.animateShadowAlpha) {
406 // just a local update was performed
407 if (previousAnimator != null) {
408 // we need to increase all animation keyframes of the previous animator by the
409 // relative change to the end value
410 PropertyValuesHolder[] values = previousAnimator.getValues();
411 float relativeDiff = newEndValue - previousEndValue;
412 float newStartValue = previousStartValue + relativeDiff;
413 values[0].setFloatValues(newStartValue, newEndValue);
414 child.setTag(TAG_START_SHADOW_ALPHA, newStartValue);
415 child.setTag(TAG_END_SHADOW_ALPHA, newEndValue);
416 previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
419 // no new animation needed, let's just apply the value
420 child.setShadowAlpha(newEndValue);
425 ValueAnimator animator = ValueAnimator.ofFloat(child.getShadowAlpha(), newEndValue);
426 animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
428 public void onAnimationUpdate(ValueAnimator animation) {
429 child.setShadowAlpha((float) animation.getAnimatedValue());
432 animator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
433 long newDuration = cancelAnimatorAndGetNewDuration(duration, previousAnimator);
434 animator.setDuration(newDuration);
435 if (delay > 0 && (previousAnimator == null
436 || previousAnimator.getAnimatedFraction() == 0)) {
437 animator.setStartDelay(delay);
439 animator.addListener(getGlobalAnimationFinishedListener());
440 // remove the tag when the animation is finished
441 animator.addListener(new AnimatorListenerAdapter() {
443 public void onAnimationEnd(Animator animation) {
444 child.setTag(TAG_ANIMATOR_SHADOW_ALPHA, null);
445 child.setTag(TAG_START_SHADOW_ALPHA, null);
446 child.setTag(TAG_END_SHADOW_ALPHA, null);
449 startAnimator(animator);
450 child.setTag(TAG_ANIMATOR_SHADOW_ALPHA, animator);
451 child.setTag(TAG_START_SHADOW_ALPHA, child.getShadowAlpha());
452 child.setTag(TAG_END_SHADOW_ALPHA, newEndValue);
455 private void startHeightAnimation(final ExpandableView child,
456 StackViewState viewState, long duration, long delay) {
457 Integer previousStartValue = getChildTag(child, TAG_START_HEIGHT);
458 Integer previousEndValue = getChildTag(child, TAG_END_HEIGHT);
459 int newEndValue = viewState.height;
460 if (previousEndValue != null && previousEndValue == newEndValue) {
463 ValueAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_HEIGHT);
464 if (!mAnimationFilter.animateHeight) {
465 // just a local update was performed
466 if (previousAnimator != null) {
467 // we need to increase all animation keyframes of the previous animator by the
468 // relative change to the end value
469 PropertyValuesHolder[] values = previousAnimator.getValues();
470 int relativeDiff = newEndValue - previousEndValue;
471 int newStartValue = previousStartValue + relativeDiff;
472 values[0].setIntValues(newStartValue, newEndValue);
473 child.setTag(TAG_START_HEIGHT, newStartValue);
474 child.setTag(TAG_END_HEIGHT, newEndValue);
475 previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
478 // no new animation needed, let's just apply the value
479 child.setActualHeight(newEndValue, false);
484 ValueAnimator animator = ValueAnimator.ofInt(child.getActualHeight(), newEndValue);
485 animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
487 public void onAnimationUpdate(ValueAnimator animation) {
488 child.setActualHeight((int) animation.getAnimatedValue(),
489 false /* notifyListeners */);
492 animator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
493 long newDuration = cancelAnimatorAndGetNewDuration(duration, previousAnimator);
494 animator.setDuration(newDuration);
495 if (delay > 0 && (previousAnimator == null
496 || previousAnimator.getAnimatedFraction() == 0)) {
497 animator.setStartDelay(delay);
499 animator.addListener(getGlobalAnimationFinishedListener());
500 // remove the tag when the animation is finished
501 animator.addListener(new AnimatorListenerAdapter() {
502 boolean mWasCancelled;
505 public void onAnimationEnd(Animator animation) {
506 child.setTag(TAG_ANIMATOR_HEIGHT, null);
507 child.setTag(TAG_START_HEIGHT, null);
508 child.setTag(TAG_END_HEIGHT, null);
509 child.setActualHeightAnimating(false);
510 if (!mWasCancelled && child instanceof ExpandableNotificationRow) {
511 ((ExpandableNotificationRow) child).setGroupExpansionChanging(
512 false /* isExpansionChanging */);
517 public void onAnimationStart(Animator animation) {
518 mWasCancelled = false;
522 public void onAnimationCancel(Animator animation) {
523 mWasCancelled = true;
526 startAnimator(animator);
527 child.setTag(TAG_ANIMATOR_HEIGHT, animator);
528 child.setTag(TAG_START_HEIGHT, child.getActualHeight());
529 child.setTag(TAG_END_HEIGHT, newEndValue);
530 child.setActualHeightAnimating(true);
533 private void startInsetAnimation(final ExpandableView child,
534 StackViewState viewState, long duration, long delay) {
535 Integer previousStartValue = getChildTag(child, TAG_START_TOP_INSET);
536 Integer previousEndValue = getChildTag(child, TAG_END_TOP_INSET);
537 int newEndValue = viewState.clipTopAmount;
538 if (previousEndValue != null && previousEndValue == newEndValue) {
541 ValueAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_TOP_INSET);
542 if (!mAnimationFilter.animateTopInset) {
543 // just a local update was performed
544 if (previousAnimator != null) {
545 // we need to increase all animation keyframes of the previous animator by the
546 // relative change to the end value
547 PropertyValuesHolder[] values = previousAnimator.getValues();
548 int relativeDiff = newEndValue - previousEndValue;
549 int newStartValue = previousStartValue + relativeDiff;
550 values[0].setIntValues(newStartValue, newEndValue);
551 child.setTag(TAG_START_TOP_INSET, newStartValue);
552 child.setTag(TAG_END_TOP_INSET, newEndValue);
553 previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
556 // no new animation needed, let's just apply the value
557 child.setClipTopAmount(newEndValue);
562 ValueAnimator animator = ValueAnimator.ofInt(child.getClipTopAmount(), newEndValue);
563 animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
565 public void onAnimationUpdate(ValueAnimator animation) {
566 child.setClipTopAmount((int) animation.getAnimatedValue());
569 animator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
570 long newDuration = cancelAnimatorAndGetNewDuration(duration, previousAnimator);
571 animator.setDuration(newDuration);
572 if (delay > 0 && (previousAnimator == null
573 || previousAnimator.getAnimatedFraction() == 0)) {
574 animator.setStartDelay(delay);
576 animator.addListener(getGlobalAnimationFinishedListener());
577 // remove the tag when the animation is finished
578 animator.addListener(new AnimatorListenerAdapter() {
580 public void onAnimationEnd(Animator animation) {
581 child.setTag(TAG_ANIMATOR_TOP_INSET, null);
582 child.setTag(TAG_START_TOP_INSET, null);
583 child.setTag(TAG_END_TOP_INSET, null);
586 startAnimator(animator);
587 child.setTag(TAG_ANIMATOR_TOP_INSET, animator);
588 child.setTag(TAG_START_TOP_INSET, child.getClipTopAmount());
589 child.setTag(TAG_END_TOP_INSET, newEndValue);
592 private void startAlphaAnimation(final View child,
593 final ViewState viewState, long duration, long delay) {
594 Float previousStartValue = getChildTag(child,TAG_START_ALPHA);
595 Float previousEndValue = getChildTag(child,TAG_END_ALPHA);
596 final float newEndValue = viewState.alpha;
597 if (previousEndValue != null && previousEndValue == newEndValue) {
600 ObjectAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_ALPHA);
601 if (!mAnimationFilter.animateAlpha) {
602 // just a local update was performed
603 if (previousAnimator != null) {
604 // we need to increase all animation keyframes of the previous animator by the
605 // relative change to the end value
606 PropertyValuesHolder[] values = previousAnimator.getValues();
607 float relativeDiff = newEndValue - previousEndValue;
608 float newStartValue = previousStartValue + relativeDiff;
609 values[0].setFloatValues(newStartValue, newEndValue);
610 child.setTag(TAG_START_ALPHA, newStartValue);
611 child.setTag(TAG_END_ALPHA, newEndValue);
612 previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
615 // no new animation needed, let's just apply the value
616 child.setAlpha(newEndValue);
617 if (newEndValue == 0) {
618 child.setVisibility(View.INVISIBLE);
623 ObjectAnimator animator = ObjectAnimator.ofFloat(child, View.ALPHA,
624 child.getAlpha(), newEndValue);
625 animator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
627 child.setLayerType(View.LAYER_TYPE_HARDWARE, null);
628 animator.addListener(new AnimatorListenerAdapter() {
629 public boolean mWasCancelled;
632 public void onAnimationEnd(Animator animation) {
633 child.setLayerType(View.LAYER_TYPE_NONE, null);
634 if (newEndValue == 0 && !mWasCancelled) {
635 child.setVisibility(View.INVISIBLE);
637 // remove the tag when the animation is finished
638 child.setTag(TAG_ANIMATOR_ALPHA, null);
639 child.setTag(TAG_START_ALPHA, null);
640 child.setTag(TAG_END_ALPHA, null);
644 public void onAnimationCancel(Animator animation) {
645 mWasCancelled = true;
649 public void onAnimationStart(Animator animation) {
650 mWasCancelled = false;
653 long newDuration = cancelAnimatorAndGetNewDuration(duration, previousAnimator);
654 animator.setDuration(newDuration);
655 if (delay > 0 && (previousAnimator == null
656 || previousAnimator.getAnimatedFraction() == 0)) {
657 animator.setStartDelay(delay);
659 animator.addListener(getGlobalAnimationFinishedListener());
661 startAnimator(animator);
662 child.setTag(TAG_ANIMATOR_ALPHA, animator);
663 child.setTag(TAG_START_ALPHA, child.getAlpha());
664 child.setTag(TAG_END_ALPHA, newEndValue);
667 private void startZTranslationAnimation(final View child,
668 final ViewState viewState, long duration, long delay) {
669 Float previousStartValue = getChildTag(child,TAG_START_TRANSLATION_Z);
670 Float previousEndValue = getChildTag(child,TAG_END_TRANSLATION_Z);
671 float newEndValue = viewState.zTranslation;
672 if (previousEndValue != null && previousEndValue == newEndValue) {
675 ObjectAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_TRANSLATION_Z);
676 if (!mAnimationFilter.animateZ) {
677 // just a local update was performed
678 if (previousAnimator != null) {
679 // we need to increase all animation keyframes of the previous animator by the
680 // relative change to the end value
681 PropertyValuesHolder[] values = previousAnimator.getValues();
682 float relativeDiff = newEndValue - previousEndValue;
683 float newStartValue = previousStartValue + relativeDiff;
684 values[0].setFloatValues(newStartValue, newEndValue);
685 child.setTag(TAG_START_TRANSLATION_Z, newStartValue);
686 child.setTag(TAG_END_TRANSLATION_Z, newEndValue);
687 previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
690 // no new animation needed, let's just apply the value
691 child.setTranslationZ(newEndValue);
695 ObjectAnimator animator = ObjectAnimator.ofFloat(child, View.TRANSLATION_Z,
696 child.getTranslationZ(), newEndValue);
697 animator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
698 long newDuration = cancelAnimatorAndGetNewDuration(duration, previousAnimator);
699 animator.setDuration(newDuration);
700 if (delay > 0 && (previousAnimator == null
701 || previousAnimator.getAnimatedFraction() == 0)) {
702 animator.setStartDelay(delay);
704 animator.addListener(getGlobalAnimationFinishedListener());
705 // remove the tag when the animation is finished
706 animator.addListener(new AnimatorListenerAdapter() {
708 public void onAnimationEnd(Animator animation) {
709 child.setTag(TAG_ANIMATOR_TRANSLATION_Z, null);
710 child.setTag(TAG_START_TRANSLATION_Z, null);
711 child.setTag(TAG_END_TRANSLATION_Z, null);
714 startAnimator(animator);
715 child.setTag(TAG_ANIMATOR_TRANSLATION_Z, animator);
716 child.setTag(TAG_START_TRANSLATION_Z, child.getTranslationZ());
717 child.setTag(TAG_END_TRANSLATION_Z, newEndValue);
720 private void startYTranslationAnimation(final View child,
721 ViewState viewState, long duration, long delay) {
722 Float previousStartValue = getChildTag(child,TAG_START_TRANSLATION_Y);
723 Float previousEndValue = getChildTag(child,TAG_END_TRANSLATION_Y);
724 float newEndValue = viewState.yTranslation;
725 if (previousEndValue != null && previousEndValue == newEndValue) {
728 ObjectAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_TRANSLATION_Y);
729 if (!mAnimationFilter.animateY) {
730 // just a local update was performed
731 if (previousAnimator != null) {
732 // we need to increase all animation keyframes of the previous animator by the
733 // relative change to the end value
734 PropertyValuesHolder[] values = previousAnimator.getValues();
735 float relativeDiff = newEndValue - previousEndValue;
736 float newStartValue = previousStartValue + relativeDiff;
737 values[0].setFloatValues(newStartValue, newEndValue);
738 child.setTag(TAG_START_TRANSLATION_Y, newStartValue);
739 child.setTag(TAG_END_TRANSLATION_Y, newEndValue);
740 previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
743 // no new animation needed, let's just apply the value
744 child.setTranslationY(newEndValue);
749 ObjectAnimator animator = ObjectAnimator.ofFloat(child, View.TRANSLATION_Y,
750 child.getTranslationY(), newEndValue);
751 Interpolator interpolator = mHeadsUpAppearChildren.contains(child) ?
752 mHeadsUpAppearInterpolator :Interpolators.FAST_OUT_SLOW_IN;
753 animator.setInterpolator(interpolator);
754 long newDuration = cancelAnimatorAndGetNewDuration(duration, previousAnimator);
755 animator.setDuration(newDuration);
756 if (delay > 0 && (previousAnimator == null
757 || previousAnimator.getAnimatedFraction() == 0)) {
758 animator.setStartDelay(delay);
760 animator.addListener(getGlobalAnimationFinishedListener());
761 final boolean isHeadsUpDisappear = mHeadsUpDisappearChildren.contains(child);
762 // remove the tag when the animation is finished
763 animator.addListener(new AnimatorListenerAdapter() {
765 public void onAnimationEnd(Animator animation) {
766 HeadsUpManager.setIsClickedNotification(child, false);
767 child.setTag(TAG_ANIMATOR_TRANSLATION_Y, null);
768 child.setTag(TAG_START_TRANSLATION_Y, null);
769 child.setTag(TAG_END_TRANSLATION_Y, null);
770 if (isHeadsUpDisappear) {
771 ((ExpandableNotificationRow) child).setHeadsupDisappearRunning(false);
775 startAnimator(animator);
776 child.setTag(TAG_ANIMATOR_TRANSLATION_Y, animator);
777 child.setTag(TAG_START_TRANSLATION_Y, child.getTranslationY());
778 child.setTag(TAG_END_TRANSLATION_Y, newEndValue);
781 private void startAnimator(ValueAnimator animator) {
782 mAnimatorSet.add(animator);
787 * @return an adapter which ensures that onAnimationFinished is called once no animation is
790 private AnimatorListenerAdapter getGlobalAnimationFinishedListener() {
791 if (!mAnimationListenerPool.empty()) {
792 return mAnimationListenerPool.pop();
795 // We need to create a new one, no reusable ones found
796 return new AnimatorListenerAdapter() {
797 private boolean mWasCancelled;
800 public void onAnimationEnd(Animator animation) {
801 mAnimatorSet.remove(animation);
802 if (mAnimatorSet.isEmpty() && !mWasCancelled) {
803 onAnimationFinished();
805 mAnimationListenerPool.push(this);
809 public void onAnimationCancel(Animator animation) {
810 mWasCancelled = true;
814 public void onAnimationStart(Animator animation) {
815 mWasCancelled = false;
820 public static <T> T getChildTag(View child, int tag) {
821 return (T) child.getTag(tag);
825 * Cancel the previous animator and get the duration of the new animation.
827 * @param duration the new duration
828 * @param previousAnimator the animator which was running before
829 * @return the new duration
831 private long cancelAnimatorAndGetNewDuration(long duration, ValueAnimator previousAnimator) {
832 long newDuration = duration;
833 if (previousAnimator != null) {
834 // We take either the desired length of the new animation or the remaining time of
835 // the previous animator, whichever is longer.
836 newDuration = Math.max(previousAnimator.getDuration()
837 - previousAnimator.getCurrentPlayTime(), newDuration);
838 previousAnimator.cancel();
843 private void onAnimationFinished() {
844 mHostLayout.onChildAnimationFinished();
845 for (View v : mChildrenToClearFromOverlay) {
846 removeFromOverlay(v);
848 mChildrenToClearFromOverlay.clear();
852 * Process the animationEvents for a new animation
854 * @param animationEvents the animation events for the animation to perform
855 * @param finalState the final state to animate to
857 private void processAnimationEvents(
858 ArrayList<NotificationStackScrollLayout.AnimationEvent> animationEvents,
859 StackScrollState finalState) {
860 for (NotificationStackScrollLayout.AnimationEvent event : animationEvents) {
861 final ExpandableView changingView = (ExpandableView) event.changingView;
862 if (event.animationType ==
863 NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_ADD) {
865 // This item is added, initialize it's properties.
866 StackViewState viewState = finalState
867 .getViewStateForView(changingView);
868 if (viewState == null) {
869 // The position for this child was never generated, let's continue.
872 finalState.applyState(changingView, viewState);
873 mNewAddChildren.add(changingView);
875 } else if (event.animationType ==
876 NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_REMOVE) {
877 if (changingView.getVisibility() == View.GONE) {
878 removeFromOverlay(changingView);
882 // Find the amount to translate up. This is needed in order to understand the
883 // direction of the remove animation (either downwards or upwards)
884 StackViewState viewState = finalState
885 .getViewStateForView(event.viewAfterChangingView);
886 int actualHeight = changingView.getActualHeight();
887 // upwards by default
888 float translationDirection = -1.0f;
889 if (viewState != null) {
890 // there was a view after this one, Approximate the distance the next child
892 translationDirection = ((viewState.yTranslation
893 - (changingView.getTranslationY() + actualHeight / 2.0f)) * 2 /
895 translationDirection = Math.max(Math.min(translationDirection, 1.0f),-1.0f);
898 changingView.performRemoveAnimation(ANIMATION_DURATION_APPEAR_DISAPPEAR,
899 translationDirection, new Runnable() {
902 // remove the temporary overlay
903 removeFromOverlay(changingView);
906 } else if (event.animationType ==
907 NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_REMOVE_SWIPED_OUT) {
908 // A race condition can trigger the view to be added to the overlay even though
909 // it was fully swiped out. So let's remove it
910 mHostLayout.getOverlay().remove(changingView);
911 if (Math.abs(changingView.getTranslation()) == changingView.getWidth()
912 && changingView.getTransientContainer() != null) {
913 changingView.getTransientContainer().removeTransientView(changingView);
915 } else if (event.animationType == NotificationStackScrollLayout
916 .AnimationEvent.ANIMATION_TYPE_GROUP_EXPANSION_CHANGED) {
917 ExpandableNotificationRow row = (ExpandableNotificationRow) event.changingView;
918 row.prepareExpansionChanged(finalState);
919 } else if (event.animationType == NotificationStackScrollLayout
920 .AnimationEvent.ANIMATION_TYPE_HEADS_UP_APPEAR) {
921 // This item is added, initialize it's properties.
922 StackViewState viewState = finalState.getViewStateForView(changingView);
923 mTmpState.copyFrom(viewState);
924 if (event.headsUpFromBottom) {
925 mTmpState.yTranslation = mHeadsUpAppearHeightBottom;
927 mTmpState.yTranslation = -mTmpState.height;
929 mHeadsUpAppearChildren.add(changingView);
930 finalState.applyState(changingView, mTmpState);
931 } else if (event.animationType == NotificationStackScrollLayout
932 .AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR ||
933 event.animationType == NotificationStackScrollLayout
934 .AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK) {
935 mHeadsUpDisappearChildren.add(changingView);
936 if (changingView.getParent() == null) {
937 // This notification was actually removed, so we need to add it to the overlay
938 mHostLayout.getOverlay().add(changingView);
939 mTmpState.initFrom(changingView);
940 mTmpState.yTranslation = -changingView.getActualHeight();
941 // We temporarily enable Y animations, the real filter will be combined
943 mAnimationFilter.animateY = true;
944 startViewAnimations(changingView, mTmpState,
945 event.animationType == NotificationStackScrollLayout
946 .AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK
947 ? ANIMATION_DELAY_HEADS_UP
949 ANIMATION_DURATION_HEADS_UP_DISAPPEAR);
950 mChildrenToClearFromOverlay.add(changingView);
953 mNewEvents.add(event);
957 public static void removeFromOverlay(View changingView) {
958 ViewGroup parent = (ViewGroup) changingView.getParent();
959 if (parent != null) {
960 parent.removeView(changingView);
964 public void animateOverScrollToAmount(float targetAmount, final boolean onTop,
965 final boolean isRubberbanded) {
966 final float startOverScrollAmount = mHostLayout.getCurrentOverScrollAmount(onTop);
967 if (targetAmount == startOverScrollAmount) {
970 cancelOverScrollAnimators(onTop);
971 ValueAnimator overScrollAnimator = ValueAnimator.ofFloat(startOverScrollAmount,
973 overScrollAnimator.setDuration(ANIMATION_DURATION_STANDARD);
974 overScrollAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
976 public void onAnimationUpdate(ValueAnimator animation) {
977 float currentOverScroll = (float) animation.getAnimatedValue();
978 mHostLayout.setOverScrollAmount(
979 currentOverScroll, onTop, false /* animate */, false /* cancelAnimators */,
983 overScrollAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
984 overScrollAnimator.addListener(new AnimatorListenerAdapter() {
986 public void onAnimationEnd(Animator animation) {
988 mTopOverScrollAnimator = null;
990 mBottomOverScrollAnimator = null;
994 overScrollAnimator.start();
996 mTopOverScrollAnimator = overScrollAnimator;
998 mBottomOverScrollAnimator = overScrollAnimator;
1002 public void cancelOverScrollAnimators(boolean onTop) {
1003 ValueAnimator currentAnimator = onTop ? mTopOverScrollAnimator : mBottomOverScrollAnimator;
1004 if (currentAnimator != null) {
1005 currentAnimator.cancel();
1010 * Get the end value of the height animation running on a view or the actualHeight
1011 * if no animation is running.
1013 public static int getFinalActualHeight(ExpandableView view) {
1017 ValueAnimator heightAnimator = getChildTag(view, TAG_ANIMATOR_HEIGHT);
1018 if (heightAnimator == null) {
1019 return view.getActualHeight();
1021 return getChildTag(view, TAG_END_HEIGHT);
1026 * Get the end value of the yTranslation animation running on a view or the yTranslation
1027 * if no animation is running.
1029 public static float getFinalTranslationY(View view) {
1033 ValueAnimator yAnimator = getChildTag(view, TAG_ANIMATOR_TRANSLATION_Y);
1034 if (yAnimator == null) {
1035 return view.getTranslationY();
1037 return getChildTag(view, TAG_END_TRANSLATION_Y);
1041 public void setHeadsUpAppearHeightBottom(int headsUpAppearHeightBottom) {
1042 mHeadsUpAppearHeightBottom = headsUpAppearHeightBottom;
1045 public void setShadeExpanded(boolean shadeExpanded) {
1046 mShadeExpanded = shadeExpanded;