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