1 /* 2 * Copyright (C) 2016 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; 18 19 import android.animation.Animator; 20 import android.animation.AnimatorListenerAdapter; 21 import android.animation.PropertyValuesHolder; 22 import android.animation.ValueAnimator; 23 import android.util.Property; 24 import android.view.View; 25 import android.view.animation.Interpolator; 26 27 import com.android.systemui.animation.Interpolators; 28 import com.android.systemui.statusbar.notification.stack.AnimationFilter; 29 import com.android.systemui.statusbar.notification.stack.AnimationProperties; 30 import com.android.systemui.statusbar.notification.stack.ViewState; 31 32 /** 33 * An animator to animate properties 34 */ 35 public class PropertyAnimator { 36 37 /** 38 * Set a property on a view, updating its value, even if it's already animating. 39 * The @param animated can be used to request an animation. 40 * If the view isn't animated, this utility will update the current animation if existent, 41 * such that the end value will point to @param newEndValue or apply it directly if there's 42 * no animation. 43 */ setProperty(final T view, AnimatableProperty animatableProperty, float newEndValue, AnimationProperties properties, boolean animated)44 public static <T extends View> void setProperty(final T view, 45 AnimatableProperty animatableProperty, float newEndValue, 46 AnimationProperties properties, boolean animated) { 47 int animatorTag = animatableProperty.getAnimatorTag(); 48 ValueAnimator previousAnimator = ViewState.getChildTag(view, animatorTag); 49 if (previousAnimator != null || animated) { 50 startAnimation(view, animatableProperty, newEndValue, animated ? properties : null); 51 } else { 52 // no new animation needed, let's just apply the value 53 animatableProperty.getProperty().set(view, newEndValue); 54 } 55 } 56 startAnimation(final T view, AnimatableProperty animatableProperty, float newEndValue, AnimationProperties properties)57 public static <T extends View> void startAnimation(final T view, 58 AnimatableProperty animatableProperty, float newEndValue, 59 AnimationProperties properties) { 60 Property<T, Float> property = animatableProperty.getProperty(); 61 int animationStartTag = animatableProperty.getAnimationStartTag(); 62 int animationEndTag = animatableProperty.getAnimationEndTag(); 63 Float previousStartValue = ViewState.getChildTag(view, animationStartTag); 64 Float previousEndValue = ViewState.getChildTag(view, animationEndTag); 65 if (previousEndValue != null && previousEndValue == newEndValue) { 66 return; 67 } 68 int animatorTag = animatableProperty.getAnimatorTag(); 69 ValueAnimator previousAnimator = ViewState.getChildTag(view, animatorTag); 70 AnimationFilter filter = properties != null ? properties.getAnimationFilter() : null; 71 if (filter == null || !filter.shouldAnimateProperty(property)) { 72 // just a local update was performed 73 if (previousAnimator != null) { 74 // we need to increase all animation keyframes of the previous animator by the 75 // relative change to the end value 76 PropertyValuesHolder[] values = previousAnimator.getValues(); 77 float relativeDiff = newEndValue - previousEndValue; 78 float newStartValue = previousStartValue + relativeDiff; 79 values[0].setFloatValues(newStartValue, newEndValue); 80 view.setTag(animationStartTag, newStartValue); 81 view.setTag(animationEndTag, newEndValue); 82 previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime()); 83 return; 84 } else { 85 // no new animation needed, let's just apply the value 86 property.set(view, newEndValue); 87 return; 88 } 89 } 90 91 Float currentValue = property.get(view); 92 AnimatorListenerAdapter listener = properties.getAnimationFinishListener(property); 93 if (currentValue.equals(newEndValue)) { 94 // Skip the animation! 95 if (previousAnimator != null) { 96 previousAnimator.cancel(); 97 } 98 if (listener != null) { 99 listener.onAnimationEnd(null); 100 } 101 return; 102 } 103 ValueAnimator animator = ValueAnimator.ofFloat(currentValue, newEndValue); 104 animator.addUpdateListener( 105 animation -> property.set(view, (Float) animation.getAnimatedValue())); 106 Interpolator customInterpolator = properties.getCustomInterpolator(view, property); 107 Interpolator interpolator = customInterpolator != null ? customInterpolator 108 : Interpolators.FAST_OUT_SLOW_IN; 109 animator.setInterpolator(interpolator); 110 long newDuration = ViewState.cancelAnimatorAndGetNewDuration(properties.duration, 111 previousAnimator); 112 animator.setDuration(newDuration); 113 if (properties.delay > 0 && (previousAnimator == null 114 || previousAnimator.getAnimatedFraction() == 0)) { 115 animator.setStartDelay(properties.delay); 116 } 117 if (listener != null) { 118 animator.addListener(listener); 119 } 120 // remove the tag when the animation is finished 121 animator.addListener(new AnimatorListenerAdapter() { 122 @Override 123 public void onAnimationEnd(Animator animation) { 124 view.setTag(animatorTag, null); 125 view.setTag(animationStartTag, null); 126 view.setTag(animationEndTag, null); 127 } 128 }); 129 ViewState.startAnimator(animator, listener); 130 view.setTag(animatorTag, animator); 131 view.setTag(animationStartTag, currentValue); 132 view.setTag(animationEndTag, newEndValue); 133 } 134 applyImmediately(T view, AnimatableProperty property, float newValue)135 public static <T extends View> void applyImmediately(T view, AnimatableProperty property, 136 float newValue) { 137 cancelAnimation(view, property); 138 property.getProperty().set(view, newValue); 139 } 140 cancelAnimation(View view, AnimatableProperty property)141 public static void cancelAnimation(View view, AnimatableProperty property) { 142 ValueAnimator animator = (ValueAnimator) view.getTag(property.getAnimatorTag()); 143 if (animator != null) { 144 animator.cancel(); 145 } 146 } 147 isAnimating(View view, AnimatableProperty property)148 public static boolean isAnimating(View view, AnimatableProperty property) { 149 return view.getTag(property.getAnimatorTag()) != null; 150 } 151 } 152