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