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