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