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 
17 package com.android.systemui.statusbar.stack;
18 
19 import android.animation.Animator;
20 import android.animation.AnimatorListenerAdapter;
21 import android.animation.ObjectAnimator;
22 import android.animation.PropertyValuesHolder;
23 import android.animation.ValueAnimator;
24 import android.app.Notification;
25 import android.util.Property;
26 import android.view.View;
27 import android.view.animation.Interpolator;
28 
29 import com.android.systemui.Interpolators;
30 import com.android.systemui.R;
31 import com.android.systemui.statusbar.ExpandableView;
32 import com.android.systemui.statusbar.NotificationShelf;
33 import com.android.systemui.statusbar.notification.PropertyAnimator;
34 import 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 */
41 public 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 
copyFrom(ViewState viewState)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 
initFrom(View view)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      */
applyToView(View view)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 
isAnimating(View view)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 
isAnimating(View view, int tag)250     private static boolean isAnimating(View view, int tag) {
251         return getChildTag(view, tag) != null;
252     }
253 
isAnimating(View view, PropertyAnimator.AnimatableProperty property)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      */
animateTo(View child, AnimationProperties animationProperties)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 
updateAlphaAnimation(View view)320     private void updateAlphaAnimation(View view) {
321         startAlphaAnimation(view, NO_NEW_ANIMATIONS);
322     }
323 
startAlphaAnimation(final View child, AnimationProperties properties)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 
updateAnimationZ(View view)402     private void updateAnimationZ(View view) {
403         startZTranslationAnimation(view, NO_NEW_ANIMATIONS);
404     }
405 
updateAnimation(View view, PropertyAnimator.AnimatableProperty property, float endValue)406     private void updateAnimation(View view, PropertyAnimator.AnimatableProperty property,
407             float endValue) {
408         PropertyAnimator.startAnimation(view, property, endValue, NO_NEW_ANIMATIONS);
409     }
410 
startZTranslationAnimation(final View child, AnimationProperties properties)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 
updateAnimationX(View view)467     private void updateAnimationX(View view) {
468         startXTranslationAnimation(view, NO_NEW_ANIMATIONS);
469     }
470 
startXTranslationAnimation(final View child, AnimationProperties properties)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 
updateAnimationY(View view)532     private void updateAnimationY(View view) {
533         startYTranslationAnimation(view, NO_NEW_ANIMATIONS);
534     }
535 
startYTranslationAnimation(final View child, AnimationProperties properties)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 
onYTranslationAnimationFinished(View view)599     protected void onYTranslationAnimationFinished(View view) {
600         if (hidden && !gone) {
601             view.setVisibility(View.INVISIBLE);
602         }
603     }
604 
startAnimator(Animator animator, AnimatorListenerAdapter listener)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 
getChildTag(View child, int tag)613     public static <T> T getChildTag(View child, int tag) {
614         return (T) child.getTag(tag);
615     }
616 
abortAnimation(View child, int animatorTag)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      */
cancelAnimatorAndGetNewDuration(long duration, ValueAnimator previousAnimator)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      */
getFinalTranslationY(View view)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      */
getFinalTranslationZ(View view)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 
isAnimatingY(View child)676     public static boolean isAnimatingY(View child) {
677         return getChildTag(child, TAG_ANIMATOR_TRANSLATION_Y) != null;
678     }
679 
cancelAnimations(View view)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