/* * 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.stack; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ObjectAnimator; import android.animation.PropertyValuesHolder; import android.animation.ValueAnimator; import android.app.Notification; import android.util.Property; import android.view.View; import android.view.animation.Interpolator; import com.android.systemui.Interpolators; import com.android.systemui.R; import com.android.systemui.statusbar.ExpandableView; import com.android.systemui.statusbar.NotificationShelf; import com.android.systemui.statusbar.notification.PropertyAnimator; import com.android.systemui.statusbar.policy.HeadsUpManager; /** * 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.stack.StackScrollState} or start animations with * {@link com.android.systemui.statusbar.stack.StackStateAnimator}. */ public class ViewState { /** * 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 PropertyAnimator.AnimatableProperty SCALE_X_PROPERTY = new PropertyAnimator.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 PropertyAnimator.AnimatableProperty SCALE_Y_PROPERTY = new PropertyAnimator.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 float alpha; public float xTranslation; public float yTranslation; public float zTranslation; public boolean gone; public boolean hidden; public float scaleX = 1.0f; public float scaleY = 1.0f; public void copyFrom(ViewState viewState) { alpha = viewState.alpha; xTranslation = viewState.xTranslation; yTranslation = viewState.yTranslation; zTranslation = viewState.zTranslation; gone = viewState.gone; hidden = viewState.hidden; scaleX = viewState.scaleX; scaleY = viewState.scaleY; } public void initFrom(View view) { alpha = view.getAlpha(); xTranslation = view.getTranslationX(); yTranslation = view.getTranslationY(); zTranslation = view.getTranslationZ(); gone = view.getVisibility() == View.GONE; hidden = view.getVisibility() == View.INVISIBLE; scaleX = view.getScaleX(); scaleY = 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.xTranslation){ view.setTranslationX(this.xTranslation); } // apply yTranslation boolean animatingY = isAnimating(view, TAG_ANIMATOR_TRANSLATION_Y); if (animatingY) { updateAnimationY(view); } else if (view.getTranslationY() != this.yTranslation) { view.setTranslationY(this.yTranslation); } // apply zTranslation boolean animatingZ = isAnimating(view, TAG_ANIMATOR_TRANSLATION_Z); if (animatingZ) { updateAnimationZ(view); } else if (view.getTranslationZ() != this.zTranslation) { view.setTranslationZ(this.zTranslation); } // apply scaleX boolean animatingScaleX = isAnimating(view, SCALE_X_PROPERTY); if (animatingScaleX) { updateAnimation(view, SCALE_X_PROPERTY, scaleX); } else if (view.getScaleX() != scaleX) { view.setScaleX(scaleX); } // apply scaleY boolean animatingScaleY = isAnimating(view, SCALE_Y_PROPERTY); if (animatingScaleY) { updateAnimation(view, SCALE_Y_PROPERTY, scaleY); } else if (view.getScaleY() != scaleY) { view.setScaleY(scaleY); } int oldVisibility = view.getVisibility(); boolean becomesInvisible = this.alpha == 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.alpha) { // apply layer type boolean becomesFullyVisible = this.alpha == 1.0f; boolean newLayerTypeIsHardware = !becomesInvisible && !becomesFullyVisible && 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.alpha); } // 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, PropertyAnimator.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.alpha; if (!wasVisible && (alpha != 0 || child.getAlpha() != 0) && !this.gone && !this.hidden) { child.setVisibility(View.VISIBLE); } float childAlpha = child.getAlpha(); boolean alphaChanging = this.alpha != 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.xTranslation) { startXTranslationAnimation(child, animationProperties); } else { abortAnimation(child, TAG_ANIMATOR_TRANSLATION_X); } // start translationY animation if (child.getTranslationY() != this.yTranslation) { startYTranslationAnimation(child, animationProperties); } else { abortAnimation(child, TAG_ANIMATOR_TRANSLATION_Y); } // start translationZ animation if (child.getTranslationZ() != this.zTranslation) { startZTranslationAnimation(child, animationProperties); } else { abortAnimation(child, TAG_ANIMATOR_TRANSLATION_Z); } // start scaleX animation if (child.getScaleX() != scaleX) { PropertyAnimator.startAnimation(child, SCALE_X_PROPERTY, scaleX, animationProperties); } else { abortAnimation(child, SCALE_X_PROPERTY.getAnimatorTag()); } // start scaleX animation if (child.getScaleY() != scaleY) { PropertyAnimator.startAnimation(child, SCALE_Y_PROPERTY, scaleY, 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.alpha; 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(); 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, PropertyAnimator.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.zTranslation; 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(); 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.xTranslation; 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(); 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.yTranslation; 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(); if (listener != null) { animator.addListener(listener); } // remove the tag when the animation is finished animator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { HeadsUpManager.setIsClickedNotification(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 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(); } } }