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