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