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.graphics.Path;
25 import android.view.View;
26 import android.view.animation.AnimationUtils;
27 import android.view.animation.Interpolator;
28 import android.view.animation.PathInterpolator;
30 import com.android.systemui.R;
31 import com.android.systemui.statusbar.ExpandableNotificationRow;
32 import com.android.systemui.statusbar.ExpandableView;
33 import com.android.systemui.statusbar.SpeedBumpView;
35 import java.util.ArrayList;
36 import java.util.HashSet;
37 import java.util.Stack;
40 * An stack state animator which handles animations to new StackScrollStates
42 public class StackStateAnimator {
44 public static final int ANIMATION_DURATION_STANDARD = 360;
45 public static final int ANIMATION_DURATION_GO_TO_FULL_SHADE = 448;
46 public static final int ANIMATION_DURATION_APPEAR_DISAPPEAR = 464;
47 public static final int ANIMATION_DURATION_EXPAND_CLICKED = 360;
48 public static final int ANIMATION_DURATION_DIMMED_ACTIVATED = 220;
49 public static final int ANIMATION_DURATION_HEADS_UP_APPEAR = 650;
50 public static final int ANIMATION_DURATION_HEADS_UP_DISAPPEAR = 230;
51 public static final int ANIMATION_DELAY_PER_ELEMENT_INTERRUPTING = 80;
52 public static final int ANIMATION_DELAY_PER_ELEMENT_EXPAND_CHILDREN = 54;
53 public static final int ANIMATION_DELAY_PER_ELEMENT_MANUAL = 32;
54 public static final int ANIMATION_DELAY_PER_ELEMENT_GO_TO_FULL_SHADE = 48;
55 public static final int ANIMATION_DELAY_PER_ELEMENT_DARK = 24;
56 public static final int DELAY_EFFECT_MAX_INDEX_DIFFERENCE = 2;
57 public static final int DELAY_EFFECT_MAX_INDEX_DIFFERENCE_CHILDREN = 3;
59 private static final int TAG_ANIMATOR_TRANSLATION_Y = R.id.translation_y_animator_tag;
60 private static final int TAG_ANIMATOR_TRANSLATION_Z = R.id.translation_z_animator_tag;
61 private static final int TAG_ANIMATOR_SCALE = R.id.scale_animator_tag;
62 private static final int TAG_ANIMATOR_ALPHA = R.id.alpha_animator_tag;
63 private static final int TAG_ANIMATOR_HEIGHT = R.id.height_animator_tag;
64 private static final int TAG_ANIMATOR_TOP_INSET = R.id.top_inset_animator_tag;
65 private static final int TAG_END_TRANSLATION_Y = R.id.translation_y_animator_end_value_tag;
66 private static final int TAG_END_TRANSLATION_Z = R.id.translation_z_animator_end_value_tag;
67 private static final int TAG_END_SCALE = R.id.scale_animator_end_value_tag;
68 private static final int TAG_END_ALPHA = R.id.alpha_animator_end_value_tag;
69 private static final int TAG_END_HEIGHT = R.id.height_animator_end_value_tag;
70 private static final int TAG_END_TOP_INSET = R.id.top_inset_animator_end_value_tag;
71 private static final int TAG_START_TRANSLATION_Y = R.id.translation_y_animator_start_value_tag;
72 private static final int TAG_START_TRANSLATION_Z = R.id.translation_z_animator_start_value_tag;
73 private static final int TAG_START_SCALE = R.id.scale_animator_start_value_tag;
74 private static final int TAG_START_ALPHA = R.id.alpha_animator_start_value_tag;
75 private static final int TAG_START_HEIGHT = R.id.height_animator_start_value_tag;
76 private static final int TAG_START_TOP_INSET = R.id.top_inset_animator_start_value_tag;
78 private final Interpolator mFastOutSlowInInterpolator;
79 private final Interpolator mHeadsUpAppearInterpolator;
80 private final int mGoToFullShadeAppearingTranslation;
81 public NotificationStackScrollLayout mHostLayout;
82 private ArrayList<NotificationStackScrollLayout.AnimationEvent> mNewEvents =
84 private ArrayList<View> mNewAddChildren = new ArrayList<>();
85 private HashSet<View> mHeadsUpAppearChildren = new HashSet<>();
86 private HashSet<View> mHeadsUpDisappearChildren = new HashSet<>();
87 private HashSet<Animator> mAnimatorSet = new HashSet<>();
88 private Stack<AnimatorListenerAdapter> mAnimationListenerPool = new Stack<>();
89 private AnimationFilter mAnimationFilter = new AnimationFilter();
90 private long mCurrentLength;
91 private long mCurrentAdditionalDelay;
93 /** The current index for the last child which was not added in this event set. */
94 private int mCurrentLastNotAddedIndex;
95 private ValueAnimator mTopOverScrollAnimator;
96 private ValueAnimator mBottomOverScrollAnimator;
97 private ExpandableNotificationRow mChildExpandingView;
98 private StackViewState mTmpState = new StackViewState();
99 private int mHeadsUpAppearHeightBottom;
100 private boolean mShadeExpanded;
102 public StackStateAnimator(NotificationStackScrollLayout hostLayout) {
103 mHostLayout = hostLayout;
104 mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(hostLayout.getContext(),
105 android.R.interpolator.fast_out_slow_in);
106 mGoToFullShadeAppearingTranslation =
107 hostLayout.getContext().getResources().getDimensionPixelSize(
108 R.dimen.go_to_full_shade_appearing_translation);
109 Path path = new Path();
117 float xTot = (x1 + x2 + x3);
118 path.cubicTo(x1 * 0.9f / xTot, 0f,
119 x1 * 0.8f / xTot, y1 / y3,
120 x1 / xTot , y1 / y3);
121 path.cubicTo((x1 + x2 * 0.4f) / xTot, y1 / y3,
122 (x1 + x2 * 0.2f) / xTot, y2 / y3,
123 (x1 + x2) / xTot, y2 / y3);
124 path.cubicTo((x1 + x2 + x3 * 0.4f) / xTot, y2 / y3,
125 (x1 + x2 + x3 * 0.2f) / xTot, 1f,
127 mHeadsUpAppearInterpolator = new PathInterpolator(path);
130 public boolean isRunning() {
131 return !mAnimatorSet.isEmpty();
134 public void startAnimationForEvents(
135 ArrayList<NotificationStackScrollLayout.AnimationEvent> mAnimationEvents,
136 StackScrollState finalState, long additionalDelay) {
138 processAnimationEvents(mAnimationEvents, finalState);
140 int childCount = mHostLayout.getChildCount();
141 mAnimationFilter.applyCombination(mNewEvents);
142 mCurrentAdditionalDelay = additionalDelay;
143 mCurrentLength = NotificationStackScrollLayout.AnimationEvent.combineLength(mNewEvents);
144 mCurrentLastNotAddedIndex = findLastNotAddedIndex(finalState);
145 for (int i = 0; i < childCount; i++) {
146 final ExpandableView child = (ExpandableView) mHostLayout.getChildAt(i);
148 StackViewState viewState = finalState.getViewStateForView(child);
149 if (viewState == null || child.getVisibility() == View.GONE
150 || applyWithoutAnimation(child, viewState, finalState)) {
154 child.setClipTopOptimization(0);
155 startStackAnimations(child, viewState, finalState, i, -1 /* fixedDelay */);
158 // no child has preformed any animation, lets finish
159 onAnimationFinished();
161 mHeadsUpAppearChildren.clear();
162 mHeadsUpDisappearChildren.clear();
164 mNewAddChildren.clear();
165 mChildExpandingView = null;
169 * Determines if a view should not perform an animation and applies it directly.
171 * @return true if no animation should be performed
173 private boolean applyWithoutAnimation(ExpandableView child, StackViewState viewState,
174 StackScrollState finalState) {
175 if (mShadeExpanded) {
178 if (getChildTag(child, TAG_ANIMATOR_TRANSLATION_Y) != null) {
179 // A Y translation animation is running
182 if (mHeadsUpDisappearChildren.contains(child) || mHeadsUpAppearChildren.contains(child)) {
183 // This is a heads up animation
186 if (mHostLayout.isPinnedHeadsUp(child)) {
187 // This is another headsUp which might move. Let's animate!
190 finalState.applyState(child, viewState);
194 private int findLastNotAddedIndex(StackScrollState finalState) {
195 int childCount = mHostLayout.getChildCount();
196 for (int i = childCount - 1; i >= 0; i--) {
197 final ExpandableView child = (ExpandableView) mHostLayout.getChildAt(i);
199 StackViewState viewState = finalState.getViewStateForView(child);
200 if (viewState == null || child.getVisibility() == View.GONE) {
203 if (!mNewAddChildren.contains(child)) {
204 return viewState.notGoneIndex;
212 * Start an animation to the given {@link StackViewState}.
214 * @param child the child to start the animation on
215 * @param viewState the {@link StackViewState} of the view to animate to
216 * @param finalState the final state after the animation
217 * @param i the index of the view; only relevant if the view is the speed bump and is
219 * @param fixedDelay a fixed delay if desired or -1 if the delay should be calculated
221 public void startStackAnimations(final ExpandableView child, StackViewState viewState,
222 StackScrollState finalState, int i, long fixedDelay) {
223 final float alpha = viewState.alpha;
224 boolean wasAdded = mNewAddChildren.contains(child);
225 long duration = mCurrentLength;
226 if (wasAdded && mAnimationFilter.hasGoToFullShadeEvent) {
227 child.setTranslationY(child.getTranslationY() + mGoToFullShadeAppearingTranslation);
228 float longerDurationFactor = viewState.notGoneIndex - mCurrentLastNotAddedIndex;
229 longerDurationFactor = (float) Math.pow(longerDurationFactor, 0.7f);
230 duration = ANIMATION_DURATION_APPEAR_DISAPPEAR + 50 +
231 (long) (100 * longerDurationFactor);
233 boolean yTranslationChanging = child.getTranslationY() != viewState.yTranslation;
234 boolean zTranslationChanging = child.getTranslationZ() != viewState.zTranslation;
235 boolean scaleChanging = child.getScaleX() != viewState.scale;
236 boolean alphaChanging = alpha != child.getAlpha();
237 boolean heightChanging = viewState.height != child.getActualHeight();
238 boolean darkChanging = viewState.dark != child.isDark();
239 boolean topInsetChanging = viewState.clipTopAmount != child.getClipTopAmount();
240 boolean hasDelays = mAnimationFilter.hasDelays;
241 boolean isDelayRelevant = yTranslationChanging || zTranslationChanging || scaleChanging ||
242 alphaChanging || heightChanging || topInsetChanging || darkChanging;
244 if (fixedDelay != -1) {
246 } else if (hasDelays && isDelayRelevant || wasAdded) {
247 delay = mCurrentAdditionalDelay + calculateChildAnimationDelay(viewState, finalState);
250 startViewAnimations(child, viewState, delay, duration);
252 // start height animation
253 if (heightChanging && child.getActualHeight() != 0) {
254 startHeightAnimation(child, viewState, duration, delay);
257 // start top inset animation
258 if (topInsetChanging) {
259 startInsetAnimation(child, viewState, duration, delay);
262 // start dimmed animation
263 child.setDimmed(viewState.dimmed, mAnimationFilter.animateDimmed);
265 // start dark animation
266 child.setDark(viewState.dark, mAnimationFilter.animateDark, delay);
268 // apply speed bump state
269 child.setBelowSpeedBump(viewState.belowSpeedBump);
271 // start hiding sensitive animation
272 child.setHideSensitive(viewState.hideSensitive, mAnimationFilter.animateHideSensitive,
276 child.performAddAnimation(delay, mCurrentLength);
278 if (child instanceof SpeedBumpView) {
279 finalState.performSpeedBumpAnimation(i, (SpeedBumpView) child, viewState,
281 } else if (child instanceof ExpandableNotificationRow) {
282 ExpandableNotificationRow row = (ExpandableNotificationRow) child;
283 row.startChildAnimation(finalState, this, child == mChildExpandingView, delay,
289 * Start an animation to a new {@link ViewState}.
291 * @param child the child to start the animation on
292 * @param viewState the {@link StackViewState} of the view to animate to
293 * @param delay a fixed delay
294 * @param duration the duration of the animation
296 public void startViewAnimations(View child, ViewState viewState, long delay, long duration) {
297 boolean wasVisible = child.getVisibility() == View.VISIBLE;
298 final float alpha = viewState.alpha;
299 if (!wasVisible && alpha != 0 && !viewState.gone) {
300 child.setVisibility(View.VISIBLE);
302 boolean yTranslationChanging = child.getTranslationY() != viewState.yTranslation;
303 boolean zTranslationChanging = child.getTranslationZ() != viewState.zTranslation;
304 boolean scaleChanging = child.getScaleX() != viewState.scale;
305 float childAlpha = child.getVisibility() == View.INVISIBLE ? 0.0f : child.getAlpha();
306 boolean alphaChanging = viewState.alpha != childAlpha;
308 // start translationY animation
309 if (yTranslationChanging) {
310 startYTranslationAnimation(child, viewState, duration, delay);
313 // start translationZ animation
314 if (zTranslationChanging) {
315 startZTranslationAnimation(child, viewState, duration, delay);
318 // start scale animation
320 startScaleAnimation(child, viewState, duration);
323 // start alpha animation
324 if (alphaChanging && child.getTranslationX() == 0) {
325 startAlphaAnimation(child, viewState, duration, delay);
329 private long calculateChildAnimationDelay(StackViewState viewState,
330 StackScrollState finalState) {
331 if (mAnimationFilter.hasDarkEvent) {
332 return calculateDelayDark(viewState);
334 if (mAnimationFilter.hasGoToFullShadeEvent) {
335 return calculateDelayGoToFullShade(viewState);
338 for (NotificationStackScrollLayout.AnimationEvent event : mNewEvents) {
339 long delayPerElement = ANIMATION_DELAY_PER_ELEMENT_INTERRUPTING;
340 switch (event.animationType) {
341 case NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_ADD: {
342 int ownIndex = viewState.notGoneIndex;
343 int changingIndex = finalState
344 .getViewStateForView(event.changingView).notGoneIndex;
345 int difference = Math.abs(ownIndex - changingIndex);
346 difference = Math.max(0, Math.min(DELAY_EFFECT_MAX_INDEX_DIFFERENCE,
348 long delay = (DELAY_EFFECT_MAX_INDEX_DIFFERENCE - difference) * delayPerElement;
349 minDelay = Math.max(delay, minDelay);
352 case NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_REMOVE_SWIPED_OUT:
353 delayPerElement = ANIMATION_DELAY_PER_ELEMENT_MANUAL;
354 case NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_REMOVE: {
355 int ownIndex = viewState.notGoneIndex;
356 boolean noNextView = event.viewAfterChangingView == null;
357 View viewAfterChangingView = noNextView
358 ? mHostLayout.getLastChildNotGone()
359 : event.viewAfterChangingView;
361 int nextIndex = finalState
362 .getViewStateForView(viewAfterChangingView).notGoneIndex;
363 if (ownIndex >= nextIndex) {
364 // we only have the view afterwards
367 int difference = Math.abs(ownIndex - nextIndex);
368 difference = Math.max(0, Math.min(DELAY_EFFECT_MAX_INDEX_DIFFERENCE,
370 long delay = difference * delayPerElement;
371 minDelay = Math.max(delay, minDelay);
381 private long calculateDelayDark(StackViewState viewState) {
383 if (mAnimationFilter.darkAnimationOriginIndex ==
384 NotificationStackScrollLayout.AnimationEvent.DARK_ANIMATION_ORIGIN_INDEX_ABOVE) {
386 } else if (mAnimationFilter.darkAnimationOriginIndex ==
387 NotificationStackScrollLayout.AnimationEvent.DARK_ANIMATION_ORIGIN_INDEX_BELOW) {
388 referenceIndex = mHostLayout.getNotGoneChildCount() - 1;
390 referenceIndex = mAnimationFilter.darkAnimationOriginIndex;
392 return Math.abs(referenceIndex - viewState.notGoneIndex) * ANIMATION_DELAY_PER_ELEMENT_DARK;
395 private long calculateDelayGoToFullShade(StackViewState viewState) {
396 float index = viewState.notGoneIndex;
397 index = (float) Math.pow(index, 0.7f);
398 return (long) (index * ANIMATION_DELAY_PER_ELEMENT_GO_TO_FULL_SHADE);
401 private void startHeightAnimation(final ExpandableView child,
402 StackViewState viewState, long duration, long delay) {
403 Integer previousStartValue = getChildTag(child, TAG_START_HEIGHT);
404 Integer previousEndValue = getChildTag(child, TAG_END_HEIGHT);
405 int newEndValue = viewState.height;
406 if (previousEndValue != null && previousEndValue == newEndValue) {
409 ValueAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_HEIGHT);
410 if (!mAnimationFilter.animateHeight) {
411 // just a local update was performed
412 if (previousAnimator != null) {
413 // we need to increase all animation keyframes of the previous animator by the
414 // relative change to the end value
415 PropertyValuesHolder[] values = previousAnimator.getValues();
416 int relativeDiff = newEndValue - previousEndValue;
417 int newStartValue = previousStartValue + relativeDiff;
418 values[0].setIntValues(newStartValue, newEndValue);
419 child.setTag(TAG_START_HEIGHT, newStartValue);
420 child.setTag(TAG_END_HEIGHT, newEndValue);
421 previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
424 // no new animation needed, let's just apply the value
425 child.setActualHeight(newEndValue, false);
430 ValueAnimator animator = ValueAnimator.ofInt(child.getActualHeight(), newEndValue);
431 animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
433 public void onAnimationUpdate(ValueAnimator animation) {
434 child.setActualHeight((int) animation.getAnimatedValue(),
435 false /* notifyListeners */);
438 animator.setInterpolator(mFastOutSlowInInterpolator);
439 long newDuration = cancelAnimatorAndGetNewDuration(duration, previousAnimator);
440 animator.setDuration(newDuration);
441 if (delay > 0 && (previousAnimator == null || !previousAnimator.isRunning())) {
442 animator.setStartDelay(delay);
444 animator.addListener(getGlobalAnimationFinishedListener());
445 // remove the tag when the animation is finished
446 animator.addListener(new AnimatorListenerAdapter() {
448 public void onAnimationEnd(Animator animation) {
449 child.setTag(TAG_ANIMATOR_HEIGHT, null);
450 child.setTag(TAG_START_HEIGHT, null);
451 child.setTag(TAG_END_HEIGHT, null);
454 startAnimator(animator);
455 child.setTag(TAG_ANIMATOR_HEIGHT, animator);
456 child.setTag(TAG_START_HEIGHT, child.getActualHeight());
457 child.setTag(TAG_END_HEIGHT, newEndValue);
460 private void startInsetAnimation(final ExpandableView child,
461 StackViewState viewState, long duration, long delay) {
462 Integer previousStartValue = getChildTag(child, TAG_START_TOP_INSET);
463 Integer previousEndValue = getChildTag(child, TAG_END_TOP_INSET);
464 int newEndValue = viewState.clipTopAmount;
465 if (previousEndValue != null && previousEndValue == newEndValue) {
468 ValueAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_TOP_INSET);
469 if (!mAnimationFilter.animateTopInset) {
470 // just a local update was performed
471 if (previousAnimator != null) {
472 // we need to increase all animation keyframes of the previous animator by the
473 // relative change to the end value
474 PropertyValuesHolder[] values = previousAnimator.getValues();
475 int relativeDiff = newEndValue - previousEndValue;
476 int newStartValue = previousStartValue + relativeDiff;
477 values[0].setIntValues(newStartValue, newEndValue);
478 child.setTag(TAG_START_TOP_INSET, newStartValue);
479 child.setTag(TAG_END_TOP_INSET, newEndValue);
480 previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
483 // no new animation needed, let's just apply the value
484 child.setClipTopAmount(newEndValue);
489 ValueAnimator animator = ValueAnimator.ofInt(child.getClipTopAmount(), newEndValue);
490 animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
492 public void onAnimationUpdate(ValueAnimator animation) {
493 child.setClipTopAmount((int) animation.getAnimatedValue());
496 animator.setInterpolator(mFastOutSlowInInterpolator);
497 long newDuration = cancelAnimatorAndGetNewDuration(duration, previousAnimator);
498 animator.setDuration(newDuration);
499 if (delay > 0 && (previousAnimator == null || !previousAnimator.isRunning())) {
500 animator.setStartDelay(delay);
502 animator.addListener(getGlobalAnimationFinishedListener());
503 // remove the tag when the animation is finished
504 animator.addListener(new AnimatorListenerAdapter() {
506 public void onAnimationEnd(Animator animation) {
507 child.setTag(TAG_ANIMATOR_TOP_INSET, null);
508 child.setTag(TAG_START_TOP_INSET, null);
509 child.setTag(TAG_END_TOP_INSET, null);
512 startAnimator(animator);
513 child.setTag(TAG_ANIMATOR_TOP_INSET, animator);
514 child.setTag(TAG_START_TOP_INSET, child.getClipTopAmount());
515 child.setTag(TAG_END_TOP_INSET, newEndValue);
518 private void startAlphaAnimation(final View child,
519 final ViewState viewState, long duration, long delay) {
520 Float previousStartValue = getChildTag(child,TAG_START_ALPHA);
521 Float previousEndValue = getChildTag(child,TAG_END_ALPHA);
522 final float newEndValue = viewState.alpha;
523 if (previousEndValue != null && previousEndValue == newEndValue) {
526 ObjectAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_ALPHA);
527 if (!mAnimationFilter.animateAlpha) {
528 // just a local update was performed
529 if (previousAnimator != null) {
530 // we need to increase all animation keyframes of the previous animator by the
531 // relative change to the end value
532 PropertyValuesHolder[] values = previousAnimator.getValues();
533 float relativeDiff = newEndValue - previousEndValue;
534 float newStartValue = previousStartValue + relativeDiff;
535 values[0].setFloatValues(newStartValue, newEndValue);
536 child.setTag(TAG_START_ALPHA, newStartValue);
537 child.setTag(TAG_END_ALPHA, newEndValue);
538 previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
541 // no new animation needed, let's just apply the value
542 child.setAlpha(newEndValue);
543 if (newEndValue == 0) {
544 child.setVisibility(View.INVISIBLE);
549 ObjectAnimator animator = ObjectAnimator.ofFloat(child, View.ALPHA,
550 child.getAlpha(), newEndValue);
551 animator.setInterpolator(mFastOutSlowInInterpolator);
553 child.setLayerType(View.LAYER_TYPE_HARDWARE, null);
554 animator.addListener(new AnimatorListenerAdapter() {
555 public boolean mWasCancelled;
558 public void onAnimationEnd(Animator animation) {
559 child.setLayerType(View.LAYER_TYPE_NONE, null);
560 if (newEndValue == 0 && !mWasCancelled) {
561 child.setVisibility(View.INVISIBLE);
563 // remove the tag when the animation is finished
564 child.setTag(TAG_ANIMATOR_ALPHA, null);
565 child.setTag(TAG_START_ALPHA, null);
566 child.setTag(TAG_END_ALPHA, null);
570 public void onAnimationCancel(Animator animation) {
571 mWasCancelled = true;
575 public void onAnimationStart(Animator animation) {
576 mWasCancelled = false;
579 long newDuration = cancelAnimatorAndGetNewDuration(duration, previousAnimator);
580 animator.setDuration(newDuration);
581 if (delay > 0 && (previousAnimator == null || !previousAnimator.isRunning())) {
582 animator.setStartDelay(delay);
584 animator.addListener(getGlobalAnimationFinishedListener());
586 startAnimator(animator);
587 child.setTag(TAG_ANIMATOR_ALPHA, animator);
588 child.setTag(TAG_START_ALPHA, child.getAlpha());
589 child.setTag(TAG_END_ALPHA, newEndValue);
592 private void startZTranslationAnimation(final View child,
593 final ViewState viewState, long duration, long delay) {
594 Float previousStartValue = getChildTag(child,TAG_START_TRANSLATION_Z);
595 Float previousEndValue = getChildTag(child,TAG_END_TRANSLATION_Z);
596 float newEndValue = viewState.zTranslation;
597 if (previousEndValue != null && previousEndValue == newEndValue) {
600 ObjectAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_TRANSLATION_Z);
601 if (!mAnimationFilter.animateZ) {
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_TRANSLATION_Z, newStartValue);
611 child.setTag(TAG_END_TRANSLATION_Z, newEndValue);
612 previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
615 // no new animation needed, let's just apply the value
616 child.setTranslationZ(newEndValue);
620 ObjectAnimator animator = ObjectAnimator.ofFloat(child, View.TRANSLATION_Z,
621 child.getTranslationZ(), newEndValue);
622 animator.setInterpolator(mFastOutSlowInInterpolator);
623 long newDuration = cancelAnimatorAndGetNewDuration(duration, previousAnimator);
624 animator.setDuration(newDuration);
625 if (delay > 0 && (previousAnimator == null || !previousAnimator.isRunning())) {
626 animator.setStartDelay(delay);
628 animator.addListener(getGlobalAnimationFinishedListener());
629 // remove the tag when the animation is finished
630 animator.addListener(new AnimatorListenerAdapter() {
632 public void onAnimationEnd(Animator animation) {
633 child.setTag(TAG_ANIMATOR_TRANSLATION_Z, null);
634 child.setTag(TAG_START_TRANSLATION_Z, null);
635 child.setTag(TAG_END_TRANSLATION_Z, null);
638 startAnimator(animator);
639 child.setTag(TAG_ANIMATOR_TRANSLATION_Z, animator);
640 child.setTag(TAG_START_TRANSLATION_Z, child.getTranslationZ());
641 child.setTag(TAG_END_TRANSLATION_Z, newEndValue);
644 private void startYTranslationAnimation(final View child,
645 ViewState viewState, long duration, long delay) {
646 Float previousStartValue = getChildTag(child,TAG_START_TRANSLATION_Y);
647 Float previousEndValue = getChildTag(child,TAG_END_TRANSLATION_Y);
648 float newEndValue = viewState.yTranslation;
649 if (previousEndValue != null && previousEndValue == newEndValue) {
652 ObjectAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_TRANSLATION_Y);
653 if (!mAnimationFilter.animateY) {
654 // just a local update was performed
655 if (previousAnimator != null) {
656 // we need to increase all animation keyframes of the previous animator by the
657 // relative change to the end value
658 PropertyValuesHolder[] values = previousAnimator.getValues();
659 float relativeDiff = newEndValue - previousEndValue;
660 float newStartValue = previousStartValue + relativeDiff;
661 values[0].setFloatValues(newStartValue, newEndValue);
662 child.setTag(TAG_START_TRANSLATION_Y, newStartValue);
663 child.setTag(TAG_END_TRANSLATION_Y, newEndValue);
664 previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
667 // no new animation needed, let's just apply the value
668 child.setTranslationY(newEndValue);
673 ObjectAnimator animator = ObjectAnimator.ofFloat(child, View.TRANSLATION_Y,
674 child.getTranslationY(), newEndValue);
675 Interpolator interpolator = mHeadsUpAppearChildren.contains(child) ?
676 mHeadsUpAppearInterpolator :mFastOutSlowInInterpolator;
677 animator.setInterpolator(interpolator);
678 long newDuration = cancelAnimatorAndGetNewDuration(duration, previousAnimator);
679 animator.setDuration(newDuration);
680 if (delay > 0 && (previousAnimator == null || !previousAnimator.isRunning())) {
681 animator.setStartDelay(delay);
683 animator.addListener(getGlobalAnimationFinishedListener());
684 // remove the tag when the animation is finished
685 animator.addListener(new AnimatorListenerAdapter() {
687 public void onAnimationEnd(Animator animation) {
688 child.setTag(TAG_ANIMATOR_TRANSLATION_Y, null);
689 child.setTag(TAG_START_TRANSLATION_Y, null);
690 child.setTag(TAG_END_TRANSLATION_Y, null);
693 startAnimator(animator);
694 child.setTag(TAG_ANIMATOR_TRANSLATION_Y, animator);
695 child.setTag(TAG_START_TRANSLATION_Y, child.getTranslationY());
696 child.setTag(TAG_END_TRANSLATION_Y, newEndValue);
699 private void startScaleAnimation(final View child,
700 ViewState viewState, long duration) {
701 Float previousStartValue = getChildTag(child, TAG_START_SCALE);
702 Float previousEndValue = getChildTag(child, TAG_END_SCALE);
703 float newEndValue = viewState.scale;
704 if (previousEndValue != null && previousEndValue == newEndValue) {
707 ObjectAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_SCALE);
708 if (!mAnimationFilter.animateScale) {
709 // just a local update was performed
710 if (previousAnimator != null) {
711 // we need to increase all animation keyframes of the previous animator by the
712 // relative change to the end value
713 PropertyValuesHolder[] values = previousAnimator.getValues();
714 float relativeDiff = newEndValue - previousEndValue;
715 float newStartValue = previousStartValue + relativeDiff;
716 values[0].setFloatValues(newStartValue, newEndValue);
717 values[1].setFloatValues(newStartValue, newEndValue);
718 child.setTag(TAG_START_SCALE, newStartValue);
719 child.setTag(TAG_END_SCALE, newEndValue);
720 previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
723 // no new animation needed, let's just apply the value
724 child.setScaleX(newEndValue);
725 child.setScaleY(newEndValue);
729 PropertyValuesHolder holderX =
730 PropertyValuesHolder.ofFloat(View.SCALE_X, child.getScaleX(), newEndValue);
731 PropertyValuesHolder holderY =
732 PropertyValuesHolder.ofFloat(View.SCALE_Y, child.getScaleY(), newEndValue);
733 ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(child, holderX, holderY);
734 animator.setInterpolator(mFastOutSlowInInterpolator);
735 long newDuration = cancelAnimatorAndGetNewDuration(duration, previousAnimator);
736 animator.setDuration(newDuration);
737 animator.addListener(getGlobalAnimationFinishedListener());
738 // remove the tag when the animation is finished
739 animator.addListener(new AnimatorListenerAdapter() {
741 public void onAnimationEnd(Animator animation) {
742 child.setTag(TAG_ANIMATOR_SCALE, null);
743 child.setTag(TAG_START_SCALE, null);
744 child.setTag(TAG_END_SCALE, null);
747 startAnimator(animator);
748 child.setTag(TAG_ANIMATOR_SCALE, animator);
749 child.setTag(TAG_START_SCALE, child.getScaleX());
750 child.setTag(TAG_END_SCALE, newEndValue);
753 private void startAnimator(ValueAnimator animator) {
754 mAnimatorSet.add(animator);
759 * @return an adapter which ensures that onAnimationFinished is called once no animation is
762 private AnimatorListenerAdapter getGlobalAnimationFinishedListener() {
763 if (!mAnimationListenerPool.empty()) {
764 return mAnimationListenerPool.pop();
767 // We need to create a new one, no reusable ones found
768 return new AnimatorListenerAdapter() {
769 private boolean mWasCancelled;
772 public void onAnimationEnd(Animator animation) {
773 mAnimatorSet.remove(animation);
774 if (mAnimatorSet.isEmpty() && !mWasCancelled) {
775 onAnimationFinished();
777 mAnimationListenerPool.push(this);
781 public void onAnimationCancel(Animator animation) {
782 mWasCancelled = true;
786 public void onAnimationStart(Animator animation) {
787 mWasCancelled = false;
792 private static <T> T getChildTag(View child, int tag) {
793 return (T) child.getTag(tag);
797 * Cancel the previous animator and get the duration of the new animation.
799 * @param duration the new duration
800 * @param previousAnimator the animator which was running before
801 * @return the new duration
803 private long cancelAnimatorAndGetNewDuration(long duration, ValueAnimator previousAnimator) {
804 long newDuration = duration;
805 if (previousAnimator != null) {
806 // We take either the desired length of the new animation or the remaining time of
807 // the previous animator, whichever is longer.
808 newDuration = Math.max(previousAnimator.getDuration()
809 - previousAnimator.getCurrentPlayTime(), newDuration);
810 previousAnimator.cancel();
815 private void onAnimationFinished() {
816 mHostLayout.onChildAnimationFinished();
820 * Process the animationEvents for a new animation
822 * @param animationEvents the animation events for the animation to perform
823 * @param finalState the final state to animate to
825 private void processAnimationEvents(
826 ArrayList<NotificationStackScrollLayout.AnimationEvent> animationEvents,
827 StackScrollState finalState) {
828 for (NotificationStackScrollLayout.AnimationEvent event : animationEvents) {
829 final ExpandableView changingView = (ExpandableView) event.changingView;
830 if (event.animationType ==
831 NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_ADD) {
833 // This item is added, initialize it's properties.
834 StackViewState viewState = finalState
835 .getViewStateForView(changingView);
836 if (viewState == null) {
837 // The position for this child was never generated, let's continue.
840 if (changingView.getVisibility() == View.GONE) {
841 // The view was set to gone but the state never removed
842 finalState.removeViewStateForView(changingView);
845 finalState.applyState(changingView, viewState);
846 mNewAddChildren.add(changingView);
848 } else if (event.animationType ==
849 NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_REMOVE) {
850 if (changingView.getVisibility() == View.GONE) {
851 mHostLayout.getOverlay().remove(changingView);
855 // Find the amount to translate up. This is needed in order to understand the
856 // direction of the remove animation (either downwards or upwards)
857 StackViewState viewState = finalState
858 .getViewStateForView(event.viewAfterChangingView);
859 int actualHeight = changingView.getActualHeight();
860 // upwards by default
861 float translationDirection = -1.0f;
862 if (viewState != null) {
863 // there was a view after this one, Approximate the distance the next child
865 translationDirection = ((viewState.yTranslation
866 - (changingView.getTranslationY() + actualHeight / 2.0f)) * 2 /
868 translationDirection = Math.max(Math.min(translationDirection, 1.0f),-1.0f);
871 changingView.performRemoveAnimation(ANIMATION_DURATION_APPEAR_DISAPPEAR,
872 translationDirection, new Runnable() {
875 // remove the temporary overlay
876 mHostLayout.getOverlay().remove(changingView);
879 } else if (event.animationType ==
880 NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_REMOVE_SWIPED_OUT) {
881 // A race condition can trigger the view to be added to the overlay even though
882 // it is swiped out. So let's remove it
883 mHostLayout.getOverlay().remove(changingView);
884 } else if (event.animationType == NotificationStackScrollLayout
885 .AnimationEvent.ANIMATION_TYPE_GROUP_EXPANSION_CHANGED) {
886 ExpandableNotificationRow row = (ExpandableNotificationRow) event.changingView;
887 row.prepareExpansionChanged(finalState);
888 mChildExpandingView = row;
889 } else if (event.animationType == NotificationStackScrollLayout
890 .AnimationEvent.ANIMATION_TYPE_HEADS_UP_APPEAR) {
891 // This item is added, initialize it's properties.
892 StackViewState viewState = finalState.getViewStateForView(changingView);
893 mTmpState.copyFrom(viewState);
894 if (event.headsUpFromBottom) {
895 mTmpState.yTranslation = mHeadsUpAppearHeightBottom;
897 mTmpState.yTranslation = -mTmpState.height;
899 mHeadsUpAppearChildren.add(changingView);
900 finalState.applyState(changingView, mTmpState);
901 } else if (event.animationType == NotificationStackScrollLayout
902 .AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR) {
903 // This item is added, initialize it's properties.
904 mHeadsUpDisappearChildren.add(changingView);
906 mNewEvents.add(event);
910 public void animateOverScrollToAmount(float targetAmount, final boolean onTop,
911 final boolean isRubberbanded) {
912 final float startOverScrollAmount = mHostLayout.getCurrentOverScrollAmount(onTop);
913 if (targetAmount == startOverScrollAmount) {
916 cancelOverScrollAnimators(onTop);
917 ValueAnimator overScrollAnimator = ValueAnimator.ofFloat(startOverScrollAmount,
919 overScrollAnimator.setDuration(ANIMATION_DURATION_STANDARD);
920 overScrollAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
922 public void onAnimationUpdate(ValueAnimator animation) {
923 float currentOverScroll = (float) animation.getAnimatedValue();
924 mHostLayout.setOverScrollAmount(
925 currentOverScroll, onTop, false /* animate */, false /* cancelAnimators */,
929 overScrollAnimator.setInterpolator(mFastOutSlowInInterpolator);
930 overScrollAnimator.addListener(new AnimatorListenerAdapter() {
932 public void onAnimationEnd(Animator animation) {
934 mTopOverScrollAnimator = null;
936 mBottomOverScrollAnimator = null;
940 overScrollAnimator.start();
942 mTopOverScrollAnimator = overScrollAnimator;
944 mBottomOverScrollAnimator = overScrollAnimator;
948 public void cancelOverScrollAnimators(boolean onTop) {
949 ValueAnimator currentAnimator = onTop ? mTopOverScrollAnimator : mBottomOverScrollAnimator;
950 if (currentAnimator != null) {
951 currentAnimator.cancel();
956 * Get the end value of the height animation running on a view or the actualHeight
957 * if no animation is running.
959 public static int getFinalActualHeight(ExpandableView view) {
963 ValueAnimator heightAnimator = getChildTag(view, TAG_ANIMATOR_HEIGHT);
964 if (heightAnimator == null) {
965 return view.getActualHeight();
967 return getChildTag(view, TAG_END_HEIGHT);
971 public void setHeadsUpAppearHeightBottom(int headsUpAppearHeightBottom) {
972 mHeadsUpAppearHeightBottom = headsUpAppearHeightBottom;
975 public void setShadeExpanded(boolean shadeExpanded) {
976 mShadeExpanded = shadeExpanded;