1 /* 2 * Copyright (C) 2022 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 package com.android.launcher3.anim; 17 18 import android.animation.Animator; 19 import android.animation.AnimatorListenerAdapter; 20 import android.animation.ObjectAnimator; 21 import android.util.FloatProperty; 22 23 import java.util.function.Consumer; 24 25 /** 26 * A mutable float which allows animating the value 27 */ 28 public class AnimatedFloat { 29 30 public static final FloatProperty<AnimatedFloat> VALUE = 31 new FloatProperty<AnimatedFloat>("value") { 32 @Override 33 public void setValue(AnimatedFloat obj, float v) { 34 obj.updateValue(v); 35 } 36 37 @Override 38 public Float get(AnimatedFloat obj) { 39 return obj.value; 40 } 41 }; 42 43 private static final Consumer<Float> NO_OP = t -> { }; 44 45 private final Consumer<Float> mUpdateCallback; 46 private ObjectAnimator mValueAnimator; 47 // Only non-null when an animation is playing to this value. 48 private Float mEndValue; 49 50 public float value; 51 AnimatedFloat()52 public AnimatedFloat() { 53 this(NO_OP); 54 } 55 AnimatedFloat(Runnable updateCallback)56 public AnimatedFloat(Runnable updateCallback) { 57 this(v -> updateCallback.run()); 58 } 59 AnimatedFloat(Consumer<Float> updateCallback)60 public AnimatedFloat(Consumer<Float> updateCallback) { 61 mUpdateCallback = updateCallback; 62 } 63 AnimatedFloat(Runnable updateCallback, float initialValue)64 public AnimatedFloat(Runnable updateCallback, float initialValue) { 65 this(updateCallback); 66 value = initialValue; 67 } 68 AnimatedFloat(Consumer<Float> updateCallback, float initialValue)69 public AnimatedFloat(Consumer<Float> updateCallback, float initialValue) { 70 this(updateCallback); 71 value = initialValue; 72 } 73 74 /** 75 * Returns an animation from the current value to the given value. 76 */ animateToValue(float end)77 public ObjectAnimator animateToValue(float end) { 78 return animateToValue(value, end); 79 } 80 81 /** 82 * Returns an animation from the given start value to the given end value. 83 */ animateToValue(float start, float end)84 public ObjectAnimator animateToValue(float start, float end) { 85 cancelAnimation(); 86 mValueAnimator = ObjectAnimator.ofFloat(this, VALUE, start, end); 87 mValueAnimator.addListener(new AnimatorListenerAdapter() { 88 @Override 89 public void onAnimationStart(Animator animator) { 90 if (mValueAnimator == animator) { 91 mEndValue = end; 92 } 93 } 94 95 @Override 96 public void onAnimationEnd(Animator animator) { 97 if (mValueAnimator == animator) { 98 mValueAnimator = null; 99 mEndValue = null; 100 } 101 } 102 }); 103 return mValueAnimator; 104 } 105 106 /** 107 * Changes the value and calls the callback. 108 * Note that the value can be directly accessed as well to avoid notifying the callback. 109 */ updateValue(float v)110 public void updateValue(float v) { 111 if (Float.compare(v, value) != 0) { 112 value = v; 113 mUpdateCallback.accept(value); 114 } 115 } 116 117 /** 118 * Cancels the animation. 119 */ cancelAnimation()120 public void cancelAnimation() { 121 if (mValueAnimator != null) { 122 mValueAnimator.cancel(); 123 // Clears the property values, so further ObjectAnimator#setCurrentFraction from e.g. 124 // AnimatorPlaybackController calls would do nothing. The null check is necessary to 125 // avoid mValueAnimator being set to null in onAnimationEnd. 126 if (mValueAnimator != null) { 127 mValueAnimator.setValues(); 128 mValueAnimator = null; 129 } 130 } 131 } 132 133 /** 134 * Ends the animation. 135 */ finishAnimation()136 public void finishAnimation() { 137 if (mValueAnimator != null && mValueAnimator.isRunning()) { 138 mValueAnimator.end(); 139 } 140 } 141 isAnimating()142 public boolean isAnimating() { 143 return mValueAnimator != null; 144 } 145 146 /** 147 * Returns whether we are currently animating, and the animation's end value matches the given. 148 */ isAnimatingToValue(float endValue)149 public boolean isAnimatingToValue(float endValue) { 150 return isAnimating() && mEndValue != null && mEndValue == endValue; 151 } 152 153 /** 154 * Returns the remaining time of the existing animation (if any). 155 */ getRemainingTime()156 public long getRemainingTime() { 157 return isAnimating() && mValueAnimator.isRunning() 158 ? Math.max(0, mValueAnimator.getDuration() - mValueAnimator.getCurrentPlayTime()) 159 : 0; 160 } 161 162 /** 163 * Returns whether we are currently not animating, and the animation's value matches the given. 164 */ isSettledOnValue(float endValue)165 public boolean isSettledOnValue(float endValue) { 166 return !isAnimating() && value == endValue; 167 } 168 } 169