/* * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package com.android.systemui.statusbar.notification.stack; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ObjectAnimator; import android.animation.PropertyValuesHolder; import android.animation.ValueAnimator; import android.util.Log; import android.util.Property; import android.view.View; import android.view.animation.Interpolator; import com.android.systemui.Dumpable; import com.android.systemui.R; import com.android.systemui.animation.Interpolators; import com.android.systemui.statusbar.notification.AnimatableProperty; import com.android.systemui.statusbar.notification.NotificationFadeAware.FadeOptimizedNotification; import com.android.systemui.statusbar.notification.PropertyAnimator; import com.android.systemui.statusbar.notification.row.ExpandableView; import com.android.systemui.statusbar.policy.HeadsUpUtil; import java.io.PrintWriter; import java.lang.reflect.Field; import java.lang.reflect.Modifier; /** * A state of a view. This can be used to apply a set of view properties to a view with * {@link com.android.systemui.statusbar.notification.stack.StackScrollState} or start * animations with {@link com.android.systemui.statusbar.notification.stack.StackStateAnimator}. */ public class ViewState implements Dumpable { /** * Some animation properties that can be used to update running animations but not creating * any new ones. */ protected static final AnimationProperties NO_NEW_ANIMATIONS = new AnimationProperties() { AnimationFilter mAnimationFilter = new AnimationFilter(); @Override public AnimationFilter getAnimationFilter() { return mAnimationFilter; } }; private static final int TAG_ANIMATOR_TRANSLATION_X = R.id.translation_x_animator_tag; private static final int TAG_ANIMATOR_TRANSLATION_Y = R.id.translation_y_animator_tag; private static final int TAG_ANIMATOR_TRANSLATION_Z = R.id.translation_z_animator_tag; private static final int TAG_ANIMATOR_ALPHA = R.id.alpha_animator_tag; private static final int TAG_END_TRANSLATION_X = R.id.translation_x_animator_end_value_tag; private static final int TAG_END_TRANSLATION_Y = R.id.translation_y_animator_end_value_tag; private static final int TAG_END_TRANSLATION_Z = R.id.translation_z_animator_end_value_tag; private static final int TAG_END_ALPHA = R.id.alpha_animator_end_value_tag; private static final int TAG_START_TRANSLATION_X = R.id.translation_x_animator_start_value_tag; private static final int TAG_START_TRANSLATION_Y = R.id.translation_y_animator_start_value_tag; private static final int TAG_START_TRANSLATION_Z = R.id.translation_z_animator_start_value_tag; private static final int TAG_START_ALPHA = R.id.alpha_animator_start_value_tag; private static final String LOG_TAG = "StackViewState"; private static final AnimatableProperty SCALE_X_PROPERTY = new AnimatableProperty() { @Override public int getAnimationStartTag() { return R.id.scale_x_animator_start_value_tag; } @Override public int getAnimationEndTag() { return R.id.scale_x_animator_end_value_tag; } @Override public int getAnimatorTag() { return R.id.scale_x_animator_tag; } @Override public Property getProperty() { return View.SCALE_X; } }; private static final AnimatableProperty SCALE_Y_PROPERTY = new AnimatableProperty() { @Override public int getAnimationStartTag() { return R.id.scale_y_animator_start_value_tag; } @Override public int getAnimationEndTag() { return R.id.scale_y_animator_end_value_tag; } @Override public int getAnimatorTag() { return R.id.scale_y_animator_tag; } @Override public Property getProperty() { return View.SCALE_Y; } }; public boolean gone; public boolean hidden; private float mAlpha; private float mXTranslation; private float mYTranslation; private float mZTranslation; private float mScaleX = 1.0f; private float mScaleY = 1.0f; public float getAlpha() { return mAlpha; } /** * @param alpha View transparency. */ public void setAlpha(float alpha) { if (isValidFloat(alpha, "alpha")) { this.mAlpha = alpha; } } public float getXTranslation() { return mXTranslation; } /** * @param xTranslation x-axis translation value for the animation. */ public void setXTranslation(float xTranslation) { if (isValidFloat(xTranslation, "xTranslation")) { this.mXTranslation = xTranslation; } } public float getYTranslation() { return mYTranslation; } /** * @param yTranslation y-axis translation value for the animation. */ public void setYTranslation(float yTranslation) { if (isValidFloat(yTranslation, "yTranslation")) { this.mYTranslation = yTranslation; } } public float getZTranslation() { return mZTranslation; } /** * @param zTranslation z-axis translation value for the animation. */ public void setZTranslation(float zTranslation) { if (isValidFloat(zTranslation, "zTranslation")) { this.mZTranslation = zTranslation; } } public float getScaleX() { return mScaleX; } /** * @param scaleX x-axis scale property for the animation. */ public void setScaleX(float scaleX) { if (isValidFloat(scaleX, "scaleX")) { this.mScaleX = scaleX; } } public float getScaleY() { return mScaleY; } /** * @param scaleY y-axis scale property for the animation. */ public void setScaleY(float scaleY) { if (isValidFloat(scaleY, "scaleY")) { this.mScaleY = scaleY; } } /** * Checks if {@code value} is a valid float value. If it is not, logs it (using {@code name}) * and returns false. */ private boolean isValidFloat(float value, String name) { if (Float.isNaN(value)) { Log.wtf(LOG_TAG, "Cannot set property " + name + " to NaN"); return false; } return true; } public void copyFrom(ViewState viewState) { mAlpha = viewState.mAlpha; mXTranslation = viewState.mXTranslation; mYTranslation = viewState.mYTranslation; mZTranslation = viewState.mZTranslation; gone = viewState.gone; hidden = viewState.hidden; mScaleX = viewState.mScaleX; mScaleY = viewState.mScaleY; } public void initFrom(View view) { mAlpha = view.getAlpha(); mXTranslation = view.getTranslationX(); mYTranslation = view.getTranslationY(); mZTranslation = view.getTranslationZ(); gone = view.getVisibility() == View.GONE; hidden = view.getVisibility() == View.INVISIBLE; mScaleX = view.getScaleX(); mScaleY = view.getScaleY(); } /** * Applies a {@link ViewState} to a normal view. */ public void applyToView(View view) { if (this.gone) { // don't do anything with it return; } // apply xTranslation boolean animatingX = isAnimating(view, TAG_ANIMATOR_TRANSLATION_X); if (animatingX) { updateAnimationX(view); } else if (view.getTranslationX() != this.mXTranslation) { view.setTranslationX(this.mXTranslation); } // apply yTranslation boolean animatingY = isAnimating(view, TAG_ANIMATOR_TRANSLATION_Y); if (animatingY) { updateAnimationY(view); } else if (view.getTranslationY() != this.mYTranslation) { view.setTranslationY(this.mYTranslation); } // apply zTranslation boolean animatingZ = isAnimating(view, TAG_ANIMATOR_TRANSLATION_Z); if (animatingZ) { updateAnimationZ(view); } else if (view.getTranslationZ() != this.mZTranslation) { view.setTranslationZ(this.mZTranslation); } // apply scaleX boolean animatingScaleX = isAnimating(view, SCALE_X_PROPERTY); if (animatingScaleX) { updateAnimation(view, SCALE_X_PROPERTY, mScaleX); } else if (view.getScaleX() != mScaleX) { view.setScaleX(mScaleX); } // apply scaleY boolean animatingScaleY = isAnimating(view, SCALE_Y_PROPERTY); if (animatingScaleY) { updateAnimation(view, SCALE_Y_PROPERTY, mScaleY); } else if (view.getScaleY() != mScaleY) { view.setScaleY(mScaleY); } int oldVisibility = view.getVisibility(); boolean becomesInvisible = this.mAlpha == 0.0f || (this.hidden && (!isAnimating(view) || oldVisibility != View.VISIBLE)); boolean animatingAlpha = isAnimating(view, TAG_ANIMATOR_ALPHA); if (animatingAlpha) { updateAlphaAnimation(view); } else if (view.getAlpha() != this.mAlpha) { // apply layer type boolean becomesFullyVisible = this.mAlpha == 1.0f; boolean becomesFaded = !becomesInvisible && !becomesFullyVisible; if (FadeOptimizedNotification.FADE_LAYER_OPTIMIZATION_ENABLED && view instanceof FadeOptimizedNotification) { // NOTE: A view that's going to utilize this interface to avoid having a hardware // layer will have to return false from hasOverlappingRendering(), so we // intentionally do not check that value in this if, even though we do in the else. FadeOptimizedNotification fadeOptimizedView = (FadeOptimizedNotification) view; boolean isFaded = fadeOptimizedView.isNotificationFaded(); if (isFaded != becomesFaded) { fadeOptimizedView.setNotificationFaded(becomesFaded); } } else { boolean newLayerTypeIsHardware = becomesFaded && view.hasOverlappingRendering(); int layerType = view.getLayerType(); int newLayerType = newLayerTypeIsHardware ? View.LAYER_TYPE_HARDWARE : View.LAYER_TYPE_NONE; if (layerType != newLayerType) { view.setLayerType(newLayerType, null); } } // apply alpha view.setAlpha(this.mAlpha); } // apply visibility int newVisibility = becomesInvisible ? View.INVISIBLE : View.VISIBLE; if (newVisibility != oldVisibility) { if (!(view instanceof ExpandableView) || !((ExpandableView) view).willBeGone()) { // We don't want views to change visibility when they are animating to GONE view.setVisibility(newVisibility); } } } public boolean isAnimating(View view) { if (isAnimating(view, TAG_ANIMATOR_TRANSLATION_X)) { return true; } if (isAnimating(view, TAG_ANIMATOR_TRANSLATION_Y)) { return true; } if (isAnimating(view, TAG_ANIMATOR_TRANSLATION_Z)) { return true; } if (isAnimating(view, TAG_ANIMATOR_ALPHA)) { return true; } if (isAnimating(view, SCALE_X_PROPERTY)) { return true; } if (isAnimating(view, SCALE_Y_PROPERTY)) { return true; } return false; } private static boolean isAnimating(View view, int tag) { return getChildTag(view, tag) != null; } public static boolean isAnimating(View view, AnimatableProperty property) { return getChildTag(view, property.getAnimatorTag()) != null; } /** * Start an animation to this viewstate * * @param child the view to animate * @param animationProperties the properties of the animation */ public void animateTo(View child, AnimationProperties animationProperties) { boolean wasVisible = child.getVisibility() == View.VISIBLE; final float alpha = this.mAlpha; if (!wasVisible && (alpha != 0 || child.getAlpha() != 0) && !this.gone && !this.hidden) { child.setVisibility(View.VISIBLE); } float childAlpha = child.getAlpha(); boolean alphaChanging = this.mAlpha != childAlpha; if (child instanceof ExpandableView) { // We don't want views to change visibility when they are animating to GONE alphaChanging &= !((ExpandableView) child).willBeGone(); } // start translationX animation if (child.getTranslationX() != this.mXTranslation) { startXTranslationAnimation(child, animationProperties); } else { abortAnimation(child, TAG_ANIMATOR_TRANSLATION_X); } // start translationY animation if (child.getTranslationY() != this.mYTranslation) { startYTranslationAnimation(child, animationProperties); } else { abortAnimation(child, TAG_ANIMATOR_TRANSLATION_Y); } // start translationZ animation if (child.getTranslationZ() != this.mZTranslation) { startZTranslationAnimation(child, animationProperties); } else { abortAnimation(child, TAG_ANIMATOR_TRANSLATION_Z); } // start scaleX animation if (child.getScaleX() != mScaleX) { PropertyAnimator.startAnimation(child, SCALE_X_PROPERTY, mScaleX, animationProperties); } else { abortAnimation(child, SCALE_X_PROPERTY.getAnimatorTag()); } // start scaleX animation if (child.getScaleY() != mScaleY) { PropertyAnimator.startAnimation(child, SCALE_Y_PROPERTY, mScaleY, animationProperties); } else { abortAnimation(child, SCALE_Y_PROPERTY.getAnimatorTag()); } // start alpha animation if (alphaChanging) { startAlphaAnimation(child, animationProperties); } else { abortAnimation(child, TAG_ANIMATOR_ALPHA); } } private void updateAlphaAnimation(View view) { startAlphaAnimation(view, NO_NEW_ANIMATIONS); } private void startAlphaAnimation(final View child, AnimationProperties properties) { Float previousStartValue = getChildTag(child, TAG_START_ALPHA); Float previousEndValue = getChildTag(child, TAG_END_ALPHA); final float newEndValue = this.mAlpha; if (previousEndValue != null && previousEndValue == newEndValue) { return; } ObjectAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_ALPHA); AnimationFilter filter = properties.getAnimationFilter(); if (!filter.animateAlpha) { // just a local update was performed if (previousAnimator != null) { // we need to increase all animation keyframes of the previous animator by the // relative change to the end value PropertyValuesHolder[] values = previousAnimator.getValues(); float relativeDiff = newEndValue - previousEndValue; float newStartValue = previousStartValue + relativeDiff; values[0].setFloatValues(newStartValue, newEndValue); child.setTag(TAG_START_ALPHA, newStartValue); child.setTag(TAG_END_ALPHA, newEndValue); previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime()); return; } else { // no new animation needed, let's just apply the value child.setAlpha(newEndValue); if (newEndValue == 0) { child.setVisibility(View.INVISIBLE); } } } ObjectAnimator animator = ObjectAnimator.ofFloat(child, View.ALPHA, child.getAlpha(), newEndValue); animator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN); // Handle layer type child.setLayerType(View.LAYER_TYPE_HARDWARE, null); animator.addListener(new AnimatorListenerAdapter() { public boolean mWasCancelled; @Override public void onAnimationEnd(Animator animation) { child.setLayerType(View.LAYER_TYPE_NONE, null); if (newEndValue == 0 && !mWasCancelled) { child.setVisibility(View.INVISIBLE); } // remove the tag when the animation is finished child.setTag(TAG_ANIMATOR_ALPHA, null); child.setTag(TAG_START_ALPHA, null); child.setTag(TAG_END_ALPHA, null); } @Override public void onAnimationCancel(Animator animation) { mWasCancelled = true; } @Override public void onAnimationStart(Animator animation) { mWasCancelled = false; } }); long newDuration = cancelAnimatorAndGetNewDuration(properties.duration, previousAnimator); animator.setDuration(newDuration); if (properties.delay > 0 && (previousAnimator == null || previousAnimator.getAnimatedFraction() == 0)) { animator.setStartDelay(properties.delay); } AnimatorListenerAdapter listener = properties.getAnimationFinishListener(View.ALPHA); if (listener != null) { animator.addListener(listener); } startAnimator(animator, listener); child.setTag(TAG_ANIMATOR_ALPHA, animator); child.setTag(TAG_START_ALPHA, child.getAlpha()); child.setTag(TAG_END_ALPHA, newEndValue); } private void updateAnimationZ(View view) { startZTranslationAnimation(view, NO_NEW_ANIMATIONS); } private void updateAnimation(View view, AnimatableProperty property, float endValue) { PropertyAnimator.startAnimation(view, property, endValue, NO_NEW_ANIMATIONS); } private void startZTranslationAnimation(final View child, AnimationProperties properties) { Float previousStartValue = getChildTag(child, TAG_START_TRANSLATION_Z); Float previousEndValue = getChildTag(child, TAG_END_TRANSLATION_Z); float newEndValue = this.mZTranslation; if (previousEndValue != null && previousEndValue == newEndValue) { return; } ObjectAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_TRANSLATION_Z); AnimationFilter filter = properties.getAnimationFilter(); if (!filter.animateZ) { // just a local update was performed if (previousAnimator != null) { // we need to increase all animation keyframes of the previous animator by the // relative change to the end value PropertyValuesHolder[] values = previousAnimator.getValues(); float relativeDiff = newEndValue - previousEndValue; float newStartValue = previousStartValue + relativeDiff; values[0].setFloatValues(newStartValue, newEndValue); child.setTag(TAG_START_TRANSLATION_Z, newStartValue); child.setTag(TAG_END_TRANSLATION_Z, newEndValue); previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime()); return; } else { // no new animation needed, let's just apply the value child.setTranslationZ(newEndValue); } } ObjectAnimator animator = ObjectAnimator.ofFloat(child, View.TRANSLATION_Z, child.getTranslationZ(), newEndValue); animator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN); long newDuration = cancelAnimatorAndGetNewDuration(properties.duration, previousAnimator); animator.setDuration(newDuration); if (properties.delay > 0 && (previousAnimator == null || previousAnimator.getAnimatedFraction() == 0)) { animator.setStartDelay(properties.delay); } AnimatorListenerAdapter listener = properties.getAnimationFinishListener( View.TRANSLATION_Z); if (listener != null) { animator.addListener(listener); } // remove the tag when the animation is finished animator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { child.setTag(TAG_ANIMATOR_TRANSLATION_Z, null); child.setTag(TAG_START_TRANSLATION_Z, null); child.setTag(TAG_END_TRANSLATION_Z, null); } }); startAnimator(animator, listener); child.setTag(TAG_ANIMATOR_TRANSLATION_Z, animator); child.setTag(TAG_START_TRANSLATION_Z, child.getTranslationZ()); child.setTag(TAG_END_TRANSLATION_Z, newEndValue); } private void updateAnimationX(View view) { startXTranslationAnimation(view, NO_NEW_ANIMATIONS); } private void startXTranslationAnimation(final View child, AnimationProperties properties) { Float previousStartValue = getChildTag(child, TAG_START_TRANSLATION_X); Float previousEndValue = getChildTag(child, TAG_END_TRANSLATION_X); float newEndValue = this.mXTranslation; if (previousEndValue != null && previousEndValue == newEndValue) { return; } ObjectAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_TRANSLATION_X); AnimationFilter filter = properties.getAnimationFilter(); if (!filter.animateX) { // just a local update was performed if (previousAnimator != null) { // we need to increase all animation keyframes of the previous animator by the // relative change to the end value PropertyValuesHolder[] values = previousAnimator.getValues(); float relativeDiff = newEndValue - previousEndValue; float newStartValue = previousStartValue + relativeDiff; values[0].setFloatValues(newStartValue, newEndValue); child.setTag(TAG_START_TRANSLATION_X, newStartValue); child.setTag(TAG_END_TRANSLATION_X, newEndValue); previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime()); return; } else { // no new animation needed, let's just apply the value child.setTranslationX(newEndValue); return; } } ObjectAnimator animator = ObjectAnimator.ofFloat(child, View.TRANSLATION_X, child.getTranslationX(), newEndValue); Interpolator customInterpolator = properties.getCustomInterpolator(child, View.TRANSLATION_X); Interpolator interpolator = customInterpolator != null ? customInterpolator : Interpolators.FAST_OUT_SLOW_IN; animator.setInterpolator(interpolator); long newDuration = cancelAnimatorAndGetNewDuration(properties.duration, previousAnimator); animator.setDuration(newDuration); if (properties.delay > 0 && (previousAnimator == null || previousAnimator.getAnimatedFraction() == 0)) { animator.setStartDelay(properties.delay); } AnimatorListenerAdapter listener = properties.getAnimationFinishListener( View.TRANSLATION_X); if (listener != null) { animator.addListener(listener); } // remove the tag when the animation is finished animator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { child.setTag(TAG_ANIMATOR_TRANSLATION_X, null); child.setTag(TAG_START_TRANSLATION_X, null); child.setTag(TAG_END_TRANSLATION_X, null); } }); startAnimator(animator, listener); child.setTag(TAG_ANIMATOR_TRANSLATION_X, animator); child.setTag(TAG_START_TRANSLATION_X, child.getTranslationX()); child.setTag(TAG_END_TRANSLATION_X, newEndValue); } private void updateAnimationY(View view) { startYTranslationAnimation(view, NO_NEW_ANIMATIONS); } private void startYTranslationAnimation(final View child, AnimationProperties properties) { Float previousStartValue = getChildTag(child, TAG_START_TRANSLATION_Y); Float previousEndValue = getChildTag(child, TAG_END_TRANSLATION_Y); float newEndValue = this.mYTranslation; if (previousEndValue != null && previousEndValue == newEndValue) { return; } ObjectAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_TRANSLATION_Y); AnimationFilter filter = properties.getAnimationFilter(); if (!filter.shouldAnimateY(child)) { // just a local update was performed if (previousAnimator != null) { // we need to increase all animation keyframes of the previous animator by the // relative change to the end value PropertyValuesHolder[] values = previousAnimator.getValues(); float relativeDiff = newEndValue - previousEndValue; float newStartValue = previousStartValue + relativeDiff; values[0].setFloatValues(newStartValue, newEndValue); child.setTag(TAG_START_TRANSLATION_Y, newStartValue); child.setTag(TAG_END_TRANSLATION_Y, newEndValue); previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime()); return; } else { // no new animation needed, let's just apply the value child.setTranslationY(newEndValue); return; } } ObjectAnimator animator = ObjectAnimator.ofFloat(child, View.TRANSLATION_Y, child.getTranslationY(), newEndValue); Interpolator customInterpolator = properties.getCustomInterpolator(child, View.TRANSLATION_Y); Interpolator interpolator = customInterpolator != null ? customInterpolator : Interpolators.FAST_OUT_SLOW_IN; animator.setInterpolator(interpolator); long newDuration = cancelAnimatorAndGetNewDuration(properties.duration, previousAnimator); animator.setDuration(newDuration); if (properties.delay > 0 && (previousAnimator == null || previousAnimator.getAnimatedFraction() == 0)) { animator.setStartDelay(properties.delay); } AnimatorListenerAdapter listener = properties.getAnimationFinishListener( View.TRANSLATION_Y); if (listener != null) { animator.addListener(listener); } // remove the tag when the animation is finished animator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { HeadsUpUtil.setNeedsHeadsUpDisappearAnimationAfterClick(child, false); child.setTag(TAG_ANIMATOR_TRANSLATION_Y, null); child.setTag(TAG_START_TRANSLATION_Y, null); child.setTag(TAG_END_TRANSLATION_Y, null); onYTranslationAnimationFinished(child); } }); startAnimator(animator, listener); child.setTag(TAG_ANIMATOR_TRANSLATION_Y, animator); child.setTag(TAG_START_TRANSLATION_Y, child.getTranslationY()); child.setTag(TAG_END_TRANSLATION_Y, newEndValue); } protected void onYTranslationAnimationFinished(View view) { if (hidden && !gone) { view.setVisibility(View.INVISIBLE); } } public static void startAnimator(Animator animator, AnimatorListenerAdapter listener) { if (listener != null) { // Even if there's a delay we'd want to notify it of the start immediately. listener.onAnimationStart(animator); } animator.start(); } public static T getChildTag(View child, int tag) { return (T) child.getTag(tag); } protected void abortAnimation(View child, int animatorTag) { Animator previousAnimator = getChildTag(child, animatorTag); if (previousAnimator != null) { previousAnimator.cancel(); } } /** * Cancel the previous animator and get the duration of the new animation. * * @param duration the new duration * @param previousAnimator the animator which was running before * @return the new duration */ public static long cancelAnimatorAndGetNewDuration(long duration, ValueAnimator previousAnimator) { long newDuration = duration; if (previousAnimator != null) { // We take either the desired length of the new animation or the remaining time of // the previous animator, whichever is longer. newDuration = Math.max(previousAnimator.getDuration() - previousAnimator.getCurrentPlayTime(), newDuration); previousAnimator.cancel(); } return newDuration; } /** * Get the end value of the xTranslation animation running on a view or the xTranslation * if no animation is running. */ public static float getFinalTranslationX(View view) { if (view == null) { return 0; } ValueAnimator xAnimator = getChildTag(view, TAG_ANIMATOR_TRANSLATION_X); if (xAnimator == null) { return view.getTranslationX(); } else { return getChildTag(view, TAG_END_TRANSLATION_X); } } /** * Get the end value of the yTranslation animation running on a view or the yTranslation * if no animation is running. */ public static float getFinalTranslationY(View view) { if (view == null) { return 0; } ValueAnimator yAnimator = getChildTag(view, TAG_ANIMATOR_TRANSLATION_Y); if (yAnimator == null) { return view.getTranslationY(); } else { return getChildTag(view, TAG_END_TRANSLATION_Y); } } /** * Get the end value of the zTranslation animation running on a view or the zTranslation * if no animation is running. */ public static float getFinalTranslationZ(View view) { if (view == null) { return 0; } ValueAnimator zAnimator = getChildTag(view, TAG_ANIMATOR_TRANSLATION_Z); if (zAnimator == null) { return view.getTranslationZ(); } else { return getChildTag(view, TAG_END_TRANSLATION_Z); } } public static boolean isAnimatingY(View child) { return getChildTag(child, TAG_ANIMATOR_TRANSLATION_Y) != null; } public void cancelAnimations(View view) { Animator animator = getChildTag(view, TAG_ANIMATOR_TRANSLATION_X); if (animator != null) { animator.cancel(); } animator = getChildTag(view, TAG_ANIMATOR_TRANSLATION_Y); if (animator != null) { animator.cancel(); } animator = getChildTag(view, TAG_ANIMATOR_TRANSLATION_Z); if (animator != null) { animator.cancel(); } animator = getChildTag(view, TAG_ANIMATOR_ALPHA); if (animator != null) { animator.cancel(); } } @Override public void dump(PrintWriter pw, String[] args) { StringBuilder result = new StringBuilder(); result.append("ViewState { "); boolean first = true; Class currentClass = this.getClass(); while (currentClass != null) { Field[] fields = currentClass.getDeclaredFields(); // Print field names paired with their values for (Field field : fields) { int modifiers = field.getModifiers(); if (Modifier.isStatic(modifiers) || field.isSynthetic() || Modifier.isTransient(modifiers)) { continue; } if (!first) { result.append(", "); } try { result.append(field.getName()); result.append(": "); //requires access to private field: field.setAccessible(true); result.append(field.get(this)); } catch (IllegalAccessException ex) { } first = false; } currentClass = currentClass.getSuperclass(); } result.append(" }"); pw.print(result); } }