1 /*
2  * Copyright 2019 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 @file:Suppress("NOTHING_TO_INLINE", "KotlinRedundantDiagnosticSuppress")
18 
19 package androidx.compose.animation.core
20 
21 import androidx.compose.animation.core.AnimationConstants.DefaultDurationMillis
22 import androidx.compose.animation.core.internal.JvmDefaultWithCompatibility
23 import androidx.compose.ui.util.fastCoerceIn
24 
25 /**
26  * [FloatAnimationSpec] interface is similar to [VectorizedAnimationSpec], except it deals
27  * exclusively with floats.
28  *
29  * Like [VectorizedAnimationSpec], [FloatAnimationSpec] is entirely stateless as well. It requires
30  * start/end values and start velocity to be passed in for the query of velocity and value of the
31  * animation. The [FloatAnimationSpec] itself stores only the animation configuration (such as the
32  * delay, duration and easing curve for [FloatTweenSpec], or spring constants for [FloatSpringSpec].
33  *
34  * A [FloatAnimationSpec] can be converted to an [VectorizedAnimationSpec] using [vectorize].
35  *
36  * @see [VectorizedAnimationSpec]
37  */
38 @JvmDefaultWithCompatibility
39 public interface FloatAnimationSpec : AnimationSpec<Float> {
40     /**
41      * Calculates the value of the animation at given the playtime, with the provided start/end
42      * values, and start velocity.
43      *
44      * @param playTimeNanos time since the start of the animation
45      * @param initialValue start value of the animation
46      * @param targetValue end value of the animation
47      * @param initialVelocity start velocity of the animation
48      */
getValueFromNanosnull49     public fun getValueFromNanos(
50         playTimeNanos: Long,
51         initialValue: Float,
52         targetValue: Float,
53         initialVelocity: Float
54     ): Float
55 
56     /**
57      * Calculates the velocity of the animation at given the playtime, with the provided start/end
58      * values, and start velocity.
59      *
60      * @param playTimeNanos time since the start of the animation
61      * @param initialValue start value of the animation
62      * @param targetValue end value of the animation
63      * @param initialVelocity start velocity of the animation
64      */
65     public fun getVelocityFromNanos(
66         playTimeNanos: Long,
67         initialValue: Float,
68         targetValue: Float,
69         initialVelocity: Float
70     ): Float
71 
72     /**
73      * Calculates the end velocity of the animation with the provided start/end values, and start
74      * velocity. For duration-based animations, end velocity will be the velocity of the animation
75      * at the duration time. This is also the default assumption. However, for spring animations,
76      * the transient trailing velocity will be snapped to zero.
77      *
78      * @param initialValue start value of the animation
79      * @param targetValue end value of the animation
80      * @param initialVelocity start velocity of the animation
81      */
82     public fun getEndVelocity(
83         initialValue: Float,
84         targetValue: Float,
85         initialVelocity: Float
86     ): Float =
87         getVelocityFromNanos(
88             getDurationNanos(initialValue, targetValue, initialVelocity),
89             initialValue,
90             targetValue,
91             initialVelocity
92         )
93 
94     /**
95      * Calculates the duration of an animation. For duration-based animations, this will return the
96      * pre-defined duration. For physics-based animations, the duration will be estimated based on
97      * the physics configuration (such as spring stiffness, damping ratio, visibility threshold) as
98      * well as the [initialValue], [targetValue] values, and [initialVelocity].
99      *
100      * __Note__: this may be a computation that is expensive - especially with spring based
101      * animations
102      *
103      * @param initialValue start value of the animation
104      * @param targetValue end value of the animation
105      * @param initialVelocity start velocity of the animation
106      */
107     @Suppress("MethodNameUnits")
108     public fun getDurationNanos(
109         initialValue: Float,
110         targetValue: Float,
111         initialVelocity: Float
112     ): Long
113 
114     /**
115      * Create an [VectorizedAnimationSpec] that animates [AnimationVector] from a
116      * [FloatAnimationSpec]. Every dimension of the [AnimationVector] will be animated using the
117      * given [FloatAnimationSpec].
118      */
119     override fun <V : AnimationVector> vectorize(
120         converter: TwoWayConverter<Float, V>
121     ): VectorizedFloatAnimationSpec<V> = VectorizedFloatAnimationSpec<V>(this)
122 }
123 
124 /**
125  * [FloatSpringSpec] animation uses a spring animation to animate a [Float] value. Its configuration
126  * can be tuned via adjusting the spring parameters, namely damping ratio and stiffness.
127  *
128  * @param dampingRatio damping ratio of the spring. Defaults to [Spring.DampingRatioNoBouncy]
129  * @param stiffness Stiffness of the spring. Defaults to [Spring.StiffnessMedium]
130  * @param visibilityThreshold The value threshold such that the animation is no longer significant.
131  *   e.g. 1px for translation animations. Defaults to [Spring.DefaultDisplacementThreshold]
132  */
133 public class FloatSpringSpec(
134     public val dampingRatio: Float = Spring.DampingRatioNoBouncy,
135     public val stiffness: Float = Spring.StiffnessMedium,
136     private val visibilityThreshold: Float = Spring.DefaultDisplacementThreshold
137 ) : FloatAnimationSpec {
138 
139     private val spring =
140         SpringSimulation(1f).also {
141             it.dampingRatio = dampingRatio
142             it.stiffness = stiffness
143         }
144 
145     override fun getValueFromNanos(
146         playTimeNanos: Long,
147         initialValue: Float,
148         targetValue: Float,
149         initialVelocity: Float
150     ): Float {
151         // TODO: Properly support Nanos in the spring impl
152         val playTimeMillis = playTimeNanos / MillisToNanos
153         spring.finalPosition = targetValue
154         return spring.updateValues(initialValue, initialVelocity, playTimeMillis).value
155     }
156 
157     override fun getVelocityFromNanos(
158         playTimeNanos: Long,
159         initialValue: Float,
160         targetValue: Float,
161         initialVelocity: Float
162     ): Float {
163         // TODO: Properly support Nanos in the spring impl
164         val playTimeMillis = playTimeNanos / MillisToNanos
165         spring.finalPosition = targetValue
166         return spring.updateValues(initialValue, initialVelocity, playTimeMillis).velocity
167     }
168 
169     override fun getEndVelocity(
170         initialValue: Float,
171         targetValue: Float,
172         initialVelocity: Float
173     ): Float = 0f
174 
175     @Suppress("MethodNameUnits")
176     override fun getDurationNanos(
177         initialValue: Float,
178         targetValue: Float,
179         initialVelocity: Float
180     ): Long =
181         estimateAnimationDurationMillis(
182             stiffness = spring.stiffness,
183             dampingRatio = spring.dampingRatio,
184             initialDisplacement = (initialValue - targetValue) / visibilityThreshold,
185             initialVelocity = initialVelocity / visibilityThreshold,
186             delta = 1f
187         ) * MillisToNanos
188 }
189 
190 /**
191  * [FloatTweenSpec] animates a Float value from any start value to any end value using a provided
192  * [easing] function. The animation will finish within the [duration] time. Unless a [delay] is
193  * specified, the animation will start right away.
194  *
195  * @param duration the amount of time (in milliseconds) the animation will take to finish. Defaults
196  *   to [DefaultDurationMillis]
197  * @param delay the amount of time the animation will wait before it starts running. Defaults to 0.
198  * @param easing the easing function that will be used to interoplate between the start and end
199  *   value of the animation. Defaults to [FastOutSlowInEasing].
200  */
201 public class FloatTweenSpec(
202     public val duration: Int = DefaultDurationMillis,
203     public val delay: Int = 0,
204     private val easing: Easing = FastOutSlowInEasing
205 ) : FloatAnimationSpec {
206     private val durationNanos: Long = duration * MillisToNanos
207 
208     private val delayNanos: Long = delay * MillisToNanos
209 
getValueFromNanosnull210     override fun getValueFromNanos(
211         playTimeNanos: Long,
212         initialValue: Float,
213         targetValue: Float,
214         initialVelocity: Float
215     ): Float {
216         val clampedPlayTimeNanos = clampPlayTimeNanos(playTimeNanos)
217         val rawFraction = if (duration == 0) 1f else clampedPlayTimeNanos / durationNanos.toFloat()
218         val fraction = easing.transform(rawFraction)
219         return lerp(initialValue, targetValue, fraction)
220     }
221 
clampPlayTimeNanosnull222     private inline fun clampPlayTimeNanos(playTimeNanos: Long): Long {
223         return (playTimeNanos - delayNanos).fastCoerceIn(0, durationNanos)
224     }
225 
226     @Suppress("MethodNameUnits")
getDurationNanosnull227     override fun getDurationNanos(
228         initialValue: Float,
229         targetValue: Float,
230         initialVelocity: Float
231     ): Long {
232         return delayNanos + durationNanos
233     }
234 
235     // Calculate velocity by difference between the current value and the value 1 ms ago. This is a
236     // preliminary way of calculating velocity used by easing curve based animations, and keyframe
237     // animations. Physics-based animations give a much more accurate velocity.
getVelocityFromNanosnull238     override fun getVelocityFromNanos(
239         playTimeNanos: Long,
240         initialValue: Float,
241         targetValue: Float,
242         initialVelocity: Float
243     ): Float {
244         val clampedPlayTimeNanos = clampPlayTimeNanos(playTimeNanos)
245         if (clampedPlayTimeNanos == 0L) {
246             return initialVelocity
247         }
248         val startNum =
249             getValueFromNanos(
250                 clampedPlayTimeNanos - MillisToNanos,
251                 initialValue,
252                 targetValue,
253                 initialVelocity
254             )
255         val endNum =
256             getValueFromNanos(clampedPlayTimeNanos, initialValue, targetValue, initialVelocity)
257         return (endNum - startNum) * 1000f
258     }
259 }
260