1 /*
2  * Copyright (C) 2014 The Android Open Source Project
3  *
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
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
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
15  */
16 
17 package com.android.systemui.statusbar.stack;
18 
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;
27 
28 import com.android.systemui.R;
29 import com.android.systemui.statusbar.ExpandableView;
30 import com.android.systemui.statusbar.SpeedBumpView;
31 
32 import java.util.ArrayList;
33 import java.util.HashSet;
34 import java.util.Set;
35 import java.util.Stack;
36 
37 /**
38  * An stack state animator which handles animations to new StackScrollStates
39  */
40 public class StackStateAnimator {
41 
42     public static final int ANIMATION_DURATION_STANDARD = 360;
43     public static final int ANIMATION_DURATION_GO_TO_FULL_SHADE = 448;
44     public static final int ANIMATION_DURATION_APPEAR_DISAPPEAR = 464;
45     public static final int ANIMATION_DURATION_DIMMED_ACTIVATED = 220;
46     public static final int ANIMATION_DELAY_PER_ELEMENT_INTERRUPTING = 80;
47     public static final int ANIMATION_DELAY_PER_ELEMENT_MANUAL = 32;
48     public static final int ANIMATION_DELAY_PER_ELEMENT_GO_TO_FULL_SHADE = 48;
49     public static final int ANIMATION_DELAY_PER_ELEMENT_DARK = 24;
50     private static final int DELAY_EFFECT_MAX_INDEX_DIFFERENCE = 2;
51 
52     private static final int TAG_ANIMATOR_TRANSLATION_Y = R.id.translation_y_animator_tag;
53     private static final int TAG_ANIMATOR_TRANSLATION_Z = R.id.translation_z_animator_tag;
54     private static final int TAG_ANIMATOR_SCALE = R.id.scale_animator_tag;
55     private static final int TAG_ANIMATOR_ALPHA = R.id.alpha_animator_tag;
56     private static final int TAG_ANIMATOR_HEIGHT = R.id.height_animator_tag;
57     private static final int TAG_ANIMATOR_TOP_INSET = R.id.top_inset_animator_tag;
58     private static final int TAG_END_TRANSLATION_Y = R.id.translation_y_animator_end_value_tag;
59     private static final int TAG_END_TRANSLATION_Z = R.id.translation_z_animator_end_value_tag;
60     private static final int TAG_END_SCALE = R.id.scale_animator_end_value_tag;
61     private static final int TAG_END_ALPHA = R.id.alpha_animator_end_value_tag;
62     private static final int TAG_END_HEIGHT = R.id.height_animator_end_value_tag;
63     private static final int TAG_END_TOP_INSET = R.id.top_inset_animator_end_value_tag;
64     private static final int TAG_START_TRANSLATION_Y = R.id.translation_y_animator_start_value_tag;
65     private static final int TAG_START_TRANSLATION_Z = R.id.translation_z_animator_start_value_tag;
66     private static final int TAG_START_SCALE = R.id.scale_animator_start_value_tag;
67     private static final int TAG_START_ALPHA = R.id.alpha_animator_start_value_tag;
68     private static final int TAG_START_HEIGHT = R.id.height_animator_start_value_tag;
69     private static final int TAG_START_TOP_INSET = R.id.top_inset_animator_start_value_tag;
70 
71     private final Interpolator mFastOutSlowInInterpolator;
72     private final int mGoToFullShadeAppearingTranslation;
73     public NotificationStackScrollLayout mHostLayout;
74     private ArrayList<NotificationStackScrollLayout.AnimationEvent> mNewEvents =
75             new ArrayList<>();
76     private ArrayList<View> mNewAddChildren = new ArrayList<>();
77     private Set<Animator> mAnimatorSet = new HashSet<>();
78     private Stack<AnimatorListenerAdapter> mAnimationListenerPool = new Stack<>();
79     private AnimationFilter mAnimationFilter = new AnimationFilter();
80     private long mCurrentLength;
81     private long mCurrentAdditionalDelay;
82 
83     /** The current index for the last child which was not added in this event set. */
84     private int mCurrentLastNotAddedIndex;
85 
86     private ValueAnimator mTopOverScrollAnimator;
87     private ValueAnimator mBottomOverScrollAnimator;
88 
StackStateAnimator(NotificationStackScrollLayout hostLayout)89     public StackStateAnimator(NotificationStackScrollLayout hostLayout) {
90         mHostLayout = hostLayout;
91         mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(hostLayout.getContext(),
92                 android.R.interpolator.fast_out_slow_in);
93         mGoToFullShadeAppearingTranslation =
94                 hostLayout.getContext().getResources().getDimensionPixelSize(
95                         R.dimen.go_to_full_shade_appearing_translation);
96     }
97 
isRunning()98     public boolean isRunning() {
99         return !mAnimatorSet.isEmpty();
100     }
101 
startAnimationForEvents( ArrayList<NotificationStackScrollLayout.AnimationEvent> mAnimationEvents, StackScrollState finalState, long additionalDelay)102     public void startAnimationForEvents(
103             ArrayList<NotificationStackScrollLayout.AnimationEvent> mAnimationEvents,
104             StackScrollState finalState, long additionalDelay) {
105 
106         processAnimationEvents(mAnimationEvents, finalState);
107 
108         int childCount = mHostLayout.getChildCount();
109         mAnimationFilter.applyCombination(mNewEvents);
110         mCurrentAdditionalDelay = additionalDelay;
111         mCurrentLength = NotificationStackScrollLayout.AnimationEvent.combineLength(mNewEvents);
112         mCurrentLastNotAddedIndex = findLastNotAddedIndex(finalState);
113         for (int i = 0; i < childCount; i++) {
114             final ExpandableView child = (ExpandableView) mHostLayout.getChildAt(i);
115 
116             StackScrollState.ViewState viewState = finalState.getViewStateForView(child);
117             if (viewState == null || child.getVisibility() == View.GONE) {
118                 continue;
119             }
120 
121             child.setClipBounds(null);
122             startAnimations(child, viewState, finalState, i);
123         }
124         if (!isRunning()) {
125             // no child has preformed any animation, lets finish
126             onAnimationFinished();
127         }
128         mNewEvents.clear();
129         mNewAddChildren.clear();
130     }
131 
findLastNotAddedIndex(StackScrollState finalState)132     private int findLastNotAddedIndex(StackScrollState finalState) {
133         int childCount = mHostLayout.getChildCount();
134         for (int i = childCount - 1; i >= 0; i--) {
135             final ExpandableView child = (ExpandableView) mHostLayout.getChildAt(i);
136 
137             StackScrollState.ViewState viewState = finalState.getViewStateForView(child);
138             if (viewState == null || child.getVisibility() == View.GONE) {
139                 continue;
140             }
141             if (!mNewAddChildren.contains(child)) {
142                 return viewState.notGoneIndex;
143             }
144         }
145         return -1;
146     }
147 
148     /**
149      * Start an animation to the given viewState
150      */
startAnimations(final ExpandableView child, StackScrollState.ViewState viewState, StackScrollState finalState, int i)151     private void startAnimations(final ExpandableView child, StackScrollState.ViewState viewState,
152             StackScrollState finalState, int i) {
153         int childVisibility = child.getVisibility();
154         boolean wasVisible = childVisibility == View.VISIBLE;
155         final float alpha = viewState.alpha;
156         if (!wasVisible && alpha != 0 && !viewState.gone) {
157             child.setVisibility(View.VISIBLE);
158         }
159 
160         boolean yTranslationChanging = child.getTranslationY() != viewState.yTranslation;
161         boolean zTranslationChanging = child.getTranslationZ() != viewState.zTranslation;
162         boolean scaleChanging = child.getScaleX() != viewState.scale;
163         boolean alphaChanging = alpha != child.getAlpha();
164         boolean heightChanging = viewState.height != child.getActualHeight();
165         boolean darkChanging = viewState.dark != child.isDark();
166         boolean topInsetChanging = viewState.clipTopAmount != child.getClipTopAmount();
167         boolean wasAdded = mNewAddChildren.contains(child);
168         boolean hasDelays = mAnimationFilter.hasDelays;
169         boolean isDelayRelevant = yTranslationChanging || zTranslationChanging || scaleChanging ||
170                 alphaChanging || heightChanging || topInsetChanging || darkChanging;
171         boolean noAnimation = wasAdded;
172         long delay = 0;
173         long duration = mCurrentLength;
174         if (hasDelays && isDelayRelevant || wasAdded) {
175             delay = mCurrentAdditionalDelay + calculateChildAnimationDelay(viewState, finalState);
176         }
177 
178         if (wasAdded && mAnimationFilter.hasGoToFullShadeEvent) {
179             child.setTranslationY(child.getTranslationY() + mGoToFullShadeAppearingTranslation);
180             yTranslationChanging = true;
181             float longerDurationFactor = viewState.notGoneIndex - mCurrentLastNotAddedIndex;
182             longerDurationFactor = (float) Math.pow(longerDurationFactor, 0.7f);
183             duration = ANIMATION_DURATION_APPEAR_DISAPPEAR + 50 +
184                     (long) (100 * longerDurationFactor);
185         }
186 
187         // start translationY animation
188         if (yTranslationChanging) {
189             if (noAnimation && !mAnimationFilter.hasGoToFullShadeEvent) {
190                 child.setTranslationY(viewState.yTranslation);
191             } else {
192                 startYTranslationAnimation(child, viewState, duration, delay);
193             }
194         }
195 
196         // start translationZ animation
197         if (zTranslationChanging) {
198             if (noAnimation) {
199                 child.setTranslationZ(viewState.zTranslation);
200             } else {
201                 startZTranslationAnimation(child, viewState, duration, delay);
202             }
203         }
204 
205         // start scale animation
206         if (scaleChanging) {
207             if (noAnimation) {
208                 child.setScaleX(viewState.scale);
209                 child.setScaleY(viewState.scale);
210             } else {
211                 startScaleAnimation(child, viewState, duration);
212             }
213         }
214 
215         // start alpha animation
216         if (alphaChanging && child.getTranslationX() == 0) {
217             if (noAnimation) {
218                 child.setAlpha(viewState.alpha);
219             } else {
220                 startAlphaAnimation(child, viewState, duration, delay);
221             }
222         }
223 
224         // start height animation
225         if (heightChanging && child.getActualHeight() != 0) {
226             if (noAnimation) {
227                 child.setActualHeight(viewState.height, false);
228             } else {
229                 startHeightAnimation(child, viewState, duration, delay);
230             }
231         }
232 
233         // start top inset animation
234         if (topInsetChanging) {
235             if (noAnimation) {
236                 child.setClipTopAmount(viewState.clipTopAmount);
237             } else {
238                 startInsetAnimation(child, viewState, duration, delay);
239             }
240         }
241 
242         // start dimmed animation
243         child.setDimmed(viewState.dimmed, mAnimationFilter.animateDimmed && !wasAdded
244                 && !noAnimation);
245 
246         // start dark animation
247         child.setDark(viewState.dark, mAnimationFilter.animateDark && !noAnimation, delay);
248 
249         // apply speed bump state
250         child.setBelowSpeedBump(viewState.belowSpeedBump);
251 
252         // start hiding sensitive animation
253         child.setHideSensitive(viewState.hideSensitive, mAnimationFilter.animateHideSensitive &&
254                 !wasAdded && !noAnimation, delay, duration);
255 
256         if (wasAdded) {
257             child.performAddAnimation(delay, mCurrentLength);
258         }
259         if (child instanceof SpeedBumpView) {
260             finalState.performSpeedBumpAnimation(i, (SpeedBumpView) child, viewState,
261                     delay + duration);
262         }
263     }
264 
calculateChildAnimationDelay(StackScrollState.ViewState viewState, StackScrollState finalState)265     private long calculateChildAnimationDelay(StackScrollState.ViewState viewState,
266             StackScrollState finalState) {
267         if (mAnimationFilter.hasDarkEvent) {
268             return calculateDelayDark(viewState);
269         }
270         if (mAnimationFilter.hasGoToFullShadeEvent) {
271             return calculateDelayGoToFullShade(viewState);
272         }
273         long minDelay = 0;
274         for (NotificationStackScrollLayout.AnimationEvent event : mNewEvents) {
275             long delayPerElement = ANIMATION_DELAY_PER_ELEMENT_INTERRUPTING;
276             switch (event.animationType) {
277                 case NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_ADD: {
278                     int ownIndex = viewState.notGoneIndex;
279                     int changingIndex = finalState
280                             .getViewStateForView(event.changingView).notGoneIndex;
281                     int difference = Math.abs(ownIndex - changingIndex);
282                     difference = Math.max(0, Math.min(DELAY_EFFECT_MAX_INDEX_DIFFERENCE,
283                             difference - 1));
284                     long delay = (DELAY_EFFECT_MAX_INDEX_DIFFERENCE - difference) * delayPerElement;
285                     minDelay = Math.max(delay, minDelay);
286                     break;
287                 }
288                 case NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_REMOVE_SWIPED_OUT:
289                     delayPerElement = ANIMATION_DELAY_PER_ELEMENT_MANUAL;
290                 case NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_REMOVE: {
291                     int ownIndex = viewState.notGoneIndex;
292                     boolean noNextView = event.viewAfterChangingView == null;
293                     View viewAfterChangingView = noNextView
294                             ? mHostLayout.getLastChildNotGone()
295                             : event.viewAfterChangingView;
296 
297                     int nextIndex = finalState
298                             .getViewStateForView(viewAfterChangingView).notGoneIndex;
299                     if (ownIndex >= nextIndex) {
300                         // we only have the view afterwards
301                         ownIndex++;
302                     }
303                     int difference = Math.abs(ownIndex - nextIndex);
304                     difference = Math.max(0, Math.min(DELAY_EFFECT_MAX_INDEX_DIFFERENCE,
305                             difference - 1));
306                     long delay = difference * delayPerElement;
307                     minDelay = Math.max(delay, minDelay);
308                     break;
309                 }
310                 default:
311                     break;
312             }
313         }
314         return minDelay;
315     }
316 
calculateDelayDark(StackScrollState.ViewState viewState)317     private long calculateDelayDark(StackScrollState.ViewState viewState) {
318         int referenceIndex;
319         if (mAnimationFilter.darkAnimationOriginIndex ==
320                 NotificationStackScrollLayout.AnimationEvent.DARK_ANIMATION_ORIGIN_INDEX_ABOVE) {
321             referenceIndex = 0;
322         } else if (mAnimationFilter.darkAnimationOriginIndex ==
323                 NotificationStackScrollLayout.AnimationEvent.DARK_ANIMATION_ORIGIN_INDEX_BELOW) {
324             referenceIndex = mHostLayout.getNotGoneChildCount() - 1;
325         } else {
326             referenceIndex = mAnimationFilter.darkAnimationOriginIndex;
327         }
328         return Math.abs(referenceIndex - viewState.notGoneIndex) * ANIMATION_DELAY_PER_ELEMENT_DARK;
329     }
330 
calculateDelayGoToFullShade(StackScrollState.ViewState viewState)331     private long calculateDelayGoToFullShade(StackScrollState.ViewState viewState) {
332         float index = viewState.notGoneIndex;
333         index = (float) Math.pow(index, 0.7f);
334         return (long) (index * ANIMATION_DELAY_PER_ELEMENT_GO_TO_FULL_SHADE);
335     }
336 
startHeightAnimation(final ExpandableView child, StackScrollState.ViewState viewState, long duration, long delay)337     private void startHeightAnimation(final ExpandableView child,
338             StackScrollState.ViewState viewState, long duration, long delay) {
339         Integer previousStartValue = getChildTag(child, TAG_START_HEIGHT);
340         Integer previousEndValue = getChildTag(child, TAG_END_HEIGHT);
341         int newEndValue = viewState.height;
342         if (previousEndValue != null && previousEndValue == newEndValue) {
343             return;
344         }
345         ValueAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_HEIGHT);
346         if (!mAnimationFilter.animateHeight) {
347             // just a local update was performed
348             if (previousAnimator != null) {
349                 // we need to increase all animation keyframes of the previous animator by the
350                 // relative change to the end value
351                 PropertyValuesHolder[] values = previousAnimator.getValues();
352                 int relativeDiff = newEndValue - previousEndValue;
353                 int newStartValue = previousStartValue + relativeDiff;
354                 values[0].setIntValues(newStartValue, newEndValue);
355                 child.setTag(TAG_START_HEIGHT, newStartValue);
356                 child.setTag(TAG_END_HEIGHT, newEndValue);
357                 previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
358                 return;
359             } else {
360                 // no new animation needed, let's just apply the value
361                 child.setActualHeight(newEndValue, false);
362                 return;
363             }
364         }
365 
366         ValueAnimator animator = ValueAnimator.ofInt(child.getActualHeight(), newEndValue);
367         animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
368             @Override
369             public void onAnimationUpdate(ValueAnimator animation) {
370                 child.setActualHeight((int) animation.getAnimatedValue(),
371                         false /* notifyListeners */);
372             }
373         });
374         animator.setInterpolator(mFastOutSlowInInterpolator);
375         long newDuration = cancelAnimatorAndGetNewDuration(duration, previousAnimator);
376         animator.setDuration(newDuration);
377         if (delay > 0 && (previousAnimator == null || !previousAnimator.isRunning())) {
378             animator.setStartDelay(delay);
379         }
380         animator.addListener(getGlobalAnimationFinishedListener());
381         // remove the tag when the animation is finished
382         animator.addListener(new AnimatorListenerAdapter() {
383             @Override
384             public void onAnimationEnd(Animator animation) {
385                 child.setTag(TAG_ANIMATOR_HEIGHT, null);
386                 child.setTag(TAG_START_HEIGHT, null);
387                 child.setTag(TAG_END_HEIGHT, null);
388             }
389         });
390         startAnimator(animator);
391         child.setTag(TAG_ANIMATOR_HEIGHT, animator);
392         child.setTag(TAG_START_HEIGHT, child.getActualHeight());
393         child.setTag(TAG_END_HEIGHT, newEndValue);
394     }
395 
startInsetAnimation(final ExpandableView child, StackScrollState.ViewState viewState, long duration, long delay)396     private void startInsetAnimation(final ExpandableView child,
397             StackScrollState.ViewState viewState, long duration, long delay) {
398         Integer previousStartValue = getChildTag(child, TAG_START_TOP_INSET);
399         Integer previousEndValue = getChildTag(child, TAG_END_TOP_INSET);
400         int newEndValue = viewState.clipTopAmount;
401         if (previousEndValue != null && previousEndValue == newEndValue) {
402             return;
403         }
404         ValueAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_TOP_INSET);
405         if (!mAnimationFilter.animateTopInset) {
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                 int relativeDiff = newEndValue - previousEndValue;
412                 int newStartValue = previousStartValue + relativeDiff;
413                 values[0].setIntValues(newStartValue, newEndValue);
414                 child.setTag(TAG_START_TOP_INSET, newStartValue);
415                 child.setTag(TAG_END_TOP_INSET, newEndValue);
416                 previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
417                 return;
418             } else {
419                 // no new animation needed, let's just apply the value
420                 child.setClipTopAmount(newEndValue);
421                 return;
422             }
423         }
424 
425         ValueAnimator animator = ValueAnimator.ofInt(child.getClipTopAmount(), newEndValue);
426         animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
427             @Override
428             public void onAnimationUpdate(ValueAnimator animation) {
429                 child.setClipTopAmount((int) animation.getAnimatedValue());
430             }
431         });
432         animator.setInterpolator(mFastOutSlowInInterpolator);
433         long newDuration = cancelAnimatorAndGetNewDuration(duration, previousAnimator);
434         animator.setDuration(newDuration);
435         if (delay > 0 && (previousAnimator == null || !previousAnimator.isRunning())) {
436             animator.setStartDelay(delay);
437         }
438         animator.addListener(getGlobalAnimationFinishedListener());
439         // remove the tag when the animation is finished
440         animator.addListener(new AnimatorListenerAdapter() {
441             @Override
442             public void onAnimationEnd(Animator animation) {
443                 child.setTag(TAG_ANIMATOR_TOP_INSET, null);
444                 child.setTag(TAG_START_TOP_INSET, null);
445                 child.setTag(TAG_END_TOP_INSET, null);
446             }
447         });
448         startAnimator(animator);
449         child.setTag(TAG_ANIMATOR_TOP_INSET, animator);
450         child.setTag(TAG_START_TOP_INSET, child.getClipTopAmount());
451         child.setTag(TAG_END_TOP_INSET, newEndValue);
452     }
453 
startAlphaAnimation(final ExpandableView child, final StackScrollState.ViewState viewState, long duration, long delay)454     private void startAlphaAnimation(final ExpandableView child,
455             final StackScrollState.ViewState viewState, long duration, long delay) {
456         Float previousStartValue = getChildTag(child,TAG_START_ALPHA);
457         Float previousEndValue = getChildTag(child,TAG_END_ALPHA);
458         final float newEndValue = viewState.alpha;
459         if (previousEndValue != null && previousEndValue == newEndValue) {
460             return;
461         }
462         ObjectAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_ALPHA);
463         if (!mAnimationFilter.animateAlpha) {
464             // just a local update was performed
465             if (previousAnimator != null) {
466                 // we need to increase all animation keyframes of the previous animator by the
467                 // relative change to the end value
468                 PropertyValuesHolder[] values = previousAnimator.getValues();
469                 float relativeDiff = newEndValue - previousEndValue;
470                 float newStartValue = previousStartValue + relativeDiff;
471                 values[0].setFloatValues(newStartValue, newEndValue);
472                 child.setTag(TAG_START_ALPHA, newStartValue);
473                 child.setTag(TAG_END_ALPHA, newEndValue);
474                 previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
475                 return;
476             } else {
477                 // no new animation needed, let's just apply the value
478                 child.setAlpha(newEndValue);
479                 if (newEndValue == 0) {
480                     child.setVisibility(View.INVISIBLE);
481                 }
482             }
483         }
484 
485         ObjectAnimator animator = ObjectAnimator.ofFloat(child, View.ALPHA,
486                 child.getAlpha(), newEndValue);
487         animator.setInterpolator(mFastOutSlowInInterpolator);
488         // Handle layer type
489         child.setLayerType(View.LAYER_TYPE_HARDWARE, null);
490         animator.addListener(new AnimatorListenerAdapter() {
491             public boolean mWasCancelled;
492 
493             @Override
494             public void onAnimationEnd(Animator animation) {
495                 child.setLayerType(View.LAYER_TYPE_NONE, null);
496                 if (newEndValue == 0 && !mWasCancelled) {
497                     child.setVisibility(View.INVISIBLE);
498                 }
499                 // remove the tag when the animation is finished
500                 child.setTag(TAG_ANIMATOR_ALPHA, null);
501                 child.setTag(TAG_START_ALPHA, null);
502                 child.setTag(TAG_END_ALPHA, null);
503             }
504 
505             @Override
506             public void onAnimationCancel(Animator animation) {
507                 mWasCancelled = true;
508             }
509 
510             @Override
511             public void onAnimationStart(Animator animation) {
512                 mWasCancelled = false;
513             }
514         });
515         long newDuration = cancelAnimatorAndGetNewDuration(duration, previousAnimator);
516         animator.setDuration(newDuration);
517         if (delay > 0 && (previousAnimator == null || !previousAnimator.isRunning())) {
518             animator.setStartDelay(delay);
519         }
520         animator.addListener(getGlobalAnimationFinishedListener());
521 
522         startAnimator(animator);
523         child.setTag(TAG_ANIMATOR_ALPHA, animator);
524         child.setTag(TAG_START_ALPHA, child.getAlpha());
525         child.setTag(TAG_END_ALPHA, newEndValue);
526     }
527 
startZTranslationAnimation(final ExpandableView child, final StackScrollState.ViewState viewState, long duration, long delay)528     private void startZTranslationAnimation(final ExpandableView child,
529             final StackScrollState.ViewState viewState, long duration, long delay) {
530         Float previousStartValue = getChildTag(child,TAG_START_TRANSLATION_Z);
531         Float previousEndValue = getChildTag(child,TAG_END_TRANSLATION_Z);
532         float newEndValue = viewState.zTranslation;
533         if (previousEndValue != null && previousEndValue == newEndValue) {
534             return;
535         }
536         ObjectAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_TRANSLATION_Z);
537         if (!mAnimationFilter.animateZ) {
538             // just a local update was performed
539             if (previousAnimator != null) {
540                 // we need to increase all animation keyframes of the previous animator by the
541                 // relative change to the end value
542                 PropertyValuesHolder[] values = previousAnimator.getValues();
543                 float relativeDiff = newEndValue - previousEndValue;
544                 float newStartValue = previousStartValue + relativeDiff;
545                 values[0].setFloatValues(newStartValue, newEndValue);
546                 child.setTag(TAG_START_TRANSLATION_Z, newStartValue);
547                 child.setTag(TAG_END_TRANSLATION_Z, newEndValue);
548                 previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
549                 return;
550             } else {
551                 // no new animation needed, let's just apply the value
552                 child.setTranslationZ(newEndValue);
553             }
554         }
555 
556         ObjectAnimator animator = ObjectAnimator.ofFloat(child, View.TRANSLATION_Z,
557                 child.getTranslationZ(), newEndValue);
558         animator.setInterpolator(mFastOutSlowInInterpolator);
559         long newDuration = cancelAnimatorAndGetNewDuration(duration, previousAnimator);
560         animator.setDuration(newDuration);
561         if (delay > 0 && (previousAnimator == null || !previousAnimator.isRunning())) {
562             animator.setStartDelay(delay);
563         }
564         animator.addListener(getGlobalAnimationFinishedListener());
565         // remove the tag when the animation is finished
566         animator.addListener(new AnimatorListenerAdapter() {
567             @Override
568             public void onAnimationEnd(Animator animation) {
569                 child.setTag(TAG_ANIMATOR_TRANSLATION_Z, null);
570                 child.setTag(TAG_START_TRANSLATION_Z, null);
571                 child.setTag(TAG_END_TRANSLATION_Z, null);
572             }
573         });
574         startAnimator(animator);
575         child.setTag(TAG_ANIMATOR_TRANSLATION_Z, animator);
576         child.setTag(TAG_START_TRANSLATION_Z, child.getTranslationZ());
577         child.setTag(TAG_END_TRANSLATION_Z, newEndValue);
578     }
579 
startYTranslationAnimation(final ExpandableView child, StackScrollState.ViewState viewState, long duration, long delay)580     private void startYTranslationAnimation(final ExpandableView child,
581             StackScrollState.ViewState viewState, long duration, long delay) {
582         Float previousStartValue = getChildTag(child,TAG_START_TRANSLATION_Y);
583         Float previousEndValue = getChildTag(child,TAG_END_TRANSLATION_Y);
584         float newEndValue = viewState.yTranslation;
585         if (previousEndValue != null && previousEndValue == newEndValue) {
586             return;
587         }
588         ObjectAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_TRANSLATION_Y);
589         if (!mAnimationFilter.animateY) {
590             // just a local update was performed
591             if (previousAnimator != null) {
592                 // we need to increase all animation keyframes of the previous animator by the
593                 // relative change to the end value
594                 PropertyValuesHolder[] values = previousAnimator.getValues();
595                 float relativeDiff = newEndValue - previousEndValue;
596                 float newStartValue = previousStartValue + relativeDiff;
597                 values[0].setFloatValues(newStartValue, newEndValue);
598                 child.setTag(TAG_START_TRANSLATION_Y, newStartValue);
599                 child.setTag(TAG_END_TRANSLATION_Y, newEndValue);
600                 previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
601                 return;
602             } else {
603                 // no new animation needed, let's just apply the value
604                 child.setTranslationY(newEndValue);
605                 return;
606             }
607         }
608 
609         ObjectAnimator animator = ObjectAnimator.ofFloat(child, View.TRANSLATION_Y,
610                 child.getTranslationY(), newEndValue);
611         animator.setInterpolator(mFastOutSlowInInterpolator);
612         long newDuration = cancelAnimatorAndGetNewDuration(duration, previousAnimator);
613         animator.setDuration(newDuration);
614         if (delay > 0 && (previousAnimator == null || !previousAnimator.isRunning())) {
615             animator.setStartDelay(delay);
616         }
617         animator.addListener(getGlobalAnimationFinishedListener());
618         // remove the tag when the animation is finished
619         animator.addListener(new AnimatorListenerAdapter() {
620             @Override
621             public void onAnimationEnd(Animator animation) {
622                 child.setTag(TAG_ANIMATOR_TRANSLATION_Y, null);
623                 child.setTag(TAG_START_TRANSLATION_Y, null);
624                 child.setTag(TAG_END_TRANSLATION_Y, null);
625             }
626         });
627         startAnimator(animator);
628         child.setTag(TAG_ANIMATOR_TRANSLATION_Y, animator);
629         child.setTag(TAG_START_TRANSLATION_Y, child.getTranslationY());
630         child.setTag(TAG_END_TRANSLATION_Y, newEndValue);
631     }
632 
startScaleAnimation(final ExpandableView child, StackScrollState.ViewState viewState, long duration)633     private void startScaleAnimation(final ExpandableView child,
634             StackScrollState.ViewState viewState, long duration) {
635         Float previousStartValue = getChildTag(child, TAG_START_SCALE);
636         Float previousEndValue = getChildTag(child, TAG_END_SCALE);
637         float newEndValue = viewState.scale;
638         if (previousEndValue != null && previousEndValue == newEndValue) {
639             return;
640         }
641         ObjectAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_SCALE);
642         if (!mAnimationFilter.animateScale) {
643             // just a local update was performed
644             if (previousAnimator != null) {
645                 // we need to increase all animation keyframes of the previous animator by the
646                 // relative change to the end value
647                 PropertyValuesHolder[] values = previousAnimator.getValues();
648                 float relativeDiff = newEndValue - previousEndValue;
649                 float newStartValue = previousStartValue + relativeDiff;
650                 values[0].setFloatValues(newStartValue, newEndValue);
651                 values[1].setFloatValues(newStartValue, newEndValue);
652                 child.setTag(TAG_START_SCALE, newStartValue);
653                 child.setTag(TAG_END_SCALE, newEndValue);
654                 previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
655                 return;
656             } else {
657                 // no new animation needed, let's just apply the value
658                 child.setScaleX(newEndValue);
659                 child.setScaleY(newEndValue);
660             }
661         }
662 
663         PropertyValuesHolder holderX =
664                 PropertyValuesHolder.ofFloat(View.SCALE_X, child.getScaleX(), newEndValue);
665         PropertyValuesHolder holderY =
666                 PropertyValuesHolder.ofFloat(View.SCALE_Y, child.getScaleY(), newEndValue);
667         ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(child, holderX, holderY);
668         animator.setInterpolator(mFastOutSlowInInterpolator);
669         long newDuration = cancelAnimatorAndGetNewDuration(duration, previousAnimator);
670         animator.setDuration(newDuration);
671         animator.addListener(getGlobalAnimationFinishedListener());
672         // remove the tag when the animation is finished
673         animator.addListener(new AnimatorListenerAdapter() {
674             @Override
675             public void onAnimationEnd(Animator animation) {
676                 child.setTag(TAG_ANIMATOR_SCALE, null);
677                 child.setTag(TAG_START_SCALE, null);
678                 child.setTag(TAG_END_SCALE, null);
679             }
680         });
681         startAnimator(animator);
682         child.setTag(TAG_ANIMATOR_SCALE, animator);
683         child.setTag(TAG_START_SCALE, child.getScaleX());
684         child.setTag(TAG_END_SCALE, newEndValue);
685     }
686 
startAnimator(ValueAnimator animator)687     private void startAnimator(ValueAnimator animator) {
688         mAnimatorSet.add(animator);
689         animator.start();
690     }
691 
692     /**
693      * @return an adapter which ensures that onAnimationFinished is called once no animation is
694      *         running anymore
695      */
getGlobalAnimationFinishedListener()696     private AnimatorListenerAdapter getGlobalAnimationFinishedListener() {
697         if (!mAnimationListenerPool.empty()) {
698             return mAnimationListenerPool.pop();
699         }
700 
701         // We need to create a new one, no reusable ones found
702         return new AnimatorListenerAdapter() {
703             private boolean mWasCancelled;
704 
705             @Override
706             public void onAnimationEnd(Animator animation) {
707                 mAnimatorSet.remove(animation);
708                 if (mAnimatorSet.isEmpty() && !mWasCancelled) {
709                     onAnimationFinished();
710                 }
711                 mAnimationListenerPool.push(this);
712             }
713 
714             @Override
715             public void onAnimationCancel(Animator animation) {
716                 mWasCancelled = true;
717             }
718 
719             @Override
720             public void onAnimationStart(Animator animation) {
721                 mWasCancelled = false;
722             }
723         };
724     }
725 
getChildTag(View child, int tag)726     private static <T> T getChildTag(View child, int tag) {
727         return (T) child.getTag(tag);
728     }
729 
730     /**
731      * Cancel the previous animator and get the duration of the new animation.
732      *
733      * @param duration the new duration
734      * @param previousAnimator the animator which was running before
735      * @return the new duration
736      */
cancelAnimatorAndGetNewDuration(long duration, ValueAnimator previousAnimator)737     private long cancelAnimatorAndGetNewDuration(long duration, ValueAnimator previousAnimator) {
738         long newDuration = duration;
739         if (previousAnimator != null) {
740             // We take either the desired length of the new animation or the remaining time of
741             // the previous animator, whichever is longer.
742             newDuration = Math.max(previousAnimator.getDuration()
743                     - previousAnimator.getCurrentPlayTime(), newDuration);
744             previousAnimator.cancel();
745         }
746         return newDuration;
747     }
748 
onAnimationFinished()749     private void onAnimationFinished() {
750         mHostLayout.onChildAnimationFinished();
751     }
752 
753     /**
754      * Process the animationEvents for a new animation
755      *
756      * @param animationEvents the animation events for the animation to perform
757      * @param finalState the final state to animate to
758      */
processAnimationEvents( ArrayList<NotificationStackScrollLayout.AnimationEvent> animationEvents, StackScrollState finalState)759     private void processAnimationEvents(
760             ArrayList<NotificationStackScrollLayout.AnimationEvent> animationEvents,
761             StackScrollState finalState) {
762         for (NotificationStackScrollLayout.AnimationEvent event : animationEvents) {
763             final ExpandableView changingView = (ExpandableView) event.changingView;
764             if (event.animationType ==
765                     NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_ADD) {
766 
767                 // This item is added, initialize it's properties.
768                 StackScrollState.ViewState viewState = finalState
769                         .getViewStateForView(changingView);
770                 if (viewState == null) {
771                     // The position for this child was never generated, let's continue.
772                     continue;
773                 }
774                 if (changingView.getVisibility() == View.GONE) {
775                     // The view was set to gone but the state never removed
776                     finalState.removeViewStateForView(changingView);
777                     continue;
778                 }
779                 changingView.setAlpha(viewState.alpha);
780                 changingView.setTranslationY(viewState.yTranslation);
781                 changingView.setTranslationZ(viewState.zTranslation);
782                 changingView.setActualHeight(viewState.height, false);
783                 mNewAddChildren.add(changingView);
784 
785             } else if (event.animationType ==
786                     NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_REMOVE) {
787                 if (changingView.getVisibility() == View.GONE) {
788                     mHostLayout.getOverlay().remove(changingView);
789                     continue;
790                 }
791 
792                 // Find the amount to translate up. This is needed in order to understand the
793                 // direction of the remove animation (either downwards or upwards)
794                 StackScrollState.ViewState viewState = finalState
795                         .getViewStateForView(event.viewAfterChangingView);
796                 int actualHeight = changingView.getActualHeight();
797                 // upwards by default
798                 float translationDirection = -1.0f;
799                 if (viewState != null) {
800                     // there was a view after this one, Approximate the distance the next child
801                     // travelled
802                     translationDirection = ((viewState.yTranslation
803                             - (changingView.getTranslationY() + actualHeight / 2.0f)) * 2 /
804                             actualHeight);
805                     translationDirection = Math.max(Math.min(translationDirection, 1.0f),-1.0f);
806 
807                 }
808                 changingView.performRemoveAnimation(ANIMATION_DURATION_APPEAR_DISAPPEAR,
809                         translationDirection, new Runnable() {
810                     @Override
811                     public void run() {
812                         // remove the temporary overlay
813                         mHostLayout.getOverlay().remove(changingView);
814                     }
815                 });
816             }  else if (event.animationType ==
817                 NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_REMOVE_SWIPED_OUT) {
818                 // A race condition can trigger the view to be added to the overlay even though
819                 // it is swiped out. So let's remove it
820                 mHostLayout.getOverlay().remove(changingView);
821             }
822             mNewEvents.add(event);
823         }
824     }
825 
animateOverScrollToAmount(float targetAmount, final boolean onTop, final boolean isRubberbanded)826     public void animateOverScrollToAmount(float targetAmount, final boolean onTop,
827             final boolean isRubberbanded) {
828         final float startOverScrollAmount = mHostLayout.getCurrentOverScrollAmount(onTop);
829         if (targetAmount == startOverScrollAmount) {
830             return;
831         }
832         cancelOverScrollAnimators(onTop);
833         ValueAnimator overScrollAnimator = ValueAnimator.ofFloat(startOverScrollAmount,
834                 targetAmount);
835         overScrollAnimator.setDuration(ANIMATION_DURATION_STANDARD);
836         overScrollAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
837             @Override
838             public void onAnimationUpdate(ValueAnimator animation) {
839                 float currentOverScroll = (float) animation.getAnimatedValue();
840                 mHostLayout.setOverScrollAmount(
841                         currentOverScroll, onTop, false /* animate */, false /* cancelAnimators */,
842                         isRubberbanded);
843             }
844         });
845         overScrollAnimator.setInterpolator(mFastOutSlowInInterpolator);
846         overScrollAnimator.addListener(new AnimatorListenerAdapter() {
847             @Override
848             public void onAnimationEnd(Animator animation) {
849                 if (onTop) {
850                     mTopOverScrollAnimator = null;
851                 } else {
852                     mBottomOverScrollAnimator = null;
853                 }
854             }
855         });
856         overScrollAnimator.start();
857         if (onTop) {
858             mTopOverScrollAnimator = overScrollAnimator;
859         } else {
860             mBottomOverScrollAnimator = overScrollAnimator;
861         }
862     }
863 
cancelOverScrollAnimators(boolean onTop)864     public void cancelOverScrollAnimators(boolean onTop) {
865         ValueAnimator currentAnimator = onTop ? mTopOverScrollAnimator : mBottomOverScrollAnimator;
866         if (currentAnimator != null) {
867             currentAnimator.cancel();
868         }
869     }
870 
871     /**
872      * Get the end value of the height animation running on a view or the actualHeight
873      * if no animation is running.
874      */
getFinalActualHeight(ExpandableView view)875     public static int getFinalActualHeight(ExpandableView view) {
876         if (view == null) {
877             return 0;
878         }
879         ValueAnimator heightAnimator = getChildTag(view, TAG_ANIMATOR_HEIGHT);
880         if (heightAnimator == null) {
881             return view.getActualHeight();
882         } else {
883             return getChildTag(view, TAG_END_HEIGHT);
884         }
885     }
886 }
887