StackStateAnimator.java revision 75c95044a8c5c073d30dcc9bd21157939f161043
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_DIMMED_ACTIVATED = 220;
43
44    private static final int TAG_ANIMATOR_TRANSLATION_Y = R.id.translation_y_animator_tag;
45    private static final int TAG_ANIMATOR_TRANSLATION_Z = R.id.translation_z_animator_tag;
46    private static final int TAG_ANIMATOR_SCALE = R.id.scale_animator_tag;
47    private static final int TAG_ANIMATOR_ALPHA = R.id.alpha_animator_tag;
48    private static final int TAG_ANIMATOR_HEIGHT = R.id.height_animator_tag;
49    private static final int TAG_ANIMATOR_TOP_INSET = R.id.top_inset_animator_tag;
50    private static final int TAG_END_TRANSLATION_Y = R.id.translation_y_animator_end_value_tag;
51    private static final int TAG_END_TRANSLATION_Z = R.id.translation_z_animator_end_value_tag;
52    private static final int TAG_END_SCALE = R.id.scale_animator_end_value_tag;
53    private static final int TAG_END_ALPHA = R.id.alpha_animator_end_value_tag;
54    private static final int TAG_END_HEIGHT = R.id.height_animator_end_value_tag;
55    private static final int TAG_END_TOP_INSET = R.id.top_inset_animator_end_value_tag;
56    private static final int TAG_START_TRANSLATION_Y = R.id.translation_y_animator_start_value_tag;
57    private static final int TAG_START_TRANSLATION_Z = R.id.translation_z_animator_start_value_tag;
58    private static final int TAG_START_SCALE = R.id.scale_animator_start_value_tag;
59    private static final int TAG_START_ALPHA = R.id.alpha_animator_start_value_tag;
60    private static final int TAG_START_HEIGHT = R.id.height_animator_start_value_tag;
61    private static final int TAG_START_TOP_INSET = R.id.top_inset_animator_start_value_tag;
62
63    private final Interpolator mFastOutSlowInInterpolator;
64    public NotificationStackScrollLayout mHostLayout;
65    private ArrayList<NotificationStackScrollLayout.AnimationEvent> mHandledEvents =
66            new ArrayList<>();
67    private ArrayList<NotificationStackScrollLayout.AnimationEvent> mNewEvents =
68            new ArrayList<>();
69    private Set<Animator> mAnimatorSet = new HashSet<Animator>();
70    private Stack<AnimatorListenerAdapter> mAnimationListenerPool
71            = new Stack<AnimatorListenerAdapter>();
72    private AnimationFilter mAnimationFilter = new AnimationFilter();
73    private long mCurrentLength;
74
75    private ValueAnimator mTopOverScrollAnimator;
76    private ValueAnimator mBottomOverScrollAnimator;
77
78    public StackStateAnimator(NotificationStackScrollLayout hostLayout) {
79        mHostLayout = hostLayout;
80        mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(hostLayout.getContext(),
81                android.R.interpolator.fast_out_slow_in);
82    }
83
84    public boolean isRunning() {
85        return !mAnimatorSet.isEmpty();
86    }
87
88    public void startAnimationForEvents(
89            ArrayList<NotificationStackScrollLayout.AnimationEvent> mAnimationEvents,
90            StackScrollState finalState) {
91
92        processAnimationEvents(mAnimationEvents, finalState);
93
94        int childCount = mHostLayout.getChildCount();
95        mAnimationFilter.applyCombination(mNewEvents);
96        mCurrentLength = NotificationStackScrollLayout.AnimationEvent.combineLength(mNewEvents);
97        for (int i = 0; i < childCount; i++) {
98            final ExpandableView child = (ExpandableView) mHostLayout.getChildAt(i);
99            StackScrollState.ViewState viewState = finalState.getViewStateForView(child);
100            if (viewState == null) {
101                continue;
102            }
103
104            startAnimations(child, viewState);
105
106            child.setClipBounds(null);
107        }
108        if (!isRunning()) {
109            // no child has preformed any animation, lets finish
110            onAnimationFinished();
111        }
112    }
113
114    /**
115     * Start an animation to the given viewState
116     */
117    private void startAnimations(final ExpandableView child, StackScrollState.ViewState viewState) {
118        int childVisibility = child.getVisibility();
119        boolean wasVisible = childVisibility == View.VISIBLE;
120        final float alpha = viewState.alpha;
121        if (!wasVisible && alpha != 0 && !viewState.gone) {
122            child.setVisibility(View.VISIBLE);
123        }
124        // start translationY animation
125        if (child.getTranslationY() != viewState.yTranslation) {
126            startYTranslationAnimation(child, viewState);
127        }
128        // start translationZ animation
129        if (child.getTranslationZ() != viewState.zTranslation) {
130            startZTranslationAnimation(child, viewState);
131        }
132        // start scale animation
133        if (child.getScaleX() != viewState.scale) {
134            startScaleAnimation(child, viewState);
135        }
136        // start alpha animation
137        if (alpha != child.getAlpha()) {
138            startAlphaAnimation(child, viewState);
139        }
140        // start height animation
141        if (viewState.height != child.getActualHeight()) {
142            startHeightAnimation(child, viewState);
143        }
144        // start dimmed animation
145        child.setDimmed(viewState.dimmed, mAnimationFilter.animateDimmed);
146    }
147
148    private void startHeightAnimation(final ExpandableView child,
149            StackScrollState.ViewState viewState) {
150        Integer previousStartValue = getChildTag(child, TAG_START_HEIGHT);
151        Integer previousEndValue = getChildTag(child, TAG_END_HEIGHT);
152        int newEndValue = viewState.height;
153        if (previousEndValue != null && previousEndValue == newEndValue) {
154            return;
155        }
156        ValueAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_HEIGHT);
157        if (!mAnimationFilter.animateHeight) {
158            // just a local update was performed
159            if (previousAnimator != null) {
160                // we need to increase all animation keyframes of the previous animator by the
161                // relative change to the end value
162                PropertyValuesHolder[] values = previousAnimator.getValues();
163                int relativeDiff = newEndValue - previousEndValue;
164                int newStartValue = previousStartValue + relativeDiff;
165                values[0].setIntValues(newStartValue, newEndValue);
166                child.setTag(TAG_START_HEIGHT, newStartValue);
167                child.setTag(TAG_END_HEIGHT, newEndValue);
168                previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
169                return;
170            } else {
171                // no new animation needed, let's just apply the value
172                child.setActualHeight(newEndValue, false);
173                return;
174            }
175        }
176
177        ValueAnimator animator = ValueAnimator.ofInt(child.getActualHeight(), newEndValue);
178        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
179            @Override
180            public void onAnimationUpdate(ValueAnimator animation) {
181                child.setActualHeight((int) animation.getAnimatedValue(),
182                        false /* notifyListeners */);
183            }
184        });
185        animator.setInterpolator(mFastOutSlowInInterpolator);
186        long newDuration = cancelAnimatorAndGetNewDuration(previousAnimator);
187        animator.setDuration(newDuration);
188        animator.addListener(getGlobalAnimationFinishedListener());
189        // remove the tag when the animation is finished
190        animator.addListener(new AnimatorListenerAdapter() {
191            @Override
192            public void onAnimationEnd(Animator animation) {
193                child.setTag(TAG_ANIMATOR_HEIGHT, null);
194                child.setTag(TAG_START_HEIGHT, null);
195                child.setTag(TAG_END_HEIGHT, null);
196            }
197        });
198        startInstantly(animator);
199        child.setTag(TAG_ANIMATOR_HEIGHT, animator);
200        child.setTag(TAG_START_HEIGHT, child.getActualHeight());
201        child.setTag(TAG_END_HEIGHT, newEndValue);
202    }
203
204    private void startAlphaAnimation(final ExpandableView child,
205            final StackScrollState.ViewState viewState) {
206        Float previousStartValue = getChildTag(child,TAG_START_ALPHA);
207        Float previousEndValue = getChildTag(child,TAG_END_ALPHA);
208        final float newEndValue = viewState.alpha;
209        if (previousEndValue != null && previousEndValue == newEndValue) {
210            return;
211        }
212        ObjectAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_ALPHA);
213        if (!mAnimationFilter.animateAlpha) {
214            // just a local update was performed
215            if (previousAnimator != null) {
216                // we need to increase all animation keyframes of the previous animator by the
217                // relative change to the end value
218                PropertyValuesHolder[] values = previousAnimator.getValues();
219                float relativeDiff = newEndValue - previousEndValue;
220                float newStartValue = previousStartValue + relativeDiff;
221                values[0].setFloatValues(newStartValue, newEndValue);
222                child.setTag(TAG_START_ALPHA, newStartValue);
223                child.setTag(TAG_END_ALPHA, newEndValue);
224                previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
225                return;
226            } else {
227                // no new animation needed, let's just apply the value
228                child.setAlpha(newEndValue);
229                if (newEndValue == 0) {
230                    child.setVisibility(View.INVISIBLE);
231                }
232            }
233        }
234
235        ObjectAnimator animator = ObjectAnimator.ofFloat(child, View.ALPHA,
236                child.getAlpha(), newEndValue);
237        animator.setInterpolator(mFastOutSlowInInterpolator);
238        // Handle layer type
239        final int currentLayerType = child.getLayerType();
240        child.setLayerType(View.LAYER_TYPE_HARDWARE, null);
241        animator.addListener(new AnimatorListenerAdapter() {
242            public boolean mWasCancelled;
243
244            @Override
245            public void onAnimationEnd(Animator animation) {
246                child.setLayerType(currentLayerType, null);
247                if (newEndValue == 0 && !mWasCancelled) {
248                    child.setVisibility(View.INVISIBLE);
249                }
250                child.setTag(TAG_ANIMATOR_ALPHA, null);
251                child.setTag(TAG_START_ALPHA, null);
252                child.setTag(TAG_END_ALPHA, null);
253            }
254
255            @Override
256            public void onAnimationCancel(Animator animation) {
257                mWasCancelled = true;
258            }
259
260            @Override
261            public void onAnimationStart(Animator animation) {
262                mWasCancelled = false;
263            }
264        });
265        long newDuration = cancelAnimatorAndGetNewDuration(previousAnimator);
266        animator.setDuration(newDuration);
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
273            }
274        });
275        startInstantly(animator);
276        child.setTag(TAG_ANIMATOR_ALPHA, animator);
277        child.setTag(TAG_START_ALPHA, child.getAlpha());
278        child.setTag(TAG_END_ALPHA, newEndValue);
279    }
280
281    private void startZTranslationAnimation(final ExpandableView child,
282            final StackScrollState.ViewState viewState) {
283        Float previousStartValue = getChildTag(child,TAG_START_TRANSLATION_Z);
284        Float previousEndValue = getChildTag(child,TAG_END_TRANSLATION_Z);
285        float newEndValue = viewState.zTranslation;
286        if (previousEndValue != null && previousEndValue == newEndValue) {
287            return;
288        }
289        ObjectAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_TRANSLATION_Z);
290        if (!mAnimationFilter.animateZ) {
291            // just a local update was performed
292            if (previousAnimator != null) {
293                // we need to increase all animation keyframes of the previous animator by the
294                // relative change to the end value
295                PropertyValuesHolder[] values = previousAnimator.getValues();
296                float relativeDiff = newEndValue - previousEndValue;
297                float newStartValue = previousStartValue + relativeDiff;
298                values[0].setFloatValues(newStartValue, newEndValue);
299                child.setTag(TAG_START_TRANSLATION_Z, newStartValue);
300                child.setTag(TAG_END_TRANSLATION_Z, newEndValue);
301                previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
302                return;
303            } else {
304                // no new animation needed, let's just apply the value
305                child.setTranslationZ(newEndValue);
306            }
307        }
308
309        ObjectAnimator animator = ObjectAnimator.ofFloat(child, View.TRANSLATION_Z,
310                child.getTranslationZ(), newEndValue);
311        animator.setInterpolator(mFastOutSlowInInterpolator);
312        long newDuration = cancelAnimatorAndGetNewDuration(previousAnimator);
313        animator.setDuration(newDuration);
314        animator.addListener(getGlobalAnimationFinishedListener());
315        // remove the tag when the animation is finished
316        animator.addListener(new AnimatorListenerAdapter() {
317            @Override
318            public void onAnimationEnd(Animator animation) {
319                child.setTag(TAG_ANIMATOR_TRANSLATION_Z, null);
320                child.setTag(TAG_START_TRANSLATION_Z, null);
321                child.setTag(TAG_END_TRANSLATION_Z, null);
322            }
323        });
324        startInstantly(animator);
325        child.setTag(TAG_ANIMATOR_TRANSLATION_Z, animator);
326        child.setTag(TAG_START_TRANSLATION_Z, child.getTranslationZ());
327        child.setTag(TAG_END_TRANSLATION_Z, newEndValue);
328    }
329
330    private void startYTranslationAnimation(final ExpandableView child,
331            StackScrollState.ViewState viewState) {
332        Float previousStartValue = getChildTag(child,TAG_START_TRANSLATION_Y);
333        Float previousEndValue = getChildTag(child,TAG_END_TRANSLATION_Y);
334        float newEndValue = viewState.yTranslation;
335        if (previousEndValue != null && previousEndValue == newEndValue) {
336            return;
337        }
338        ObjectAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_TRANSLATION_Y);
339        if (!mAnimationFilter.animateY) {
340            // just a local update was performed
341            if (previousAnimator != null) {
342                // we need to increase all animation keyframes of the previous animator by the
343                // relative change to the end value
344                PropertyValuesHolder[] values = previousAnimator.getValues();
345                float relativeDiff = newEndValue - previousEndValue;
346                float newStartValue = previousStartValue + relativeDiff;
347                values[0].setFloatValues(newStartValue, newEndValue);
348                child.setTag(TAG_START_TRANSLATION_Y, newStartValue);
349                child.setTag(TAG_END_TRANSLATION_Y, newEndValue);
350                previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
351                return;
352            } else {
353                // no new animation needed, let's just apply the value
354                child.setTranslationY(newEndValue);
355                return;
356            }
357        }
358
359        ObjectAnimator animator = ObjectAnimator.ofFloat(child, View.TRANSLATION_Y,
360                child.getTranslationY(), newEndValue);
361        animator.setInterpolator(mFastOutSlowInInterpolator);
362        long newDuration = cancelAnimatorAndGetNewDuration(previousAnimator);
363        animator.setDuration(newDuration);
364        animator.addListener(getGlobalAnimationFinishedListener());
365        // remove the tag when the animation is finished
366        animator.addListener(new AnimatorListenerAdapter() {
367            @Override
368            public void onAnimationEnd(Animator animation) {
369                child.setTag(TAG_ANIMATOR_TRANSLATION_Y, null);
370                child.setTag(TAG_START_TRANSLATION_Y, null);
371                child.setTag(TAG_END_TRANSLATION_Y, null);
372            }
373        });
374        startInstantly(animator);
375        child.setTag(TAG_ANIMATOR_TRANSLATION_Y, animator);
376        child.setTag(TAG_START_TRANSLATION_Y, child.getTranslationY());
377        child.setTag(TAG_END_TRANSLATION_Y, newEndValue);
378    }
379
380    private void startScaleAnimation(final ExpandableView child,
381            StackScrollState.ViewState viewState) {
382        Float previousStartValue = getChildTag(child, TAG_START_SCALE);
383        Float previousEndValue = getChildTag(child, TAG_END_SCALE);
384        float newEndValue = viewState.scale;
385        if (previousEndValue != null && previousEndValue == newEndValue) {
386            return;
387        }
388        ObjectAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_SCALE);
389        if (!mAnimationFilter.animateScale) {
390            // just a local update was performed
391            if (previousAnimator != null) {
392                // we need to increase all animation keyframes of the previous animator by the
393                // relative change to the end value
394                PropertyValuesHolder[] values = previousAnimator.getValues();
395                float relativeDiff = newEndValue - previousEndValue;
396                float newStartValue = previousStartValue + relativeDiff;
397                values[0].setFloatValues(newStartValue, newEndValue);
398                values[1].setFloatValues(newStartValue, newEndValue);
399                child.setTag(TAG_START_SCALE, newStartValue);
400                child.setTag(TAG_END_SCALE, newEndValue);
401                previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
402                return;
403            } else {
404                // no new animation needed, let's just apply the value
405                child.setScaleX(newEndValue);
406                child.setScaleY(newEndValue);
407            }
408        }
409
410        PropertyValuesHolder holderX =
411                PropertyValuesHolder.ofFloat(View.SCALE_X, child.getScaleX(), newEndValue);
412        PropertyValuesHolder holderY =
413                PropertyValuesHolder.ofFloat(View.SCALE_Y, child.getScaleY(), newEndValue);
414        ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(child, holderX, holderY);
415        animator.setInterpolator(mFastOutSlowInInterpolator);
416        long newDuration = cancelAnimatorAndGetNewDuration(previousAnimator);
417        animator.setDuration(newDuration);
418        animator.addListener(getGlobalAnimationFinishedListener());
419        // remove the tag when the animation is finished
420        animator.addListener(new AnimatorListenerAdapter() {
421            @Override
422            public void onAnimationEnd(Animator animation) {
423                child.setTag(TAG_ANIMATOR_SCALE, null);
424                child.setTag(TAG_START_SCALE, null);
425                child.setTag(TAG_END_SCALE, null);
426            }
427        });
428        startInstantly(animator);
429        child.setTag(TAG_ANIMATOR_SCALE, animator);
430        child.setTag(TAG_START_SCALE, child.getScaleX());
431        child.setTag(TAG_END_SCALE, newEndValue);
432    }
433
434    /**
435     * Start an animator instantly instead of waiting on the next synchronization frame
436     */
437    public static void startInstantly(ValueAnimator animator) {
438        animator.start();
439        animator.setCurrentPlayTime(0);
440    }
441
442    /**
443     * @return an adapter which ensures that onAnimationFinished is called once no animation is
444     *         running anymore
445     */
446    private AnimatorListenerAdapter getGlobalAnimationFinishedListener() {
447        if (!mAnimationListenerPool.empty()) {
448            return mAnimationListenerPool.pop();
449        }
450
451        // We need to create a new one, no reusable ones found
452        return new AnimatorListenerAdapter() {
453            private boolean mWasCancelled;
454
455            @Override
456            public void onAnimationEnd(Animator animation) {
457                mAnimatorSet.remove(animation);
458                if (mAnimatorSet.isEmpty() && !mWasCancelled) {
459                    onAnimationFinished();
460                }
461                mAnimationListenerPool.push(this);
462            }
463
464            @Override
465            public void onAnimationCancel(Animator animation) {
466                mWasCancelled = true;
467            }
468
469            @Override
470            public void onAnimationStart(Animator animation) {
471                mAnimatorSet.add(animation);
472                mWasCancelled = false;
473            }
474        };
475    }
476
477    private <T> T getChildTag(View child, int tag) {
478        return (T) child.getTag(tag);
479    }
480
481    /**
482     * Cancel the previous animator and get the duration of the new animation.
483     *
484     * @param previousAnimator the animator which was running before
485     * @return the new duration
486     */
487    private long cancelAnimatorAndGetNewDuration(ValueAnimator previousAnimator) {
488        long newDuration = mCurrentLength;
489        if (previousAnimator != null) {
490            // We take either the desired length of the new animation or the remaining time of
491            // the previous animator, whichever is longer.
492            newDuration = Math.max(previousAnimator.getDuration()
493                    - previousAnimator.getCurrentPlayTime(), newDuration);
494            previousAnimator.cancel();
495        }
496        return newDuration;
497    }
498
499    private void onAnimationFinished() {
500        mHandledEvents.clear();
501        mNewEvents.clear();
502        mHostLayout.onChildAnimationFinished();
503    }
504
505    /**
506     * Process the animationEvents for a new animation
507     *
508     * @param animationEvents the animation events for the animation to perform
509     * @param finalState the final state to animate to
510     */
511    private void processAnimationEvents(
512            ArrayList<NotificationStackScrollLayout.AnimationEvent> animationEvents,
513            StackScrollState finalState) {
514        mNewEvents.clear();
515        for (NotificationStackScrollLayout.AnimationEvent event : animationEvents) {
516            View changingView = event.changingView;
517            if (!mHandledEvents.contains(event)) {
518                if (event.animationType == NotificationStackScrollLayout.AnimationEvent
519                        .ANIMATION_TYPE_ADD) {
520
521                    // This item is added, initialize it's properties.
522                    StackScrollState.ViewState viewState = finalState
523                            .getViewStateForView(changingView);
524                    if (viewState == null) {
525                        // The position for this child was never generated, let's continue.
526                        continue;
527                    }
528                    changingView.setAlpha(0);
529                    changingView.setTranslationY(viewState.yTranslation);
530                    changingView.setTranslationZ(viewState.zTranslation);
531                }
532                mHandledEvents.add(event);
533                mNewEvents.add(event);
534            }
535        }
536    }
537
538    public void animateOverScrollToAmount(float targetAmount, final boolean onTop) {
539        final float startOverScrollAmount = mHostLayout.getCurrentOverScrollAmount(onTop);
540        cancelOverScrollAnimators(onTop);
541        ValueAnimator overScrollAnimator = ValueAnimator.ofFloat(startOverScrollAmount,
542                targetAmount);
543        overScrollAnimator.setDuration(ANIMATION_DURATION_STANDARD);
544        overScrollAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
545            @Override
546            public void onAnimationUpdate(ValueAnimator animation) {
547                float currentOverScroll = (float) animation.getAnimatedValue();
548                mHostLayout.setOverScrollAmount(currentOverScroll, onTop, false /* animate */,
549                        false /* cancelAnimators */);
550            }
551        });
552        overScrollAnimator.setInterpolator(mFastOutSlowInInterpolator);
553        overScrollAnimator.start();
554        if (onTop) {
555            mTopOverScrollAnimator = overScrollAnimator;
556        } else {
557            mBottomOverScrollAnimator = overScrollAnimator;
558        }
559    }
560
561    public void cancelOverScrollAnimators(boolean onTop) {
562        ValueAnimator currentAnimator = onTop ? mTopOverScrollAnimator : mBottomOverScrollAnimator;
563        if (currentAnimator != null) {
564            currentAnimator.cancel();
565        }
566    }
567}
568