1/*
2 * Copyright (C) 2015 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.app.Notification;
25import android.util.Property;
26import android.view.View;
27import android.view.animation.Interpolator;
28
29import com.android.systemui.Interpolators;
30import com.android.systemui.R;
31import com.android.systemui.statusbar.ExpandableView;
32import com.android.systemui.statusbar.NotificationShelf;
33import com.android.systemui.statusbar.notification.PropertyAnimator;
34import com.android.systemui.statusbar.policy.HeadsUpManager;
35
36/**
37 * A state of a view. This can be used to apply a set of view properties to a view with
38 * {@link com.android.systemui.statusbar.stack.StackScrollState} or start animations with
39 * {@link com.android.systemui.statusbar.stack.StackStateAnimator}.
40*/
41public class ViewState {
42
43    /**
44     * Some animation properties that can be used to update running animations but not creating
45     * any new ones.
46     */
47    protected static final AnimationProperties NO_NEW_ANIMATIONS = new AnimationProperties() {
48        AnimationFilter mAnimationFilter = new AnimationFilter();
49        @Override
50        public AnimationFilter getAnimationFilter() {
51            return mAnimationFilter;
52        }
53    };
54    private static final int TAG_ANIMATOR_TRANSLATION_X = R.id.translation_x_animator_tag;
55    private static final int TAG_ANIMATOR_TRANSLATION_Y = R.id.translation_y_animator_tag;
56    private static final int TAG_ANIMATOR_TRANSLATION_Z = R.id.translation_z_animator_tag;
57    private static final int TAG_ANIMATOR_ALPHA = R.id.alpha_animator_tag;
58    private static final int TAG_END_TRANSLATION_X = R.id.translation_x_animator_end_value_tag;
59    private static final int TAG_END_TRANSLATION_Y = R.id.translation_y_animator_end_value_tag;
60    private static final int TAG_END_TRANSLATION_Z = R.id.translation_z_animator_end_value_tag;
61    private static final int TAG_END_ALPHA = R.id.alpha_animator_end_value_tag;
62    private static final int TAG_START_TRANSLATION_X = R.id.translation_x_animator_start_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_ALPHA = R.id.alpha_animator_start_value_tag;
66
67    private static final PropertyAnimator.AnimatableProperty SCALE_X_PROPERTY
68            = new PropertyAnimator.AnimatableProperty() {
69
70        @Override
71        public int getAnimationStartTag() {
72            return R.id.scale_x_animator_start_value_tag;
73        }
74
75        @Override
76        public int getAnimationEndTag() {
77            return R.id.scale_x_animator_end_value_tag;
78        }
79
80        @Override
81        public int getAnimatorTag() {
82            return R.id.scale_x_animator_tag;
83        }
84
85        @Override
86        public Property getProperty() {
87            return View.SCALE_X;
88        }
89    };
90
91    private static final PropertyAnimator.AnimatableProperty SCALE_Y_PROPERTY
92            = new PropertyAnimator.AnimatableProperty() {
93
94        @Override
95        public int getAnimationStartTag() {
96            return R.id.scale_y_animator_start_value_tag;
97        }
98
99        @Override
100        public int getAnimationEndTag() {
101            return R.id.scale_y_animator_end_value_tag;
102        }
103
104        @Override
105        public int getAnimatorTag() {
106            return R.id.scale_y_animator_tag;
107        }
108
109        @Override
110        public Property getProperty() {
111            return View.SCALE_Y;
112        }
113    };
114
115    public float alpha;
116    public float xTranslation;
117    public float yTranslation;
118    public float zTranslation;
119    public boolean gone;
120    public boolean hidden;
121    public float scaleX = 1.0f;
122    public float scaleY = 1.0f;
123
124    public void copyFrom(ViewState viewState) {
125        alpha = viewState.alpha;
126        xTranslation = viewState.xTranslation;
127        yTranslation = viewState.yTranslation;
128        zTranslation = viewState.zTranslation;
129        gone = viewState.gone;
130        hidden = viewState.hidden;
131        scaleX = viewState.scaleX;
132        scaleY = viewState.scaleY;
133    }
134
135    public void initFrom(View view) {
136        alpha = view.getAlpha();
137        xTranslation = view.getTranslationX();
138        yTranslation = view.getTranslationY();
139        zTranslation = view.getTranslationZ();
140        gone = view.getVisibility() == View.GONE;
141        hidden = view.getVisibility() == View.INVISIBLE;
142        scaleX = view.getScaleX();
143        scaleY = view.getScaleY();
144    }
145
146    /**
147     * Applies a {@link ViewState} to a normal view.
148     */
149    public void applyToView(View view) {
150        if (this.gone) {
151            // don't do anything with it
152            return;
153        }
154
155        // apply xTranslation
156        boolean animatingX = isAnimating(view, TAG_ANIMATOR_TRANSLATION_X);
157        if (animatingX) {
158            updateAnimationX(view);
159        } else if (view.getTranslationX() != this.xTranslation){
160            view.setTranslationX(this.xTranslation);
161        }
162
163        // apply yTranslation
164        boolean animatingY = isAnimating(view, TAG_ANIMATOR_TRANSLATION_Y);
165        if (animatingY) {
166            updateAnimationY(view);
167        } else if (view.getTranslationY() != this.yTranslation) {
168            view.setTranslationY(this.yTranslation);
169        }
170
171        // apply zTranslation
172        boolean animatingZ = isAnimating(view, TAG_ANIMATOR_TRANSLATION_Z);
173        if (animatingZ) {
174            updateAnimationZ(view);
175        } else if (view.getTranslationZ() != this.zTranslation) {
176            view.setTranslationZ(this.zTranslation);
177        }
178
179        // apply scaleX
180        boolean animatingScaleX = isAnimating(view, SCALE_X_PROPERTY);
181        if (animatingScaleX) {
182            updateAnimation(view, SCALE_X_PROPERTY, scaleX);
183        } else if (view.getScaleX() != scaleX) {
184            view.setScaleX(scaleX);
185        }
186
187        // apply scaleY
188        boolean animatingScaleY = isAnimating(view, SCALE_Y_PROPERTY);
189        if (animatingScaleY) {
190            updateAnimation(view, SCALE_Y_PROPERTY, scaleY);
191        } else if (view.getScaleY() != scaleY) {
192            view.setScaleY(scaleY);
193        }
194
195        int oldVisibility = view.getVisibility();
196        boolean becomesInvisible = this.alpha == 0.0f
197                || (this.hidden && (!isAnimating(view) || oldVisibility != View.VISIBLE));
198        boolean animatingAlpha = isAnimating(view, TAG_ANIMATOR_ALPHA);
199        if (animatingAlpha) {
200            updateAlphaAnimation(view);
201        } else if (view.getAlpha() != this.alpha) {
202            // apply layer type
203            boolean becomesFullyVisible = this.alpha == 1.0f;
204            boolean newLayerTypeIsHardware = !becomesInvisible && !becomesFullyVisible
205                    && view.hasOverlappingRendering();
206            int layerType = view.getLayerType();
207            int newLayerType = newLayerTypeIsHardware
208                    ? View.LAYER_TYPE_HARDWARE
209                    : View.LAYER_TYPE_NONE;
210            if (layerType != newLayerType) {
211                view.setLayerType(newLayerType, null);
212            }
213
214            // apply alpha
215            view.setAlpha(this.alpha);
216        }
217
218        // apply visibility
219        int newVisibility = becomesInvisible ? View.INVISIBLE : View.VISIBLE;
220        if (newVisibility != oldVisibility) {
221            if (!(view instanceof ExpandableView) || !((ExpandableView) view).willBeGone()) {
222                // We don't want views to change visibility when they are animating to GONE
223                view.setVisibility(newVisibility);
224            }
225        }
226    }
227
228    public boolean isAnimating(View view) {
229        if (isAnimating(view, TAG_ANIMATOR_TRANSLATION_X)) {
230            return true;
231        }
232        if (isAnimating(view, TAG_ANIMATOR_TRANSLATION_Y)) {
233            return true;
234        }
235        if (isAnimating(view, TAG_ANIMATOR_TRANSLATION_Z)) {
236            return true;
237        }
238        if (isAnimating(view, TAG_ANIMATOR_ALPHA)) {
239            return true;
240        }
241        if (isAnimating(view, SCALE_X_PROPERTY)) {
242            return true;
243        }
244        if (isAnimating(view, SCALE_Y_PROPERTY)) {
245            return true;
246        }
247        return false;
248    }
249
250    private static boolean isAnimating(View view, int tag) {
251        return getChildTag(view, tag) != null;
252    }
253
254    public static boolean isAnimating(View view, PropertyAnimator.AnimatableProperty property) {
255        return getChildTag(view, property.getAnimatorTag()) != null;
256    }
257
258    /**
259     * Start an animation to this viewstate
260     * @param child the view to animate
261     * @param animationProperties the properties of the animation
262     */
263    public void animateTo(View child, AnimationProperties animationProperties) {
264        boolean wasVisible = child.getVisibility() == View.VISIBLE;
265        final float alpha = this.alpha;
266        if (!wasVisible && (alpha != 0 || child.getAlpha() != 0)
267                && !this.gone && !this.hidden) {
268            child.setVisibility(View.VISIBLE);
269        }
270        float childAlpha = child.getAlpha();
271        boolean alphaChanging = this.alpha != childAlpha;
272        if (child instanceof ExpandableView) {
273            // We don't want views to change visibility when they are animating to GONE
274            alphaChanging &= !((ExpandableView) child).willBeGone();
275        }
276
277        // start translationX animation
278        if (child.getTranslationX() != this.xTranslation) {
279            startXTranslationAnimation(child, animationProperties);
280        } else {
281            abortAnimation(child, TAG_ANIMATOR_TRANSLATION_X);
282        }
283
284        // start translationY animation
285        if (child.getTranslationY() != this.yTranslation) {
286            startYTranslationAnimation(child, animationProperties);
287        } else {
288            abortAnimation(child, TAG_ANIMATOR_TRANSLATION_Y);
289        }
290
291        // start translationZ animation
292        if (child.getTranslationZ() != this.zTranslation) {
293            startZTranslationAnimation(child, animationProperties);
294        } else {
295            abortAnimation(child, TAG_ANIMATOR_TRANSLATION_Z);
296        }
297
298        // start scaleX animation
299        if (child.getScaleX() != scaleX) {
300            PropertyAnimator.startAnimation(child, SCALE_X_PROPERTY, scaleX, animationProperties);
301        } else {
302            abortAnimation(child, SCALE_X_PROPERTY.getAnimatorTag());
303        }
304
305        // start scaleX animation
306        if (child.getScaleY() != scaleY) {
307            PropertyAnimator.startAnimation(child, SCALE_Y_PROPERTY, scaleY, animationProperties);
308        } else {
309            abortAnimation(child, SCALE_Y_PROPERTY.getAnimatorTag());
310        }
311
312        // start alpha animation
313        if (alphaChanging) {
314            startAlphaAnimation(child, animationProperties);
315        }  else {
316            abortAnimation(child, TAG_ANIMATOR_ALPHA);
317        }
318    }
319
320    private void updateAlphaAnimation(View view) {
321        startAlphaAnimation(view, NO_NEW_ANIMATIONS);
322    }
323
324    private void startAlphaAnimation(final View child, AnimationProperties properties) {
325        Float previousStartValue = getChildTag(child,TAG_START_ALPHA);
326        Float previousEndValue = getChildTag(child,TAG_END_ALPHA);
327        final float newEndValue = this.alpha;
328        if (previousEndValue != null && previousEndValue == newEndValue) {
329            return;
330        }
331        ObjectAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_ALPHA);
332        AnimationFilter filter = properties.getAnimationFilter();
333        if (!filter.animateAlpha) {
334            // just a local update was performed
335            if (previousAnimator != null) {
336                // we need to increase all animation keyframes of the previous animator by the
337                // relative change to the end value
338                PropertyValuesHolder[] values = previousAnimator.getValues();
339                float relativeDiff = newEndValue - previousEndValue;
340                float newStartValue = previousStartValue + relativeDiff;
341                values[0].setFloatValues(newStartValue, newEndValue);
342                child.setTag(TAG_START_ALPHA, newStartValue);
343                child.setTag(TAG_END_ALPHA, newEndValue);
344                previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
345                return;
346            } else {
347                // no new animation needed, let's just apply the value
348                child.setAlpha(newEndValue);
349                if (newEndValue == 0) {
350                    child.setVisibility(View.INVISIBLE);
351                }
352            }
353        }
354
355        ObjectAnimator animator = ObjectAnimator.ofFloat(child, View.ALPHA,
356                child.getAlpha(), newEndValue);
357        animator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
358        // Handle layer type
359        child.setLayerType(View.LAYER_TYPE_HARDWARE, null);
360        animator.addListener(new AnimatorListenerAdapter() {
361            public boolean mWasCancelled;
362
363            @Override
364            public void onAnimationEnd(Animator animation) {
365                child.setLayerType(View.LAYER_TYPE_NONE, null);
366                if (newEndValue == 0 && !mWasCancelled) {
367                    child.setVisibility(View.INVISIBLE);
368                }
369                // remove the tag when the animation is finished
370                child.setTag(TAG_ANIMATOR_ALPHA, null);
371                child.setTag(TAG_START_ALPHA, null);
372                child.setTag(TAG_END_ALPHA, null);
373            }
374
375            @Override
376            public void onAnimationCancel(Animator animation) {
377                mWasCancelled = true;
378            }
379
380            @Override
381            public void onAnimationStart(Animator animation) {
382                mWasCancelled = false;
383            }
384        });
385        long newDuration = cancelAnimatorAndGetNewDuration(properties.duration, previousAnimator);
386        animator.setDuration(newDuration);
387        if (properties.delay > 0 && (previousAnimator == null
388                || previousAnimator.getAnimatedFraction() == 0)) {
389            animator.setStartDelay(properties.delay);
390        }
391        AnimatorListenerAdapter listener = properties.getAnimationFinishListener();
392        if (listener != null) {
393            animator.addListener(listener);
394        }
395
396        startAnimator(animator, listener);
397        child.setTag(TAG_ANIMATOR_ALPHA, animator);
398        child.setTag(TAG_START_ALPHA, child.getAlpha());
399        child.setTag(TAG_END_ALPHA, newEndValue);
400    }
401
402    private void updateAnimationZ(View view) {
403        startZTranslationAnimation(view, NO_NEW_ANIMATIONS);
404    }
405
406    private void updateAnimation(View view, PropertyAnimator.AnimatableProperty property,
407            float endValue) {
408        PropertyAnimator.startAnimation(view, property, endValue, NO_NEW_ANIMATIONS);
409    }
410
411    private void startZTranslationAnimation(final View child, AnimationProperties properties) {
412        Float previousStartValue = getChildTag(child,TAG_START_TRANSLATION_Z);
413        Float previousEndValue = getChildTag(child,TAG_END_TRANSLATION_Z);
414        float newEndValue = this.zTranslation;
415        if (previousEndValue != null && previousEndValue == newEndValue) {
416            return;
417        }
418        ObjectAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_TRANSLATION_Z);
419        AnimationFilter filter = properties.getAnimationFilter();
420        if (!filter.animateZ) {
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_TRANSLATION_Z, newStartValue);
430                child.setTag(TAG_END_TRANSLATION_Z, newEndValue);
431                previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
432                return;
433            } else {
434                // no new animation needed, let's just apply the value
435                child.setTranslationZ(newEndValue);
436            }
437        }
438
439        ObjectAnimator animator = ObjectAnimator.ofFloat(child, View.TRANSLATION_Z,
440                child.getTranslationZ(), newEndValue);
441        animator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
442        long newDuration = cancelAnimatorAndGetNewDuration(properties.duration, previousAnimator);
443        animator.setDuration(newDuration);
444        if (properties.delay > 0 && (previousAnimator == null
445                || previousAnimator.getAnimatedFraction() == 0)) {
446            animator.setStartDelay(properties.delay);
447        }
448        AnimatorListenerAdapter listener = properties.getAnimationFinishListener();
449        if (listener != null) {
450            animator.addListener(listener);
451        }
452        // remove the tag when the animation is finished
453        animator.addListener(new AnimatorListenerAdapter() {
454            @Override
455            public void onAnimationEnd(Animator animation) {
456                child.setTag(TAG_ANIMATOR_TRANSLATION_Z, null);
457                child.setTag(TAG_START_TRANSLATION_Z, null);
458                child.setTag(TAG_END_TRANSLATION_Z, null);
459            }
460        });
461        startAnimator(animator, listener);
462        child.setTag(TAG_ANIMATOR_TRANSLATION_Z, animator);
463        child.setTag(TAG_START_TRANSLATION_Z, child.getTranslationZ());
464        child.setTag(TAG_END_TRANSLATION_Z, newEndValue);
465    }
466
467    private void updateAnimationX(View view) {
468        startXTranslationAnimation(view, NO_NEW_ANIMATIONS);
469    }
470
471    private void startXTranslationAnimation(final View child, AnimationProperties properties) {
472        Float previousStartValue = getChildTag(child,TAG_START_TRANSLATION_X);
473        Float previousEndValue = getChildTag(child,TAG_END_TRANSLATION_X);
474        float newEndValue = this.xTranslation;
475        if (previousEndValue != null && previousEndValue == newEndValue) {
476            return;
477        }
478        ObjectAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_TRANSLATION_X);
479        AnimationFilter filter = properties.getAnimationFilter();
480        if (!filter.animateX) {
481            // just a local update was performed
482            if (previousAnimator != null) {
483                // we need to increase all animation keyframes of the previous animator by the
484                // relative change to the end value
485                PropertyValuesHolder[] values = previousAnimator.getValues();
486                float relativeDiff = newEndValue - previousEndValue;
487                float newStartValue = previousStartValue + relativeDiff;
488                values[0].setFloatValues(newStartValue, newEndValue);
489                child.setTag(TAG_START_TRANSLATION_X, newStartValue);
490                child.setTag(TAG_END_TRANSLATION_X, newEndValue);
491                previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
492                return;
493            } else {
494                // no new animation needed, let's just apply the value
495                child.setTranslationX(newEndValue);
496                return;
497            }
498        }
499
500        ObjectAnimator animator = ObjectAnimator.ofFloat(child, View.TRANSLATION_X,
501                child.getTranslationX(), newEndValue);
502        Interpolator customInterpolator = properties.getCustomInterpolator(child,
503                View.TRANSLATION_X);
504        Interpolator interpolator =  customInterpolator != null ? customInterpolator
505                : Interpolators.FAST_OUT_SLOW_IN;
506        animator.setInterpolator(interpolator);
507        long newDuration = cancelAnimatorAndGetNewDuration(properties.duration, previousAnimator);
508        animator.setDuration(newDuration);
509        if (properties.delay > 0 && (previousAnimator == null
510                || previousAnimator.getAnimatedFraction() == 0)) {
511            animator.setStartDelay(properties.delay);
512        }
513        AnimatorListenerAdapter listener = properties.getAnimationFinishListener();
514        if (listener != null) {
515            animator.addListener(listener);
516        }
517        // remove the tag when the animation is finished
518        animator.addListener(new AnimatorListenerAdapter() {
519            @Override
520            public void onAnimationEnd(Animator animation) {
521                child.setTag(TAG_ANIMATOR_TRANSLATION_X, null);
522                child.setTag(TAG_START_TRANSLATION_X, null);
523                child.setTag(TAG_END_TRANSLATION_X, null);
524            }
525        });
526        startAnimator(animator, listener);
527        child.setTag(TAG_ANIMATOR_TRANSLATION_X, animator);
528        child.setTag(TAG_START_TRANSLATION_X, child.getTranslationX());
529        child.setTag(TAG_END_TRANSLATION_X, newEndValue);
530    }
531
532    private void updateAnimationY(View view) {
533        startYTranslationAnimation(view, NO_NEW_ANIMATIONS);
534    }
535
536    private void startYTranslationAnimation(final View child, AnimationProperties properties) {
537        Float previousStartValue = getChildTag(child,TAG_START_TRANSLATION_Y);
538        Float previousEndValue = getChildTag(child,TAG_END_TRANSLATION_Y);
539        float newEndValue = this.yTranslation;
540        if (previousEndValue != null && previousEndValue == newEndValue) {
541            return;
542        }
543        ObjectAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_TRANSLATION_Y);
544        AnimationFilter filter = properties.getAnimationFilter();
545        if (!filter.shouldAnimateY(child)) {
546            // just a local update was performed
547            if (previousAnimator != null) {
548                // we need to increase all animation keyframes of the previous animator by the
549                // relative change to the end value
550                PropertyValuesHolder[] values = previousAnimator.getValues();
551                float relativeDiff = newEndValue - previousEndValue;
552                float newStartValue = previousStartValue + relativeDiff;
553                values[0].setFloatValues(newStartValue, newEndValue);
554                child.setTag(TAG_START_TRANSLATION_Y, newStartValue);
555                child.setTag(TAG_END_TRANSLATION_Y, newEndValue);
556                previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
557                return;
558            } else {
559                // no new animation needed, let's just apply the value
560                child.setTranslationY(newEndValue);
561                return;
562            }
563        }
564
565        ObjectAnimator animator = ObjectAnimator.ofFloat(child, View.TRANSLATION_Y,
566                child.getTranslationY(), newEndValue);
567        Interpolator customInterpolator = properties.getCustomInterpolator(child,
568                View.TRANSLATION_Y);
569        Interpolator interpolator =  customInterpolator != null ? customInterpolator
570                : Interpolators.FAST_OUT_SLOW_IN;
571        animator.setInterpolator(interpolator);
572        long newDuration = cancelAnimatorAndGetNewDuration(properties.duration, previousAnimator);
573        animator.setDuration(newDuration);
574        if (properties.delay > 0 && (previousAnimator == null
575                || previousAnimator.getAnimatedFraction() == 0)) {
576            animator.setStartDelay(properties.delay);
577        }
578        AnimatorListenerAdapter listener = properties.getAnimationFinishListener();
579        if (listener != null) {
580            animator.addListener(listener);
581        }
582        // remove the tag when the animation is finished
583        animator.addListener(new AnimatorListenerAdapter() {
584            @Override
585            public void onAnimationEnd(Animator animation) {
586                HeadsUpManager.setIsClickedNotification(child, false);
587                child.setTag(TAG_ANIMATOR_TRANSLATION_Y, null);
588                child.setTag(TAG_START_TRANSLATION_Y, null);
589                child.setTag(TAG_END_TRANSLATION_Y, null);
590                onYTranslationAnimationFinished(child);
591            }
592        });
593        startAnimator(animator, listener);
594        child.setTag(TAG_ANIMATOR_TRANSLATION_Y, animator);
595        child.setTag(TAG_START_TRANSLATION_Y, child.getTranslationY());
596        child.setTag(TAG_END_TRANSLATION_Y, newEndValue);
597    }
598
599    protected void onYTranslationAnimationFinished(View view) {
600        if (hidden && !gone) {
601            view.setVisibility(View.INVISIBLE);
602        }
603    }
604
605    public static void startAnimator(Animator animator, AnimatorListenerAdapter listener) {
606        if (listener != null) {
607            // Even if there's a delay we'd want to notify it of the start immediately.
608            listener.onAnimationStart(animator);
609        }
610        animator.start();
611    }
612
613    public static <T> T getChildTag(View child, int tag) {
614        return (T) child.getTag(tag);
615    }
616
617    protected void abortAnimation(View child, int animatorTag) {
618        Animator previousAnimator = getChildTag(child, animatorTag);
619        if (previousAnimator != null) {
620            previousAnimator.cancel();
621        }
622    }
623
624    /**
625     * Cancel the previous animator and get the duration of the new animation.
626     *
627     * @param duration the new duration
628     * @param previousAnimator the animator which was running before
629     * @return the new duration
630     */
631    public static long cancelAnimatorAndGetNewDuration(long duration,
632            ValueAnimator previousAnimator) {
633        long newDuration = duration;
634        if (previousAnimator != null) {
635            // We take either the desired length of the new animation or the remaining time of
636            // the previous animator, whichever is longer.
637            newDuration = Math.max(previousAnimator.getDuration()
638                    - previousAnimator.getCurrentPlayTime(), newDuration);
639            previousAnimator.cancel();
640        }
641        return newDuration;
642    }
643
644    /**
645     * Get the end value of the yTranslation animation running on a view or the yTranslation
646     * if no animation is running.
647     */
648    public static float getFinalTranslationY(View view) {
649        if (view == null) {
650            return 0;
651        }
652        ValueAnimator yAnimator = getChildTag(view, TAG_ANIMATOR_TRANSLATION_Y);
653        if (yAnimator == null) {
654            return view.getTranslationY();
655        } else {
656            return getChildTag(view, TAG_END_TRANSLATION_Y);
657        }
658    }
659
660    /**
661     * Get the end value of the zTranslation animation running on a view or the zTranslation
662     * if no animation is running.
663     */
664    public static float getFinalTranslationZ(View view) {
665        if (view == null) {
666            return 0;
667        }
668        ValueAnimator zAnimator = getChildTag(view, TAG_ANIMATOR_TRANSLATION_Z);
669        if (zAnimator == null) {
670            return view.getTranslationZ();
671        } else {
672            return getChildTag(view, TAG_END_TRANSLATION_Z);
673        }
674    }
675
676    public static boolean isAnimatingY(View child) {
677        return getChildTag(child, TAG_ANIMATOR_TRANSLATION_Y) != null;
678    }
679
680    public void cancelAnimations(View view) {
681        Animator animator = getChildTag(view, TAG_ANIMATOR_TRANSLATION_X);
682        if (animator != null) {
683            animator.cancel();
684        }
685        animator = getChildTag(view, TAG_ANIMATOR_TRANSLATION_Y);
686        if (animator != null) {
687            animator.cancel();
688        }
689        animator = getChildTag(view, TAG_ANIMATOR_TRANSLATION_Z);
690        if (animator != null) {
691            animator.cancel();
692        }
693        animator = getChildTag(view, TAG_ANIMATOR_ALPHA);
694        if (animator != null) {
695            animator.cancel();
696        }
697    }
698}
699