StackStateAnimator.java revision 1efb240c1a0aeca9492cf8891794712adfdb1fa7
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;
30
31import java.util.ArrayList;
32import java.util.HashSet;
33import java.util.Set;
34import java.util.Stack;
35
36/**
37 * An stack state animator which handles animations to new StackScrollStates
38 */
39public class StackStateAnimator {
40
41    public static final int ANIMATION_DURATION_STANDARD = 360;
42    public static final int ANIMATION_DURATION_APPEAR_DISAPPEAR = 464;
43    public static final int ANIMATION_DURATION_DIMMED_ACTIVATED = 220;
44    public static final int ANIMATION_DELAY_PER_ELEMENT_INTERRUPTING = 80;
45    public static final int ANIMATION_DELAY_PER_ELEMENT_MANUAL = 32;
46    private static final int DELAY_EFFECT_MAX_INDEX_DIFFERENCE = 2;
47
48    private static final int TAG_ANIMATOR_TRANSLATION_Y = R.id.translation_y_animator_tag;
49    private static final int TAG_ANIMATOR_TRANSLATION_Z = R.id.translation_z_animator_tag;
50    private static final int TAG_ANIMATOR_SCALE = R.id.scale_animator_tag;
51    private static final int TAG_ANIMATOR_ALPHA = R.id.alpha_animator_tag;
52    private static final int TAG_ANIMATOR_HEIGHT = R.id.height_animator_tag;
53    private static final int TAG_ANIMATOR_TOP_INSET = R.id.top_inset_animator_tag;
54    private static final int TAG_END_TRANSLATION_Y = R.id.translation_y_animator_end_value_tag;
55    private static final int TAG_END_TRANSLATION_Z = R.id.translation_z_animator_end_value_tag;
56    private static final int TAG_END_SCALE = R.id.scale_animator_end_value_tag;
57    private static final int TAG_END_ALPHA = R.id.alpha_animator_end_value_tag;
58    private static final int TAG_END_HEIGHT = R.id.height_animator_end_value_tag;
59    private static final int TAG_END_TOP_INSET = R.id.top_inset_animator_end_value_tag;
60    private static final int TAG_START_TRANSLATION_Y = R.id.translation_y_animator_start_value_tag;
61    private static final int TAG_START_TRANSLATION_Z = R.id.translation_z_animator_start_value_tag;
62    private static final int TAG_START_SCALE = R.id.scale_animator_start_value_tag;
63    private static final int TAG_START_ALPHA = R.id.alpha_animator_start_value_tag;
64    private static final int TAG_START_HEIGHT = R.id.height_animator_start_value_tag;
65    private static final int TAG_START_TOP_INSET = R.id.top_inset_animator_start_value_tag;
66
67    private final Interpolator mFastOutSlowInInterpolator;
68    public NotificationStackScrollLayout mHostLayout;
69    private ArrayList<NotificationStackScrollLayout.AnimationEvent> mNewEvents =
70            new ArrayList<>();
71    private ArrayList<View> mNewAddChildren = new ArrayList<>();
72    private Set<Animator> mAnimatorSet = new HashSet<Animator>();
73    private Stack<AnimatorListenerAdapter> mAnimationListenerPool
74            = new Stack<AnimatorListenerAdapter>();
75    private AnimationFilter mAnimationFilter = new AnimationFilter();
76    private long mCurrentLength;
77
78    private ValueAnimator mTopOverScrollAnimator;
79    private ValueAnimator mBottomOverScrollAnimator;
80
81    public StackStateAnimator(NotificationStackScrollLayout hostLayout) {
82        mHostLayout = hostLayout;
83        mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(hostLayout.getContext(),
84                android.R.interpolator.fast_out_slow_in);
85    }
86
87    public boolean isRunning() {
88        return !mAnimatorSet.isEmpty();
89    }
90
91    public void startAnimationForEvents(
92            ArrayList<NotificationStackScrollLayout.AnimationEvent> mAnimationEvents,
93            StackScrollState finalState) {
94
95        processAnimationEvents(mAnimationEvents, finalState);
96
97        int childCount = mHostLayout.getChildCount();
98        mAnimationFilter.applyCombination(mNewEvents);
99        mCurrentLength = NotificationStackScrollLayout.AnimationEvent.combineLength(mNewEvents);
100        for (int i = 0; i < childCount; i++) {
101            final ExpandableView child = (ExpandableView) mHostLayout.getChildAt(i);
102
103            StackScrollState.ViewState viewState = finalState.getViewStateForView(child);
104            if (viewState == null || child.getVisibility() == View.GONE) {
105                continue;
106            }
107
108            child.setClipBounds(null);
109            startAnimations(child, viewState, finalState);
110        }
111        if (!isRunning()) {
112            // no child has preformed any animation, lets finish
113            onAnimationFinished();
114        }
115        mNewEvents.clear();
116        mNewAddChildren.clear();
117    }
118
119    /**
120     * Start an animation to the given viewState
121     */
122    private void startAnimations(final ExpandableView child, StackScrollState.ViewState viewState,
123            StackScrollState finalState) {
124        int childVisibility = child.getVisibility();
125        boolean wasVisible = childVisibility == View.VISIBLE;
126        final float alpha = viewState.alpha;
127        if (!wasVisible && alpha != 0 && !viewState.gone) {
128            child.setVisibility(View.VISIBLE);
129        }
130
131        boolean yTranslationChanging = child.getTranslationY() != viewState.yTranslation;
132        boolean zTranslationChanging = child.getTranslationZ() != viewState.zTranslation;
133        boolean scaleChanging = child.getScaleX() != viewState.scale;
134        boolean alphaChanging = alpha != child.getAlpha();
135        boolean heightChanging = viewState.height != child.getActualHeight();
136        boolean wasAdded = mNewAddChildren.contains(child);
137        boolean hasDelays = mAnimationFilter.hasDelays;
138        boolean isDelayRelevant = yTranslationChanging || zTranslationChanging || scaleChanging ||
139                alphaChanging || heightChanging;
140        long delay = 0;
141        if (hasDelays && isDelayRelevant || wasAdded) {
142            delay = calculateChildAnimationDelay(viewState, finalState);
143        }
144
145        // start translationY animation
146        if (yTranslationChanging) {
147            startYTranslationAnimation(child, viewState, delay);
148        }
149
150        // start translationZ animation
151        if (zTranslationChanging) {
152            startZTranslationAnimation(child, viewState, delay);
153        }
154
155        // start scale animation
156        if (scaleChanging) {
157            startScaleAnimation(child, viewState);
158        }
159
160        // start alpha animation
161        if (alphaChanging) {
162            startAlphaAnimation(child, viewState, delay);
163        }
164
165        // start height animation
166        if (heightChanging) {
167            startHeightAnimation(child, viewState, delay);
168        }
169
170        // start dimmed animation
171        child.setDimmed(viewState.dimmed, mAnimationFilter.animateDimmed);
172
173        if (wasAdded) {
174            child.performAddAnimation(delay);
175        }
176    }
177
178    private long calculateChildAnimationDelay(StackScrollState.ViewState viewState,
179            StackScrollState finalState) {
180        long minDelay = 0;
181        for (NotificationStackScrollLayout.AnimationEvent event : mNewEvents) {
182            long delayPerElement = ANIMATION_DELAY_PER_ELEMENT_INTERRUPTING;
183            switch (event.animationType) {
184                case NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_ADD: {
185                    int ownIndex = viewState.notGoneIndex;
186                    int changingIndex = finalState
187                            .getViewStateForView(event.changingView).notGoneIndex;
188                    int difference = Math.abs(ownIndex - changingIndex);
189                    difference = Math.max(0, Math.min(DELAY_EFFECT_MAX_INDEX_DIFFERENCE,
190                            difference - 1));
191                    long delay = (DELAY_EFFECT_MAX_INDEX_DIFFERENCE - difference) * delayPerElement;
192                    minDelay = Math.max(delay, minDelay);
193                    break;
194                }
195                case NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_REMOVE_SWIPED_OUT:
196                    delayPerElement = ANIMATION_DELAY_PER_ELEMENT_MANUAL;
197                case NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_REMOVE: {
198                    int ownIndex = viewState.notGoneIndex;
199                    boolean noNextView = event.viewAfterChangingView == null;
200                    View viewAfterChangingView = noNextView
201                            ? mHostLayout.getLastChildNotGone()
202                            : event.viewAfterChangingView;
203
204                    int nextIndex = finalState
205                            .getViewStateForView(viewAfterChangingView).notGoneIndex;
206                    if (ownIndex >= nextIndex) {
207                        // we only have the view afterwards
208                        ownIndex++;
209                    }
210                    int difference = Math.abs(ownIndex - nextIndex);
211                    difference = Math.max(0, Math.min(DELAY_EFFECT_MAX_INDEX_DIFFERENCE,
212                            difference - 1));
213                    long delay = difference * delayPerElement;
214                    minDelay = Math.max(delay, minDelay);
215                    break;
216                }
217                default:
218                    break;
219            }
220        }
221        return minDelay;
222    }
223
224    private void startHeightAnimation(final ExpandableView child,
225            StackScrollState.ViewState viewState, long delay) {
226        Integer previousStartValue = getChildTag(child, TAG_START_HEIGHT);
227        Integer previousEndValue = getChildTag(child, TAG_END_HEIGHT);
228        int newEndValue = viewState.height;
229        if (previousEndValue != null && previousEndValue == newEndValue) {
230            return;
231        }
232        ValueAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_HEIGHT);
233        if (!mAnimationFilter.animateHeight) {
234            // just a local update was performed
235            if (previousAnimator != null) {
236                // we need to increase all animation keyframes of the previous animator by the
237                // relative change to the end value
238                PropertyValuesHolder[] values = previousAnimator.getValues();
239                int relativeDiff = newEndValue - previousEndValue;
240                int newStartValue = previousStartValue + relativeDiff;
241                values[0].setIntValues(newStartValue, newEndValue);
242                child.setTag(TAG_START_HEIGHT, newStartValue);
243                child.setTag(TAG_END_HEIGHT, newEndValue);
244                previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
245                return;
246            } else {
247                // no new animation needed, let's just apply the value
248                child.setActualHeight(newEndValue, false);
249                return;
250            }
251        }
252
253        ValueAnimator animator = ValueAnimator.ofInt(child.getActualHeight(), newEndValue);
254        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
255            @Override
256            public void onAnimationUpdate(ValueAnimator animation) {
257                child.setActualHeight((int) animation.getAnimatedValue(),
258                        false /* notifyListeners */);
259            }
260        });
261        animator.setInterpolator(mFastOutSlowInInterpolator);
262        long newDuration = cancelAnimatorAndGetNewDuration(previousAnimator);
263        animator.setDuration(newDuration);
264        if (delay > 0 && (previousAnimator == null || !previousAnimator.isRunning())) {
265            animator.setStartDelay(delay);
266        }
267        animator.addListener(getGlobalAnimationFinishedListener());
268        // remove the tag when the animation is finished
269        animator.addListener(new AnimatorListenerAdapter() {
270            @Override
271            public void onAnimationEnd(Animator animation) {
272                child.setTag(TAG_ANIMATOR_HEIGHT, null);
273                child.setTag(TAG_START_HEIGHT, null);
274                child.setTag(TAG_END_HEIGHT, null);
275            }
276        });
277        startAnimator(animator);
278        child.setTag(TAG_ANIMATOR_HEIGHT, animator);
279        child.setTag(TAG_START_HEIGHT, child.getActualHeight());
280        child.setTag(TAG_END_HEIGHT, newEndValue);
281    }
282
283    private void startAlphaAnimation(final ExpandableView child,
284            final StackScrollState.ViewState viewState, long delay) {
285        Float previousStartValue = getChildTag(child,TAG_START_ALPHA);
286        Float previousEndValue = getChildTag(child,TAG_END_ALPHA);
287        final float newEndValue = viewState.alpha;
288        if (previousEndValue != null && previousEndValue == newEndValue) {
289            return;
290        }
291        ObjectAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_ALPHA);
292        if (!mAnimationFilter.animateAlpha) {
293            // just a local update was performed
294            if (previousAnimator != null) {
295                // we need to increase all animation keyframes of the previous animator by the
296                // relative change to the end value
297                PropertyValuesHolder[] values = previousAnimator.getValues();
298                float relativeDiff = newEndValue - previousEndValue;
299                float newStartValue = previousStartValue + relativeDiff;
300                values[0].setFloatValues(newStartValue, newEndValue);
301                child.setTag(TAG_START_ALPHA, newStartValue);
302                child.setTag(TAG_END_ALPHA, newEndValue);
303                previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
304                return;
305            } else {
306                // no new animation needed, let's just apply the value
307                child.setAlpha(newEndValue);
308                if (newEndValue == 0) {
309                    child.setVisibility(View.INVISIBLE);
310                }
311            }
312        }
313
314        ObjectAnimator animator = ObjectAnimator.ofFloat(child, View.ALPHA,
315                child.getAlpha(), newEndValue);
316        animator.setInterpolator(mFastOutSlowInInterpolator);
317        // Handle layer type
318        child.setLayerType(View.LAYER_TYPE_HARDWARE, null);
319        animator.addListener(new AnimatorListenerAdapter() {
320            public boolean mWasCancelled;
321
322            @Override
323            public void onAnimationEnd(Animator animation) {
324                child.setLayerType(View.LAYER_TYPE_NONE, null);
325                if (newEndValue == 0 && !mWasCancelled) {
326                    child.setVisibility(View.INVISIBLE);
327                }
328                child.setTag(TAG_ANIMATOR_ALPHA, null);
329                child.setTag(TAG_START_ALPHA, null);
330                child.setTag(TAG_END_ALPHA, null);
331            }
332
333            @Override
334            public void onAnimationCancel(Animator animation) {
335                mWasCancelled = true;
336            }
337
338            @Override
339            public void onAnimationStart(Animator animation) {
340                mWasCancelled = false;
341            }
342        });
343        long newDuration = cancelAnimatorAndGetNewDuration(previousAnimator);
344        animator.setDuration(newDuration);
345        if (delay > 0 && (previousAnimator == null || !previousAnimator.isRunning())) {
346            animator.setStartDelay(delay);
347        }
348        animator.addListener(getGlobalAnimationFinishedListener());
349        // remove the tag when the animation is finished
350        animator.addListener(new AnimatorListenerAdapter() {
351            @Override
352            public void onAnimationEnd(Animator animation) {
353
354            }
355        });
356        startAnimator(animator);
357        child.setTag(TAG_ANIMATOR_ALPHA, animator);
358        child.setTag(TAG_START_ALPHA, child.getAlpha());
359        child.setTag(TAG_END_ALPHA, newEndValue);
360    }
361
362    private void startZTranslationAnimation(final ExpandableView child,
363            final StackScrollState.ViewState viewState, long delay) {
364        Float previousStartValue = getChildTag(child,TAG_START_TRANSLATION_Z);
365        Float previousEndValue = getChildTag(child,TAG_END_TRANSLATION_Z);
366        float newEndValue = viewState.zTranslation;
367        if (previousEndValue != null && previousEndValue == newEndValue) {
368            return;
369        }
370        ObjectAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_TRANSLATION_Z);
371        if (!mAnimationFilter.animateZ) {
372            // just a local update was performed
373            if (previousAnimator != null) {
374                // we need to increase all animation keyframes of the previous animator by the
375                // relative change to the end value
376                PropertyValuesHolder[] values = previousAnimator.getValues();
377                float relativeDiff = newEndValue - previousEndValue;
378                float newStartValue = previousStartValue + relativeDiff;
379                values[0].setFloatValues(newStartValue, newEndValue);
380                child.setTag(TAG_START_TRANSLATION_Z, newStartValue);
381                child.setTag(TAG_END_TRANSLATION_Z, newEndValue);
382                previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
383                return;
384            } else {
385                // no new animation needed, let's just apply the value
386                child.setTranslationZ(newEndValue);
387            }
388        }
389
390        ObjectAnimator animator = ObjectAnimator.ofFloat(child, View.TRANSLATION_Z,
391                child.getTranslationZ(), newEndValue);
392        animator.setInterpolator(mFastOutSlowInInterpolator);
393        long newDuration = cancelAnimatorAndGetNewDuration(previousAnimator);
394        animator.setDuration(newDuration);
395        if (delay > 0 && (previousAnimator == null || !previousAnimator.isRunning())) {
396            animator.setStartDelay(delay);
397        }
398        animator.addListener(getGlobalAnimationFinishedListener());
399        // remove the tag when the animation is finished
400        animator.addListener(new AnimatorListenerAdapter() {
401            @Override
402            public void onAnimationEnd(Animator animation) {
403                child.setTag(TAG_ANIMATOR_TRANSLATION_Z, null);
404                child.setTag(TAG_START_TRANSLATION_Z, null);
405                child.setTag(TAG_END_TRANSLATION_Z, null);
406            }
407        });
408        startAnimator(animator);
409        child.setTag(TAG_ANIMATOR_TRANSLATION_Z, animator);
410        child.setTag(TAG_START_TRANSLATION_Z, child.getTranslationZ());
411        child.setTag(TAG_END_TRANSLATION_Z, newEndValue);
412    }
413
414    private void startYTranslationAnimation(final ExpandableView child,
415            StackScrollState.ViewState viewState, long delay) {
416        Float previousStartValue = getChildTag(child,TAG_START_TRANSLATION_Y);
417        Float previousEndValue = getChildTag(child,TAG_END_TRANSLATION_Y);
418        float newEndValue = viewState.yTranslation;
419        if (previousEndValue != null && previousEndValue == newEndValue) {
420            return;
421        }
422        ObjectAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_TRANSLATION_Y);
423        if (!mAnimationFilter.animateY) {
424            // just a local update was performed
425            if (previousAnimator != null) {
426                // we need to increase all animation keyframes of the previous animator by the
427                // relative change to the end value
428                PropertyValuesHolder[] values = previousAnimator.getValues();
429                float relativeDiff = newEndValue - previousEndValue;
430                float newStartValue = previousStartValue + relativeDiff;
431                values[0].setFloatValues(newStartValue, newEndValue);
432                child.setTag(TAG_START_TRANSLATION_Y, newStartValue);
433                child.setTag(TAG_END_TRANSLATION_Y, newEndValue);
434                previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
435                return;
436            } else {
437                // no new animation needed, let's just apply the value
438                child.setTranslationY(newEndValue);
439                return;
440            }
441        }
442
443        ObjectAnimator animator = ObjectAnimator.ofFloat(child, View.TRANSLATION_Y,
444                child.getTranslationY(), newEndValue);
445        animator.setInterpolator(mFastOutSlowInInterpolator);
446        long newDuration = cancelAnimatorAndGetNewDuration(previousAnimator);
447        animator.setDuration(newDuration);
448        if (delay > 0 && (previousAnimator == null || !previousAnimator.isRunning())) {
449            animator.setStartDelay(delay);
450        }
451        animator.addListener(getGlobalAnimationFinishedListener());
452        // remove the tag when the animation is finished
453        animator.addListener(new AnimatorListenerAdapter() {
454            @Override
455            public void onAnimationEnd(Animator animation) {
456                child.setTag(TAG_ANIMATOR_TRANSLATION_Y, null);
457                child.setTag(TAG_START_TRANSLATION_Y, null);
458                child.setTag(TAG_END_TRANSLATION_Y, null);
459            }
460        });
461        startAnimator(animator);
462        child.setTag(TAG_ANIMATOR_TRANSLATION_Y, animator);
463        child.setTag(TAG_START_TRANSLATION_Y, child.getTranslationY());
464        child.setTag(TAG_END_TRANSLATION_Y, newEndValue);
465    }
466
467    private void startScaleAnimation(final ExpandableView child,
468            StackScrollState.ViewState viewState) {
469        Float previousStartValue = getChildTag(child, TAG_START_SCALE);
470        Float previousEndValue = getChildTag(child, TAG_END_SCALE);
471        float newEndValue = viewState.scale;
472        if (previousEndValue != null && previousEndValue == newEndValue) {
473            return;
474        }
475        ObjectAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_SCALE);
476        if (!mAnimationFilter.animateScale) {
477            // just a local update was performed
478            if (previousAnimator != null) {
479                // we need to increase all animation keyframes of the previous animator by the
480                // relative change to the end value
481                PropertyValuesHolder[] values = previousAnimator.getValues();
482                float relativeDiff = newEndValue - previousEndValue;
483                float newStartValue = previousStartValue + relativeDiff;
484                values[0].setFloatValues(newStartValue, newEndValue);
485                values[1].setFloatValues(newStartValue, newEndValue);
486                child.setTag(TAG_START_SCALE, newStartValue);
487                child.setTag(TAG_END_SCALE, newEndValue);
488                previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
489                return;
490            } else {
491                // no new animation needed, let's just apply the value
492                child.setScaleX(newEndValue);
493                child.setScaleY(newEndValue);
494            }
495        }
496
497        PropertyValuesHolder holderX =
498                PropertyValuesHolder.ofFloat(View.SCALE_X, child.getScaleX(), newEndValue);
499        PropertyValuesHolder holderY =
500                PropertyValuesHolder.ofFloat(View.SCALE_Y, child.getScaleY(), newEndValue);
501        ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(child, holderX, holderY);
502        animator.setInterpolator(mFastOutSlowInInterpolator);
503        long newDuration = cancelAnimatorAndGetNewDuration(previousAnimator);
504        animator.setDuration(newDuration);
505        animator.addListener(getGlobalAnimationFinishedListener());
506        // remove the tag when the animation is finished
507        animator.addListener(new AnimatorListenerAdapter() {
508            @Override
509            public void onAnimationEnd(Animator animation) {
510                child.setTag(TAG_ANIMATOR_SCALE, null);
511                child.setTag(TAG_START_SCALE, null);
512                child.setTag(TAG_END_SCALE, null);
513            }
514        });
515        startAnimator(animator);
516        child.setTag(TAG_ANIMATOR_SCALE, animator);
517        child.setTag(TAG_START_SCALE, child.getScaleX());
518        child.setTag(TAG_END_SCALE, newEndValue);
519    }
520
521    private void startAnimator(ValueAnimator animator) {
522        mAnimatorSet.add(animator);
523        animator.start();
524    }
525
526    /**
527     * @return an adapter which ensures that onAnimationFinished is called once no animation is
528     *         running anymore
529     */
530    private AnimatorListenerAdapter getGlobalAnimationFinishedListener() {
531        if (!mAnimationListenerPool.empty()) {
532            return mAnimationListenerPool.pop();
533        }
534
535        // We need to create a new one, no reusable ones found
536        return new AnimatorListenerAdapter() {
537            private boolean mWasCancelled;
538
539            @Override
540            public void onAnimationEnd(Animator animation) {
541                mAnimatorSet.remove(animation);
542                if (mAnimatorSet.isEmpty() && !mWasCancelled) {
543                    onAnimationFinished();
544                }
545                mAnimationListenerPool.push(this);
546            }
547
548            @Override
549            public void onAnimationCancel(Animator animation) {
550                mWasCancelled = true;
551            }
552
553            @Override
554            public void onAnimationStart(Animator animation) {
555                mWasCancelled = false;
556            }
557        };
558    }
559
560    private <T> T getChildTag(View child, int tag) {
561        return (T) child.getTag(tag);
562    }
563
564    /**
565     * Cancel the previous animator and get the duration of the new animation.
566     *
567     * @param previousAnimator the animator which was running before
568     * @return the new duration
569     */
570    private long cancelAnimatorAndGetNewDuration(ValueAnimator previousAnimator) {
571        long newDuration = mCurrentLength;
572        if (previousAnimator != null) {
573            // We take either the desired length of the new animation or the remaining time of
574            // the previous animator, whichever is longer.
575            newDuration = Math.max(previousAnimator.getDuration()
576                    - previousAnimator.getCurrentPlayTime(), newDuration);
577            previousAnimator.cancel();
578        }
579        return newDuration;
580    }
581
582    private void onAnimationFinished() {
583        mHostLayout.onChildAnimationFinished();
584    }
585
586    /**
587     * Process the animationEvents for a new animation
588     *
589     * @param animationEvents the animation events for the animation to perform
590     * @param finalState the final state to animate to
591     */
592    private void processAnimationEvents(
593            ArrayList<NotificationStackScrollLayout.AnimationEvent> animationEvents,
594            StackScrollState finalState) {
595        for (NotificationStackScrollLayout.AnimationEvent event : animationEvents) {
596            final ExpandableView changingView = (ExpandableView) event.changingView;
597            if (event.animationType ==
598                    NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_ADD) {
599
600                // This item is added, initialize it's properties.
601                StackScrollState.ViewState viewState = finalState
602                        .getViewStateForView(changingView);
603                if (viewState == null) {
604                    // The position for this child was never generated, let's continue.
605                    continue;
606                }
607                if (changingView.getVisibility() == View.GONE) {
608                    // The view was set to gone but the state never removed
609                    finalState.removeViewStateForView(changingView);
610                    continue;
611                }
612                changingView.setAlpha(viewState.alpha);
613                changingView.setTranslationY(viewState.yTranslation);
614                changingView.setTranslationZ(viewState.zTranslation);
615                changingView.setActualHeight(viewState.height, false);
616                mNewAddChildren.add(changingView);
617
618            } else if (event.animationType ==
619                    NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_REMOVE) {
620                if (changingView.getVisibility() == View.GONE) {
621                    continue;
622                }
623
624                // Find the amount to translate up. This is needed in order to understand the
625                // direction of the remove animation (either downwards or upwards)
626                StackScrollState.ViewState viewState = finalState
627                        .getViewStateForView(event.viewAfterChangingView);
628                int actualHeight = changingView.getActualHeight();
629                // upwards by default
630                float translationDirection = -1.0f;
631                if (viewState != null) {
632                    // there was a view after this one, Approximate the distance the next child
633                    // travelled
634                    translationDirection = ((viewState.yTranslation
635                            - (changingView.getTranslationY() + actualHeight / 2.0f)) * 2 /
636                            actualHeight);
637                    translationDirection = Math.max(Math.min(translationDirection, 1.0f),-1.0f);
638
639                }
640                changingView.performRemoveAnimation(translationDirection, new Runnable() {
641                    @Override
642                    public void run() {
643                        // remove the temporary overlay
644                        mHostLayout.getOverlay().remove(changingView);
645                    }
646                });
647            }
648            mNewEvents.add(event);
649        }
650    }
651
652    public void animateOverScrollToAmount(float targetAmount, final boolean onTop) {
653        final float startOverScrollAmount = mHostLayout.getCurrentOverScrollAmount(onTop);
654        cancelOverScrollAnimators(onTop);
655        ValueAnimator overScrollAnimator = ValueAnimator.ofFloat(startOverScrollAmount,
656                targetAmount);
657        overScrollAnimator.setDuration(ANIMATION_DURATION_STANDARD);
658        overScrollAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
659            @Override
660            public void onAnimationUpdate(ValueAnimator animation) {
661                float currentOverScroll = (float) animation.getAnimatedValue();
662                mHostLayout.setOverScrollAmount(currentOverScroll, onTop, false /* animate */,
663                        false /* cancelAnimators */);
664            }
665        });
666        overScrollAnimator.setInterpolator(mFastOutSlowInInterpolator);
667        overScrollAnimator.start();
668        if (onTop) {
669            mTopOverScrollAnimator = overScrollAnimator;
670        } else {
671            mBottomOverScrollAnimator = overScrollAnimator;
672        }
673    }
674
675    public void cancelOverScrollAnimators(boolean onTop) {
676        ValueAnimator currentAnimator = onTop ? mTopOverScrollAnimator : mBottomOverScrollAnimator;
677        if (currentAnimator != null) {
678            currentAnimator.cancel();
679        }
680    }
681}
682