StackStateAnimator.java revision 2a5e452f4f70d2dd717946dd50486cc66ea4cc43
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
17package com.android.systemui.statusbar.stack;
18
19import android.animation.Animator;
20import android.animation.AnimatorListenerAdapter;
21import android.animation.ObjectAnimator;
22import android.animation.PropertyValuesHolder;
23import android.animation.ValueAnimator;
24import android.view.View;
25import android.view.animation.AnimationUtils;
26import android.view.animation.Interpolator;
27
28import com.android.systemui.R;
29import com.android.systemui.statusbar.ExpandableView;
30import com.android.systemui.statusbar.SpeedBumpView;
31
32import java.util.ArrayList;
33import java.util.HashSet;
34import java.util.Set;
35import java.util.Stack;
36
37/**
38 * An stack state animator which handles animations to new StackScrollStates
39 */
40public 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
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
98    public boolean isRunning() {
99        return !mAnimatorSet.isEmpty();
100    }
101
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
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     */
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
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
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
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
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
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
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
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
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
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
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     */
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
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     */
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
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     */
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
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
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     */
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