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