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.animation.AnimationUtils;
26 import android.view.animation.Interpolator;
28 import com.android.systemui.R;
29 import com.android.systemui.statusbar.ExpandableNotificationRow;
30 import com.android.systemui.statusbar.ExpandableView;
31 import com.android.systemui.statusbar.SpeedBumpView;
33 import java.util.ArrayList;
34 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_EXPAND_CLICKED = 360;
47 public static final int ANIMATION_DURATION_DIMMED_ACTIVATED = 220;
48 public static final int ANIMATION_DELAY_PER_ELEMENT_INTERRUPTING = 80;
49 public static final int ANIMATION_DELAY_PER_ELEMENT_EXPAND_CHILDREN = 54;
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 DELAY_EFFECT_MAX_INDEX_DIFFERENCE_CHILDREN = 3;
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_SCALE = R.id.scale_animator_tag;
59 private static final int TAG_ANIMATOR_ALPHA = R.id.alpha_animator_tag;
60 private static final int TAG_ANIMATOR_HEIGHT = R.id.height_animator_tag;
61 private static final int TAG_ANIMATOR_TOP_INSET = R.id.top_inset_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_SCALE = R.id.scale_animator_end_value_tag;
65 private static final int TAG_END_ALPHA = R.id.alpha_animator_end_value_tag;
66 private static final int TAG_END_HEIGHT = R.id.height_animator_end_value_tag;
67 private static final int TAG_END_TOP_INSET = R.id.top_inset_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_SCALE = R.id.scale_animator_start_value_tag;
71 private static final int TAG_START_ALPHA = R.id.alpha_animator_start_value_tag;
72 private static final int TAG_START_HEIGHT = R.id.height_animator_start_value_tag;
73 private static final int TAG_START_TOP_INSET = R.id.top_inset_animator_start_value_tag;
75 private final Interpolator mFastOutSlowInInterpolator;
76 private final int mGoToFullShadeAppearingTranslation;
77 public NotificationStackScrollLayout mHostLayout;
78 private ArrayList<NotificationStackScrollLayout.AnimationEvent> mNewEvents =
80 private ArrayList<View> mNewAddChildren = new ArrayList<>();
81 private Set<Animator> mAnimatorSet = new HashSet<>();
82 private Stack<AnimatorListenerAdapter> mAnimationListenerPool = new Stack<>();
83 private AnimationFilter mAnimationFilter = new AnimationFilter();
84 private long mCurrentLength;
85 private long mCurrentAdditionalDelay;
87 /** The current index for the last child which was not added in this event set. */
88 private int mCurrentLastNotAddedIndex;
90 private ValueAnimator mTopOverScrollAnimator;
91 private ValueAnimator mBottomOverScrollAnimator;
92 private ExpandableNotificationRow mChildExpandingView;
94 public StackStateAnimator(NotificationStackScrollLayout hostLayout) {
95 mHostLayout = hostLayout;
96 mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(hostLayout.getContext(),
97 android.R.interpolator.fast_out_slow_in);
98 mGoToFullShadeAppearingTranslation =
99 hostLayout.getContext().getResources().getDimensionPixelSize(
100 R.dimen.go_to_full_shade_appearing_translation);
103 public boolean isRunning() {
104 return !mAnimatorSet.isEmpty();
107 public void startAnimationForEvents(
108 ArrayList<NotificationStackScrollLayout.AnimationEvent> mAnimationEvents,
109 StackScrollState finalState, long additionalDelay) {
111 processAnimationEvents(mAnimationEvents, finalState);
113 int childCount = mHostLayout.getChildCount();
114 mAnimationFilter.applyCombination(mNewEvents);
115 mCurrentAdditionalDelay = additionalDelay;
116 mCurrentLength = NotificationStackScrollLayout.AnimationEvent.combineLength(mNewEvents);
117 mCurrentLastNotAddedIndex = findLastNotAddedIndex(finalState);
118 for (int i = 0; i < childCount; i++) {
119 final ExpandableView child = (ExpandableView) mHostLayout.getChildAt(i);
121 StackViewState viewState = finalState.getViewStateForView(child);
122 if (viewState == null || child.getVisibility() == View.GONE) {
126 child.setClipTopOptimization(0);
127 startStackAnimations(child, viewState, finalState, i, -1 /* fixedDelay */);
130 // no child has preformed any animation, lets finish
131 onAnimationFinished();
134 mNewAddChildren.clear();
135 mChildExpandingView = null;
138 private int findLastNotAddedIndex(StackScrollState finalState) {
139 int childCount = mHostLayout.getChildCount();
140 for (int i = childCount - 1; i >= 0; i--) {
141 final ExpandableView child = (ExpandableView) mHostLayout.getChildAt(i);
143 StackViewState viewState = finalState.getViewStateForView(child);
144 if (viewState == null || child.getVisibility() == View.GONE) {
147 if (!mNewAddChildren.contains(child)) {
148 return viewState.notGoneIndex;
156 * Start an animation to the given {@link StackViewState}.
158 * @param child the child to start the animation on
159 * @param viewState the {@link StackViewState} of the view to animate to
160 * @param finalState the final state after the animation
161 * @param i the index of the view; only relevant if the view is the speed bump and is
163 * @param fixedDelay a fixed delay if desired or -1 if the delay should be calculated
165 public void startStackAnimations(final ExpandableView child, StackViewState viewState,
166 StackScrollState finalState, int i, long fixedDelay) {
167 final float alpha = viewState.alpha;
168 boolean wasAdded = mNewAddChildren.contains(child);
169 long duration = mCurrentLength;
170 if (wasAdded && mAnimationFilter.hasGoToFullShadeEvent) {
171 child.setTranslationY(child.getTranslationY() + mGoToFullShadeAppearingTranslation);
172 float longerDurationFactor = viewState.notGoneIndex - mCurrentLastNotAddedIndex;
173 longerDurationFactor = (float) Math.pow(longerDurationFactor, 0.7f);
174 duration = ANIMATION_DURATION_APPEAR_DISAPPEAR + 50 +
175 (long) (100 * longerDurationFactor);
177 boolean yTranslationChanging = child.getTranslationY() != viewState.yTranslation;
178 boolean zTranslationChanging = child.getTranslationZ() != viewState.zTranslation;
179 boolean scaleChanging = child.getScaleX() != viewState.scale;
180 boolean alphaChanging = alpha != child.getAlpha();
181 boolean heightChanging = viewState.height != child.getActualHeight();
182 boolean darkChanging = viewState.dark != child.isDark();
183 boolean topInsetChanging = viewState.clipTopAmount != child.getClipTopAmount();
184 boolean hasDelays = mAnimationFilter.hasDelays;
185 boolean isDelayRelevant = yTranslationChanging || zTranslationChanging || scaleChanging ||
186 alphaChanging || heightChanging || topInsetChanging || darkChanging;
188 if (fixedDelay != -1) {
190 } else if (hasDelays && isDelayRelevant || wasAdded) {
191 delay = mCurrentAdditionalDelay + calculateChildAnimationDelay(viewState, finalState);
194 startViewAnimations(child, viewState, delay, duration);
196 // start height animation
197 if (heightChanging && child.getActualHeight() != 0) {
198 startHeightAnimation(child, viewState, duration, delay);
201 // start top inset animation
202 if (topInsetChanging) {
203 startInsetAnimation(child, viewState, duration, delay);
206 // start dimmed animation
207 child.setDimmed(viewState.dimmed, mAnimationFilter.animateDimmed);
209 // start dark animation
210 child.setDark(viewState.dark, mAnimationFilter.animateDark, delay);
212 // apply speed bump state
213 child.setBelowSpeedBump(viewState.belowSpeedBump);
215 // start hiding sensitive animation
216 child.setHideSensitive(viewState.hideSensitive, mAnimationFilter.animateHideSensitive,
220 child.performAddAnimation(delay, mCurrentLength);
222 if (child instanceof SpeedBumpView) {
223 finalState.performSpeedBumpAnimation(i, (SpeedBumpView) child, viewState,
225 } else if (child instanceof ExpandableNotificationRow) {
226 ExpandableNotificationRow row = (ExpandableNotificationRow) child;
227 row.startChildAnimation(finalState, this, child == mChildExpandingView, delay,
233 * Start an animation to a new {@link ViewState}.
235 * @param child the child to start the animation on
236 * @param viewState the {@link StackViewState} of the view to animate to
237 * @param delay a fixed delay
238 * @param duration the duration of the animation
240 public void startViewAnimations(View child, ViewState viewState, long delay, long duration) {
241 boolean wasVisible = child.getVisibility() == View.VISIBLE;
242 final float alpha = viewState.alpha;
243 if (!wasVisible && alpha != 0 && !viewState.gone) {
244 child.setVisibility(View.VISIBLE);
246 boolean yTranslationChanging = child.getTranslationY() != viewState.yTranslation;
247 boolean zTranslationChanging = child.getTranslationZ() != viewState.zTranslation;
248 boolean scaleChanging = child.getScaleX() != viewState.scale;
249 float childAlpha = child.getVisibility() == View.INVISIBLE ? 0.0f : child.getAlpha();
250 boolean alphaChanging = viewState.alpha != childAlpha;
252 // start translationY animation
253 if (yTranslationChanging) {
254 startYTranslationAnimation(child, viewState, duration, delay);
257 // start translationZ animation
258 if (zTranslationChanging) {
259 startZTranslationAnimation(child, viewState, duration, delay);
262 // start scale animation
264 startScaleAnimation(child, viewState, duration);
267 // start alpha animation
268 if (alphaChanging && child.getTranslationX() == 0) {
269 startAlphaAnimation(child, viewState, duration, delay);
273 private long calculateChildAnimationDelay(StackViewState viewState,
274 StackScrollState finalState) {
275 if (mAnimationFilter.hasDarkEvent) {
276 return calculateDelayDark(viewState);
278 if (mAnimationFilter.hasGoToFullShadeEvent) {
279 return calculateDelayGoToFullShade(viewState);
282 for (NotificationStackScrollLayout.AnimationEvent event : mNewEvents) {
283 long delayPerElement = ANIMATION_DELAY_PER_ELEMENT_INTERRUPTING;
284 switch (event.animationType) {
285 case NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_ADD: {
286 int ownIndex = viewState.notGoneIndex;
287 int changingIndex = finalState
288 .getViewStateForView(event.changingView).notGoneIndex;
289 int difference = Math.abs(ownIndex - changingIndex);
290 difference = Math.max(0, Math.min(DELAY_EFFECT_MAX_INDEX_DIFFERENCE,
292 long delay = (DELAY_EFFECT_MAX_INDEX_DIFFERENCE - difference) * delayPerElement;
293 minDelay = Math.max(delay, minDelay);
296 case NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_REMOVE_SWIPED_OUT:
297 delayPerElement = ANIMATION_DELAY_PER_ELEMENT_MANUAL;
298 case NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_REMOVE: {
299 int ownIndex = viewState.notGoneIndex;
300 boolean noNextView = event.viewAfterChangingView == null;
301 View viewAfterChangingView = noNextView
302 ? mHostLayout.getLastChildNotGone()
303 : event.viewAfterChangingView;
305 int nextIndex = finalState
306 .getViewStateForView(viewAfterChangingView).notGoneIndex;
307 if (ownIndex >= nextIndex) {
308 // we only have the view afterwards
311 int difference = Math.abs(ownIndex - nextIndex);
312 difference = Math.max(0, Math.min(DELAY_EFFECT_MAX_INDEX_DIFFERENCE,
314 long delay = difference * delayPerElement;
315 minDelay = Math.max(delay, minDelay);
325 private long calculateDelayDark(StackViewState viewState) {
327 if (mAnimationFilter.darkAnimationOriginIndex ==
328 NotificationStackScrollLayout.AnimationEvent.DARK_ANIMATION_ORIGIN_INDEX_ABOVE) {
330 } else if (mAnimationFilter.darkAnimationOriginIndex ==
331 NotificationStackScrollLayout.AnimationEvent.DARK_ANIMATION_ORIGIN_INDEX_BELOW) {
332 referenceIndex = mHostLayout.getNotGoneChildCount() - 1;
334 referenceIndex = mAnimationFilter.darkAnimationOriginIndex;
336 return Math.abs(referenceIndex - viewState.notGoneIndex) * ANIMATION_DELAY_PER_ELEMENT_DARK;
339 private long calculateDelayGoToFullShade(StackViewState viewState) {
340 float index = viewState.notGoneIndex;
341 index = (float) Math.pow(index, 0.7f);
342 return (long) (index * ANIMATION_DELAY_PER_ELEMENT_GO_TO_FULL_SHADE);
345 private void startHeightAnimation(final ExpandableView child,
346 StackViewState viewState, long duration, long delay) {
347 Integer previousStartValue = getChildTag(child, TAG_START_HEIGHT);
348 Integer previousEndValue = getChildTag(child, TAG_END_HEIGHT);
349 int newEndValue = viewState.height;
350 if (previousEndValue != null && previousEndValue == newEndValue) {
353 ValueAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_HEIGHT);
354 if (!mAnimationFilter.animateHeight) {
355 // just a local update was performed
356 if (previousAnimator != null) {
357 // we need to increase all animation keyframes of the previous animator by the
358 // relative change to the end value
359 PropertyValuesHolder[] values = previousAnimator.getValues();
360 int relativeDiff = newEndValue - previousEndValue;
361 int newStartValue = previousStartValue + relativeDiff;
362 values[0].setIntValues(newStartValue, newEndValue);
363 child.setTag(TAG_START_HEIGHT, newStartValue);
364 child.setTag(TAG_END_HEIGHT, newEndValue);
365 previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
368 // no new animation needed, let's just apply the value
369 child.setActualHeight(newEndValue, false);
374 ValueAnimator animator = ValueAnimator.ofInt(child.getActualHeight(), newEndValue);
375 animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
377 public void onAnimationUpdate(ValueAnimator animation) {
378 child.setActualHeight((int) animation.getAnimatedValue(),
379 false /* notifyListeners */);
382 animator.setInterpolator(mFastOutSlowInInterpolator);
383 long newDuration = cancelAnimatorAndGetNewDuration(duration, previousAnimator);
384 animator.setDuration(newDuration);
385 if (delay > 0 && (previousAnimator == null || !previousAnimator.isRunning())) {
386 animator.setStartDelay(delay);
388 animator.addListener(getGlobalAnimationFinishedListener());
389 // remove the tag when the animation is finished
390 animator.addListener(new AnimatorListenerAdapter() {
392 public void onAnimationEnd(Animator animation) {
393 child.setTag(TAG_ANIMATOR_HEIGHT, null);
394 child.setTag(TAG_START_HEIGHT, null);
395 child.setTag(TAG_END_HEIGHT, null);
398 startAnimator(animator);
399 child.setTag(TAG_ANIMATOR_HEIGHT, animator);
400 child.setTag(TAG_START_HEIGHT, child.getActualHeight());
401 child.setTag(TAG_END_HEIGHT, newEndValue);
404 private void startInsetAnimation(final ExpandableView child,
405 StackViewState viewState, long duration, long delay) {
406 Integer previousStartValue = getChildTag(child, TAG_START_TOP_INSET);
407 Integer previousEndValue = getChildTag(child, TAG_END_TOP_INSET);
408 int newEndValue = viewState.clipTopAmount;
409 if (previousEndValue != null && previousEndValue == newEndValue) {
412 ValueAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_TOP_INSET);
413 if (!mAnimationFilter.animateTopInset) {
414 // just a local update was performed
415 if (previousAnimator != null) {
416 // we need to increase all animation keyframes of the previous animator by the
417 // relative change to the end value
418 PropertyValuesHolder[] values = previousAnimator.getValues();
419 int relativeDiff = newEndValue - previousEndValue;
420 int newStartValue = previousStartValue + relativeDiff;
421 values[0].setIntValues(newStartValue, newEndValue);
422 child.setTag(TAG_START_TOP_INSET, newStartValue);
423 child.setTag(TAG_END_TOP_INSET, newEndValue);
424 previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
427 // no new animation needed, let's just apply the value
428 child.setClipTopAmount(newEndValue);
433 ValueAnimator animator = ValueAnimator.ofInt(child.getClipTopAmount(), newEndValue);
434 animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
436 public void onAnimationUpdate(ValueAnimator animation) {
437 child.setClipTopAmount((int) animation.getAnimatedValue());
440 animator.setInterpolator(mFastOutSlowInInterpolator);
441 long newDuration = cancelAnimatorAndGetNewDuration(duration, previousAnimator);
442 animator.setDuration(newDuration);
443 if (delay > 0 && (previousAnimator == null || !previousAnimator.isRunning())) {
444 animator.setStartDelay(delay);
446 animator.addListener(getGlobalAnimationFinishedListener());
447 // remove the tag when the animation is finished
448 animator.addListener(new AnimatorListenerAdapter() {
450 public void onAnimationEnd(Animator animation) {
451 child.setTag(TAG_ANIMATOR_TOP_INSET, null);
452 child.setTag(TAG_START_TOP_INSET, null);
453 child.setTag(TAG_END_TOP_INSET, null);
456 startAnimator(animator);
457 child.setTag(TAG_ANIMATOR_TOP_INSET, animator);
458 child.setTag(TAG_START_TOP_INSET, child.getClipTopAmount());
459 child.setTag(TAG_END_TOP_INSET, newEndValue);
462 private void startAlphaAnimation(final View child,
463 final ViewState viewState, long duration, long delay) {
464 Float previousStartValue = getChildTag(child,TAG_START_ALPHA);
465 Float previousEndValue = getChildTag(child,TAG_END_ALPHA);
466 final float newEndValue = viewState.alpha;
467 if (previousEndValue != null && previousEndValue == newEndValue) {
470 ObjectAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_ALPHA);
471 if (!mAnimationFilter.animateAlpha) {
472 // just a local update was performed
473 if (previousAnimator != null) {
474 // we need to increase all animation keyframes of the previous animator by the
475 // relative change to the end value
476 PropertyValuesHolder[] values = previousAnimator.getValues();
477 float relativeDiff = newEndValue - previousEndValue;
478 float newStartValue = previousStartValue + relativeDiff;
479 values[0].setFloatValues(newStartValue, newEndValue);
480 child.setTag(TAG_START_ALPHA, newStartValue);
481 child.setTag(TAG_END_ALPHA, newEndValue);
482 previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
485 // no new animation needed, let's just apply the value
486 child.setAlpha(newEndValue);
487 if (newEndValue == 0) {
488 child.setVisibility(View.INVISIBLE);
493 ObjectAnimator animator = ObjectAnimator.ofFloat(child, View.ALPHA,
494 child.getAlpha(), newEndValue);
495 animator.setInterpolator(mFastOutSlowInInterpolator);
497 child.setLayerType(View.LAYER_TYPE_HARDWARE, null);
498 animator.addListener(new AnimatorListenerAdapter() {
499 public boolean mWasCancelled;
502 public void onAnimationEnd(Animator animation) {
503 child.setLayerType(View.LAYER_TYPE_NONE, null);
504 if (newEndValue == 0 && !mWasCancelled) {
505 child.setVisibility(View.INVISIBLE);
507 // remove the tag when the animation is finished
508 child.setTag(TAG_ANIMATOR_ALPHA, null);
509 child.setTag(TAG_START_ALPHA, null);
510 child.setTag(TAG_END_ALPHA, null);
514 public void onAnimationCancel(Animator animation) {
515 mWasCancelled = true;
519 public void onAnimationStart(Animator animation) {
520 mWasCancelled = false;
523 long newDuration = cancelAnimatorAndGetNewDuration(duration, previousAnimator);
524 animator.setDuration(newDuration);
525 if (delay > 0 && (previousAnimator == null || !previousAnimator.isRunning())) {
526 animator.setStartDelay(delay);
528 animator.addListener(getGlobalAnimationFinishedListener());
530 startAnimator(animator);
531 child.setTag(TAG_ANIMATOR_ALPHA, animator);
532 child.setTag(TAG_START_ALPHA, child.getAlpha());
533 child.setTag(TAG_END_ALPHA, newEndValue);
536 private void startZTranslationAnimation(final View child,
537 final ViewState viewState, long duration, long delay) {
538 Float previousStartValue = getChildTag(child,TAG_START_TRANSLATION_Z);
539 Float previousEndValue = getChildTag(child,TAG_END_TRANSLATION_Z);
540 float newEndValue = viewState.zTranslation;
541 if (previousEndValue != null && previousEndValue == newEndValue) {
544 ObjectAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_TRANSLATION_Z);
545 if (!mAnimationFilter.animateZ) {
546 // just a local update was performed
547 if (previousAnimator != null) {
548 // we need to increase all animation keyframes of the previous animator by the
549 // relative change to the end value
550 PropertyValuesHolder[] values = previousAnimator.getValues();
551 float relativeDiff = newEndValue - previousEndValue;
552 float newStartValue = previousStartValue + relativeDiff;
553 values[0].setFloatValues(newStartValue, newEndValue);
554 child.setTag(TAG_START_TRANSLATION_Z, newStartValue);
555 child.setTag(TAG_END_TRANSLATION_Z, newEndValue);
556 previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
559 // no new animation needed, let's just apply the value
560 child.setTranslationZ(newEndValue);
564 ObjectAnimator animator = ObjectAnimator.ofFloat(child, View.TRANSLATION_Z,
565 child.getTranslationZ(), newEndValue);
566 animator.setInterpolator(mFastOutSlowInInterpolator);
567 long newDuration = cancelAnimatorAndGetNewDuration(duration, previousAnimator);
568 animator.setDuration(newDuration);
569 if (delay > 0 && (previousAnimator == null || !previousAnimator.isRunning())) {
570 animator.setStartDelay(delay);
572 animator.addListener(getGlobalAnimationFinishedListener());
573 // remove the tag when the animation is finished
574 animator.addListener(new AnimatorListenerAdapter() {
576 public void onAnimationEnd(Animator animation) {
577 child.setTag(TAG_ANIMATOR_TRANSLATION_Z, null);
578 child.setTag(TAG_START_TRANSLATION_Z, null);
579 child.setTag(TAG_END_TRANSLATION_Z, null);
582 startAnimator(animator);
583 child.setTag(TAG_ANIMATOR_TRANSLATION_Z, animator);
584 child.setTag(TAG_START_TRANSLATION_Z, child.getTranslationZ());
585 child.setTag(TAG_END_TRANSLATION_Z, newEndValue);
588 private void startYTranslationAnimation(final View child,
589 ViewState viewState, long duration, long delay) {
590 Float previousStartValue = getChildTag(child,TAG_START_TRANSLATION_Y);
591 Float previousEndValue = getChildTag(child,TAG_END_TRANSLATION_Y);
592 float newEndValue = viewState.yTranslation;
593 if (previousEndValue != null && previousEndValue == newEndValue) {
596 ObjectAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_TRANSLATION_Y);
597 if (!mAnimationFilter.animateY) {
598 // just a local update was performed
599 if (previousAnimator != null) {
600 // we need to increase all animation keyframes of the previous animator by the
601 // relative change to the end value
602 PropertyValuesHolder[] values = previousAnimator.getValues();
603 float relativeDiff = newEndValue - previousEndValue;
604 float newStartValue = previousStartValue + relativeDiff;
605 values[0].setFloatValues(newStartValue, newEndValue);
606 child.setTag(TAG_START_TRANSLATION_Y, newStartValue);
607 child.setTag(TAG_END_TRANSLATION_Y, newEndValue);
608 previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
611 // no new animation needed, let's just apply the value
612 child.setTranslationY(newEndValue);
617 ObjectAnimator animator = ObjectAnimator.ofFloat(child, View.TRANSLATION_Y,
618 child.getTranslationY(), newEndValue);
619 animator.setInterpolator(mFastOutSlowInInterpolator);
620 long newDuration = cancelAnimatorAndGetNewDuration(duration, previousAnimator);
621 animator.setDuration(newDuration);
622 if (delay > 0 && (previousAnimator == null || !previousAnimator.isRunning())) {
623 animator.setStartDelay(delay);
625 animator.addListener(getGlobalAnimationFinishedListener());
626 // remove the tag when the animation is finished
627 animator.addListener(new AnimatorListenerAdapter() {
629 public void onAnimationEnd(Animator animation) {
630 child.setTag(TAG_ANIMATOR_TRANSLATION_Y, null);
631 child.setTag(TAG_START_TRANSLATION_Y, null);
632 child.setTag(TAG_END_TRANSLATION_Y, null);
635 startAnimator(animator);
636 child.setTag(TAG_ANIMATOR_TRANSLATION_Y, animator);
637 child.setTag(TAG_START_TRANSLATION_Y, child.getTranslationY());
638 child.setTag(TAG_END_TRANSLATION_Y, newEndValue);
641 private void startScaleAnimation(final View child,
642 ViewState viewState, long duration) {
643 Float previousStartValue = getChildTag(child, TAG_START_SCALE);
644 Float previousEndValue = getChildTag(child, TAG_END_SCALE);
645 float newEndValue = viewState.scale;
646 if (previousEndValue != null && previousEndValue == newEndValue) {
649 ObjectAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_SCALE);
650 if (!mAnimationFilter.animateScale) {
651 // just a local update was performed
652 if (previousAnimator != null) {
653 // we need to increase all animation keyframes of the previous animator by the
654 // relative change to the end value
655 PropertyValuesHolder[] values = previousAnimator.getValues();
656 float relativeDiff = newEndValue - previousEndValue;
657 float newStartValue = previousStartValue + relativeDiff;
658 values[0].setFloatValues(newStartValue, newEndValue);
659 values[1].setFloatValues(newStartValue, newEndValue);
660 child.setTag(TAG_START_SCALE, newStartValue);
661 child.setTag(TAG_END_SCALE, newEndValue);
662 previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
665 // no new animation needed, let's just apply the value
666 child.setScaleX(newEndValue);
667 child.setScaleY(newEndValue);
671 PropertyValuesHolder holderX =
672 PropertyValuesHolder.ofFloat(View.SCALE_X, child.getScaleX(), newEndValue);
673 PropertyValuesHolder holderY =
674 PropertyValuesHolder.ofFloat(View.SCALE_Y, child.getScaleY(), newEndValue);
675 ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(child, holderX, holderY);
676 animator.setInterpolator(mFastOutSlowInInterpolator);
677 long newDuration = cancelAnimatorAndGetNewDuration(duration, previousAnimator);
678 animator.setDuration(newDuration);
679 animator.addListener(getGlobalAnimationFinishedListener());
680 // remove the tag when the animation is finished
681 animator.addListener(new AnimatorListenerAdapter() {
683 public void onAnimationEnd(Animator animation) {
684 child.setTag(TAG_ANIMATOR_SCALE, null);
685 child.setTag(TAG_START_SCALE, null);
686 child.setTag(TAG_END_SCALE, null);
689 startAnimator(animator);
690 child.setTag(TAG_ANIMATOR_SCALE, animator);
691 child.setTag(TAG_START_SCALE, child.getScaleX());
692 child.setTag(TAG_END_SCALE, newEndValue);
695 private void startAnimator(ValueAnimator animator) {
696 mAnimatorSet.add(animator);
701 * @return an adapter which ensures that onAnimationFinished is called once no animation is
704 private AnimatorListenerAdapter getGlobalAnimationFinishedListener() {
705 if (!mAnimationListenerPool.empty()) {
706 return mAnimationListenerPool.pop();
709 // We need to create a new one, no reusable ones found
710 return new AnimatorListenerAdapter() {
711 private boolean mWasCancelled;
714 public void onAnimationEnd(Animator animation) {
715 mAnimatorSet.remove(animation);
716 if (mAnimatorSet.isEmpty() && !mWasCancelled) {
717 onAnimationFinished();
719 mAnimationListenerPool.push(this);
723 public void onAnimationCancel(Animator animation) {
724 mWasCancelled = true;
728 public void onAnimationStart(Animator animation) {
729 mWasCancelled = false;
734 private static <T> T getChildTag(View child, int tag) {
735 return (T) child.getTag(tag);
739 * Cancel the previous animator and get the duration of the new animation.
741 * @param duration the new duration
742 * @param previousAnimator the animator which was running before
743 * @return the new duration
745 private long cancelAnimatorAndGetNewDuration(long duration, ValueAnimator previousAnimator) {
746 long newDuration = duration;
747 if (previousAnimator != null) {
748 // We take either the desired length of the new animation or the remaining time of
749 // the previous animator, whichever is longer.
750 newDuration = Math.max(previousAnimator.getDuration()
751 - previousAnimator.getCurrentPlayTime(), newDuration);
752 previousAnimator.cancel();
757 private void onAnimationFinished() {
758 mHostLayout.onChildAnimationFinished();
762 * Process the animationEvents for a new animation
764 * @param animationEvents the animation events for the animation to perform
765 * @param finalState the final state to animate to
767 private void processAnimationEvents(
768 ArrayList<NotificationStackScrollLayout.AnimationEvent> animationEvents,
769 StackScrollState finalState) {
770 for (NotificationStackScrollLayout.AnimationEvent event : animationEvents) {
771 final ExpandableView changingView = (ExpandableView) event.changingView;
772 if (event.animationType ==
773 NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_ADD) {
775 // This item is added, initialize it's properties.
776 StackViewState viewState = finalState
777 .getViewStateForView(changingView);
778 if (viewState == null) {
779 // The position for this child was never generated, let's continue.
782 if (changingView.getVisibility() == View.GONE) {
783 // The view was set to gone but the state never removed
784 finalState.removeViewStateForView(changingView);
787 finalState.applyState(changingView, viewState);
788 mNewAddChildren.add(changingView);
790 } else if (event.animationType ==
791 NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_REMOVE) {
792 if (changingView.getVisibility() == View.GONE) {
793 mHostLayout.getOverlay().remove(changingView);
797 // Find the amount to translate up. This is needed in order to understand the
798 // direction of the remove animation (either downwards or upwards)
799 StackViewState viewState = finalState
800 .getViewStateForView(event.viewAfterChangingView);
801 int actualHeight = changingView.getActualHeight();
802 // upwards by default
803 float translationDirection = -1.0f;
804 if (viewState != null) {
805 // there was a view after this one, Approximate the distance the next child
807 translationDirection = ((viewState.yTranslation
808 - (changingView.getTranslationY() + actualHeight / 2.0f)) * 2 /
810 translationDirection = Math.max(Math.min(translationDirection, 1.0f),-1.0f);
813 changingView.performRemoveAnimation(ANIMATION_DURATION_APPEAR_DISAPPEAR,
814 translationDirection, new Runnable() {
817 // remove the temporary overlay
818 mHostLayout.getOverlay().remove(changingView);
821 } else if (event.animationType ==
822 NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_REMOVE_SWIPED_OUT) {
823 // A race condition can trigger the view to be added to the overlay even though
824 // it is swiped out. So let's remove it
825 mHostLayout.getOverlay().remove(changingView);
826 } else if (event.animationType == NotificationStackScrollLayout
827 .AnimationEvent.ANIMATION_TYPE_GROUP_EXPANSION_CHANGED) {
828 ExpandableNotificationRow row = (ExpandableNotificationRow) event.changingView;
829 row.prepareExpansionChanged(finalState);
830 mChildExpandingView = row;
832 mNewEvents.add(event);
836 public void animateOverScrollToAmount(float targetAmount, final boolean onTop,
837 final boolean isRubberbanded) {
838 final float startOverScrollAmount = mHostLayout.getCurrentOverScrollAmount(onTop);
839 if (targetAmount == startOverScrollAmount) {
842 cancelOverScrollAnimators(onTop);
843 ValueAnimator overScrollAnimator = ValueAnimator.ofFloat(startOverScrollAmount,
845 overScrollAnimator.setDuration(ANIMATION_DURATION_STANDARD);
846 overScrollAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
848 public void onAnimationUpdate(ValueAnimator animation) {
849 float currentOverScroll = (float) animation.getAnimatedValue();
850 mHostLayout.setOverScrollAmount(
851 currentOverScroll, onTop, false /* animate */, false /* cancelAnimators */,
855 overScrollAnimator.setInterpolator(mFastOutSlowInInterpolator);
856 overScrollAnimator.addListener(new AnimatorListenerAdapter() {
858 public void onAnimationEnd(Animator animation) {
860 mTopOverScrollAnimator = null;
862 mBottomOverScrollAnimator = null;
866 overScrollAnimator.start();
868 mTopOverScrollAnimator = overScrollAnimator;
870 mBottomOverScrollAnimator = overScrollAnimator;
874 public void cancelOverScrollAnimators(boolean onTop) {
875 ValueAnimator currentAnimator = onTop ? mTopOverScrollAnimator : mBottomOverScrollAnimator;
876 if (currentAnimator != null) {
877 currentAnimator.cancel();
882 * Get the end value of the height animation running on a view or the actualHeight
883 * if no animation is running.
885 public static int getFinalActualHeight(ExpandableView view) {
889 ValueAnimator heightAnimator = getChildTag(view, TAG_ANIMATOR_HEIGHT);
890 if (heightAnimator == null) {
891 return view.getActualHeight();
893 return getChildTag(view, TAG_END_HEIGHT);