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