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 package androidx.compose.animation.core
18 
19 import androidx.annotation.FloatRange
20 import kotlin.math.abs
21 import kotlin.math.exp
22 import kotlin.math.ln
23 import kotlin.math.max
24 
25 /**
26  * This animation interface is intended to be stateless, just like Animation<T>. But unlike
27  * Animation<T>, DecayAnimation does not have an end value defined. The end value is a result of the
28  * animation rather than an input.
29  */
30 public interface FloatDecayAnimationSpec {
31     /**
32      * This is the absolute value of a velocity threshold, below which the animation is considered
33      * finished.
34      */
35     public val absVelocityThreshold: Float
36 
37     /**
38      * Returns the value of the animation at the given time.
39      *
40      * @param playTimeNanos The time elapsed in milliseconds since the start of the animation
41      * @param initialValue The start value of the animation
42      * @param initialVelocity The start velocity of the animation
43      */
getValueFromNanosnull44     public fun getValueFromNanos(
45         playTimeNanos: Long,
46         initialValue: Float,
47         initialVelocity: Float
48     ): Float
49 
50     /**
51      * Returns the duration of the decay animation, in nanoseconds.
52      *
53      * @param initialValue start value of the animation
54      * @param initialVelocity start velocity of the animation
55      */
56     @Suppress("MethodNameUnits")
57     public fun getDurationNanos(initialValue: Float, initialVelocity: Float): Long
58 
59     /**
60      * Returns the velocity of the animation at the given time.
61      *
62      * @param playTimeNanos The time elapsed in milliseconds since the start of the animation
63      * @param initialValue The start value of the animation
64      * @param initialVelocity The start velocity of the animation
65      */
66     public fun getVelocityFromNanos(
67         playTimeNanos: Long,
68         initialValue: Float,
69         initialVelocity: Float
70     ): Float
71 
72     /**
73      * Returns the target value of the animation based on the starting condition of the animation (
74      * i.e. start value and start velocity).
75      *
76      * @param initialValue The start value of the animation
77      * @param initialVelocity The start velocity of the animation
78      */
79     public fun getTargetValue(initialValue: Float, initialVelocity: Float): Float
80 }
81 
82 private const val ExponentialDecayFriction = -4.2f
83 
84 /**
85  * This is a decay animation where the friction/deceleration is always proportional to the velocity.
86  * As a result, the velocity goes under an exponential decay. The constructor parameter,
87  * `frictionMultiplier`, can be tuned to adjust the amount of friction applied in the decay. The
88  * higher the multiplier, the higher the friction, the sooner the animation will stop, and the
89  * shorter distance the animation will travel with the same starting condition.
90  *
91  * @param frictionMultiplier The friction multiplier, indicating how quickly the animation should
92  *   stop. This should be greater than `0`, with a default value of `1.0`.
93  * @param absVelocityThreshold The speed at which the animation is considered close enough to rest
94  *   for the animation to finish.
95  */
96 public class FloatExponentialDecaySpec(
97     @FloatRange(from = 0.0, fromInclusive = false) frictionMultiplier: Float = 1f,
98     @FloatRange(from = 0.0, fromInclusive = false) absVelocityThreshold: Float = 0.1f
99 ) : FloatDecayAnimationSpec {
100 
101     override val absVelocityThreshold: Float = max(0.0000001f, abs(absVelocityThreshold))
102     private val friction: Float = ExponentialDecayFriction * max(0.0001f, frictionMultiplier)
103 
104     override fun getValueFromNanos(
105         playTimeNanos: Long,
106         initialValue: Float,
107         initialVelocity: Float
108     ): Float {
109         // TODO: Properly support nanos
110         val playTimeMillis = playTimeNanos / MillisToNanos
111         return initialValue - initialVelocity / friction +
112             initialVelocity / friction * exp(friction * playTimeMillis / 1000f)
113     }
114 
115     override fun getVelocityFromNanos(
116         playTimeNanos: Long,
117         initialValue: Float,
118         initialVelocity: Float
119     ): Float {
120         // TODO: Properly support nanos
121         val playTimeMillis = playTimeNanos / MillisToNanos
122         return (initialVelocity * exp(((playTimeMillis / 1000f) * friction)))
123     }
124 
125     @Suppress("MethodNameUnits")
126     override fun getDurationNanos(initialValue: Float, initialVelocity: Float): Long {
127         // Inverse of getVelocity
128         return (1000f * ln(absVelocityThreshold / abs(initialVelocity)) / friction).toLong() *
129             MillisToNanos
130     }
131 
132     override fun getTargetValue(initialValue: Float, initialVelocity: Float): Float {
133         if (abs(initialVelocity) <= absVelocityThreshold) {
134             return initialValue
135         }
136         val duration: Double =
137             ln(abs(absVelocityThreshold / initialVelocity).toDouble()) / friction * 1000
138 
139         return initialValue - initialVelocity / friction +
140             initialVelocity / friction * exp((friction * duration / 1000f)).toFloat()
141     }
142 }
143 
144 /**
145  * Creates a [Animation] (with a fixed start value and start velocity) that decays over time based
146  * on the given [FloatDecayAnimationSpec].
147  *
148  * @param startValue the starting value of the fixed animation.
149  * @param startVelocity the starting velocity of the fixed animation.
150  */
createAnimationnull151 internal fun FloatDecayAnimationSpec.createAnimation(
152     startValue: Float,
153     startVelocity: Float = 0f
154 ): Animation<Float, AnimationVector1D> {
155     return DecayAnimation(this, startValue, startVelocity)
156 }
157