1 /*
2  * Copyright 2020 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 androidx.compose.ui.unit.IntOffset
21 
22 /**
23  * [DecayAnimationSpec] stores the specification of an animation, including 1) the data type to be
24  * animated, and 2) the animation configuration (i.e. [VectorizedDecayAnimationSpec]) that will be
25  * used once the data (of type [T]) has been converted to [AnimationVector].
26  *
27  * Any type [T] can be animated by the system as long as a [TwoWayConverter] is supplied to convert
28  * the data type [T] from and to an [AnimationVector]. There are a number of converters available
29  * out of the box. For example, to animate [androidx.compose.ui.unit.IntOffset] the system uses
30  * [IntOffset.VectorConverter][IntOffset.Companion.VectorConverter] to convert the object to
31  * [AnimationVector2D], so that both x and y dimensions are animated independently with separate
32  * velocity tracking. This enables multidimensional objects to be animated in a true
33  * multi-dimensional way. It is particularly useful for smoothly handling animation interruptions
34  * (such as when the target changes during the animation).
35  */
36 public interface DecayAnimationSpec<T> {
37 
38     /**
39      * Creates a [VectorizedDecayAnimationSpec] with the given [TwoWayConverter].
40      *
41      * The underlying animation system operates on [AnimationVector]s. [T] will be converted to
42      * [AnimationVector] to animate. [VectorizedDecayAnimationSpec] describes how the converted
43      * [AnimationVector] should be animated.
44      *
45      * @param typeConverter converts the type [T] from and to [AnimationVector] type
46      */
vectorizenull47     public fun <V : AnimationVector> vectorize(
48         typeConverter: TwoWayConverter<T, V>
49     ): VectorizedDecayAnimationSpec<V>
50 }
51 
52 /**
53  * Calculates the target value of a decay animation based on the [initialValue] and
54  * [initialVelocity], and the [typeConverter] that converts the given type [T] to [AnimationVector].
55  *
56  * @return target value where the animation will come to a natural stop
57  */
58 public fun <T, V : AnimationVector> DecayAnimationSpec<T>.calculateTargetValue(
59     typeConverter: TwoWayConverter<T, V>,
60     initialValue: T,
61     initialVelocity: T
62 ): T {
63     val vectorizedSpec = vectorize(typeConverter)
64     val targetVector =
65         vectorizedSpec.getTargetValue(
66             typeConverter.convertToVector(initialValue),
67             typeConverter.convertToVector(initialVelocity)
68         )
69     return typeConverter.convertFromVector(targetVector)
70 }
71 
72 /**
73  * Calculates the target value of a Float decay animation based on the [initialValue] and
74  * [initialVelocity].
75  *
76  * @return target value where the animation will come to a natural stop
77  */
calculateTargetValuenull78 public fun DecayAnimationSpec<Float>.calculateTargetValue(
79     initialValue: Float,
80     initialVelocity: Float
81 ): Float {
82     val vectorizedSpec = vectorize(Float.VectorConverter)
83     val targetVector =
84         vectorizedSpec.getTargetValue(
85             AnimationVector(initialValue),
86             AnimationVector(initialVelocity)
87         )
88     return targetVector.value
89 }
90 
91 /**
92  * Creates a decay animation spec where the friction/deceleration is always proportional to the
93  * velocity. As a result, the velocity goes under an exponential decay. The constructor parameter,
94  * [frictionMultiplier], can be tuned to adjust the amount of friction applied in the decay. The
95  * higher the multiplier, the higher the friction, the sooner the animation will stop, and the
96  * shorter distance the animation will travel with the same starting condition.
97  * [absVelocityThreshold] describes the absolute value of a velocity threshold, below which the
98  * animation is considered finished.
99  *
100  * @param frictionMultiplier The decay friction multiplier. This must be greater than `0`.
101  * @param absVelocityThreshold The minimum speed, below which the animation is considered finished.
102  *   Must be greater than `0`.
103  */
exponentialDecaynull104 public fun <T> exponentialDecay(
105     @FloatRange(from = 0.0, fromInclusive = false) frictionMultiplier: Float = 1f,
106     @FloatRange(from = 0.0, fromInclusive = false) absVelocityThreshold: Float = 0.1f
107 ): DecayAnimationSpec<T> =
108     FloatExponentialDecaySpec(frictionMultiplier, absVelocityThreshold).generateDecayAnimationSpec()
109 
110 /**
111  * Creates a [DecayAnimationSpec] from a [FloatDecayAnimationSpec] by applying the given
112  * [FloatDecayAnimationSpec] on every dimension of the [AnimationVector] that [T] converts to.
113  */
114 public fun <T> FloatDecayAnimationSpec.generateDecayAnimationSpec(): DecayAnimationSpec<T> {
115     return DecayAnimationSpecImpl(this)
116 }
117 
118 private class DecayAnimationSpecImpl<T>(private val floatDecaySpec: FloatDecayAnimationSpec) :
119     DecayAnimationSpec<T> {
vectorizenull120     override fun <V : AnimationVector> vectorize(
121         typeConverter: TwoWayConverter<T, V>
122     ): VectorizedDecayAnimationSpec<V> = VectorizedFloatDecaySpec(floatDecaySpec)
123 }
124 
125 private class VectorizedFloatDecaySpec<V : AnimationVector>(
126     val floatDecaySpec: FloatDecayAnimationSpec
127 ) : VectorizedDecayAnimationSpec<V> {
128     private lateinit var valueVector: V
129     private lateinit var velocityVector: V
130     private lateinit var targetVector: V
131     override val absVelocityThreshold: Float = floatDecaySpec.absVelocityThreshold
132 
133     override fun getValueFromNanos(playTimeNanos: Long, initialValue: V, initialVelocity: V): V {
134         if (!::valueVector.isInitialized) {
135             valueVector = initialValue.newInstance()
136         }
137         for (i in 0 until valueVector.size) {
138             valueVector[i] =
139                 floatDecaySpec.getValueFromNanos(playTimeNanos, initialValue[i], initialVelocity[i])
140         }
141         return valueVector
142     }
143 
144     override fun getDurationNanos(initialValue: V, initialVelocity: V): Long {
145         var maxDuration = 0L
146         if (!::velocityVector.isInitialized) {
147             velocityVector = initialValue.newInstance()
148         }
149         for (i in 0 until velocityVector.size) {
150             maxDuration =
151                 maxOf(
152                     maxDuration,
153                     floatDecaySpec.getDurationNanos(initialValue[i], initialVelocity[i])
154                 )
155         }
156         return maxDuration
157     }
158 
159     override fun getVelocityFromNanos(playTimeNanos: Long, initialValue: V, initialVelocity: V): V {
160         if (!::velocityVector.isInitialized) {
161             velocityVector = initialValue.newInstance()
162         }
163         for (i in 0 until velocityVector.size) {
164             velocityVector[i] =
165                 floatDecaySpec.getVelocityFromNanos(
166                     playTimeNanos,
167                     initialValue[i],
168                     initialVelocity[i]
169                 )
170         }
171         return velocityVector
172     }
173 
174     override fun getTargetValue(initialValue: V, initialVelocity: V): V {
175         if (!::targetVector.isInitialized) {
176             targetVector = initialValue.newInstance()
177         }
178         for (i in 0 until targetVector.size) {
179             targetVector[i] = floatDecaySpec.getTargetValue(initialValue[i], initialVelocity[i])
180         }
181         return targetVector
182     }
183 }
184