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.RestrictTo
20 import androidx.compose.animation.core.internal.JvmDefaultWithCompatibility
21 
22 /**
23  * This interface provides a convenient way to query from an [VectorizedAnimationSpec] or
24  * [FloatDecayAnimationSpec]: It spares the need to pass the starting conditions and in some cases
25  * ending condition for each value or velocity query, and instead only requires the play time to be
26  * passed for such queries.
27  *
28  * The implementation of this interface should cache the starting conditions and ending conditions
29  * of animations as needed.
30  *
31  * __Note__: [Animation] does not track the lifecycle of an animation. It merely reacts to play time
32  * change and returns the new value/velocity as a result. It can be used as a building block for
33  * more lifecycle aware animations. In contrast, [Animatable] and [Transition] are stateful and
34  * manage their own lifecycles.
35  *
36  * @see [Animatable]
37  * @see [rememberTransition]
38  * @see [updateTransition]
39  */
40 @JvmDefaultWithCompatibility
41 public interface Animation<T, V : AnimationVector> {
42     /** This amount of time in nanoseconds that the animation will run before it finishes */
43     @get:Suppress("MethodNameUnits") public val durationNanos: Long
44 
45     /**
46      * The [TwoWayConverter] that will be used to convert value/velocity from any arbitrary data
47      * type to [AnimationVector]. This makes it possible to animate different dimensions of the data
48      * object independently (e.g. x/y dimensions of the position data).
49      */
50     public val typeConverter: TwoWayConverter<T, V>
51 
52     /** This is the value that the [Animation] will reach when it finishes uninterrupted. */
53     public val targetValue: T
54 
55     /**
56      * Whether or not the [Animation] represents an infinite animation. That is, one that will not
57      * finish by itself, one that needs an external action to stop. For examples, an indeterminate
58      * progress bar, which will only stop when it is removed from the composition.
59      */
60     public val isInfinite: Boolean
61 
62     /**
63      * Returns the value of the animation at the given play time.
64      *
65      * @param playTimeNanos the play time that is used to determine the value of the animation.
66      */
getValueFromNanosnull67     public fun getValueFromNanos(playTimeNanos: Long): T
68 
69     /**
70      * Returns the velocity (in [AnimationVector] form) of the animation at the given play time.
71      *
72      * @param playTimeNanos the play time that is used to calculate the velocity of the animation.
73      */
74     public fun getVelocityVectorFromNanos(playTimeNanos: Long): V
75 
76     /**
77      * Returns whether the animation is finished at the given play time.
78      *
79      * @param playTimeNanos the play time used to determine whether the animation is finished.
80      */
81     public fun isFinishedFromNanos(playTimeNanos: Long): Boolean {
82         return playTimeNanos >= durationNanos
83     }
84 }
85 
86 internal val Animation<*, *>.durationMillis: Long
87     get() = durationNanos / MillisToNanos
88 
89 internal const val MillisToNanos: Long = 1_000_000L
90 
91 internal const val SecondsToMillis: Long = 1_000L
92 
93 /**
94  * Returns the velocity of the animation at the given play time.
95  *
96  * @param playTimeNanos the play time that is used to calculate the velocity of the animation.
97  */
getVelocityFromNanosnull98 public fun <T, V : AnimationVector> Animation<T, V>.getVelocityFromNanos(playTimeNanos: Long): T =
99     typeConverter.convertFromVector(getVelocityVectorFromNanos(playTimeNanos))
100 
101 /**
102  * Creates a [TargetBasedAnimation] from a given [VectorizedAnimationSpec] of [AnimationVector]
103  * type. This convenient method is intended for when the value being animated (i.e. start value, end
104  * value, etc) is of [AnimationVector] type.
105  *
106  * @param initialValue the value that the animation will start from
107  * @param targetValue the value that the animation will end at
108  * @param initialVelocity the initial velocity to start the animation at
109  */
110 @RestrictTo(RestrictTo.Scope.LIBRARY)
111 public fun <V : AnimationVector> VectorizedAnimationSpec<V>.createAnimation(
112     initialValue: V,
113     targetValue: V,
114     initialVelocity: V
115 ): TargetBasedAnimation<V, V> =
116     TargetBasedAnimation(
117         animationSpec = this,
118         initialValue = initialValue,
119         targetValue = targetValue,
120         initialVelocityVector = initialVelocity,
121         typeConverter = TwoWayConverter({ it }, { it })
122     )
123 
124 /**
125  * Creates a [TargetBasedAnimation] with the given start/end conditions of the animation, and the
126  * provided [animationSpec].
127  *
128  * The resulting [Animation] assumes that the start value and velocity, as well as end value do not
129  * change throughout the animation, and cache these values. This caching enables much more
130  * convenient query for animation value and velocity (where only playtime needs to be passed into
131  * the methods).
132  *
133  * __Note__: When interruptions happen to the [TargetBasedAnimation], a new instance should be
134  * created that use the current value and velocity as the starting conditions. This type of
135  * interruption handling is the default behavior for both [Animatable] and [Transition]. Consider
136  * using those APIs for the interruption handling, as well as built-in animation lifecycle
137  * management.
138  *
139  * @param animationSpec the [AnimationSpec] that will be used to calculate value/velocity
140  * @param initialValue the start value of the animation
141  * @param targetValue the end value of the animation
142  * @param initialVelocity the start velocity (of type [T] of the animation
143  * @param typeConverter the [TwoWayConverter] that is used to convert animation type [T] from/to [V]
144  */
TargetBasedAnimationnull145 public fun <T, V : AnimationVector> TargetBasedAnimation(
146     animationSpec: AnimationSpec<T>,
147     typeConverter: TwoWayConverter<T, V>,
148     initialValue: T,
149     targetValue: T,
150     initialVelocity: T
151 ): TargetBasedAnimation<T, V> =
152     TargetBasedAnimation(
153         animationSpec,
154         typeConverter,
155         initialValue,
156         targetValue,
157         typeConverter.convertToVector(initialVelocity)
158     )
159 
160 /**
161  * This is a convenient animation wrapper class that works for all target based animations, i.e.
162  * animations that has a pre-defined end value, unlike decay.
163  *
164  * It assumes that the starting value and velocity, as well as ending value do not change throughout
165  * the animation, and cache these values. This caching enables much more convenient query for
166  * animation value and velocity (where only playtime needs to be passed into the methods).
167  *
168  * __Note__: When interruptions happen to the [TargetBasedAnimation], a new instance should be
169  * created that use the current value and velocity as the starting conditions. This type of
170  * interruption handling is the default behavior for both [Animatable] and [Transition]. Consider
171  * using those APIs for the interruption handling, as well as built-in animation lifecycle
172  * management.
173  *
174  * @param animationSpec the [VectorizedAnimationSpec] that will be used to calculate value/velocity
175  * @param initialValue the start value of the animation
176  * @param targetValue the end value of the animation
177  * @param typeConverter the [TwoWayConverter] that is used to convert animation type [T] from/to [V]
178  * @param initialVelocityVector the start velocity of the animation in the form of [AnimationVector]
179  * @see [Transition]
180  * @see [rememberTransition]
181  * @see [updateTransition]
182  * @see [Animatable]
183  */
184 public class TargetBasedAnimation<T, V : AnimationVector>
185 internal constructor(
186     internal val animationSpec: VectorizedAnimationSpec<V>,
187     override val typeConverter: TwoWayConverter<T, V>,
188     initialValue: T,
189     targetValue: T,
190     initialVelocityVector: V? = null
191 ) : Animation<T, V> {
192     internal var mutableTargetValue: T = targetValue
193         set(value) {
194             if (field != value) {
195                 field = value
196                 targetValueVector = typeConverter.convertToVector(value)
197                 _endVelocity = null
198                 _durationNanos = -1L
199             }
200         }
201 
202     internal var mutableInitialValue: T = initialValue
203         set(value) {
204             if (value != field) {
205                 field = value
206                 initialValueVector = typeConverter.convertToVector(value)
207                 _endVelocity = null
208                 _durationNanos = -1L
209             }
210         }
211 
212     public val initialValue: T
213         get() = mutableInitialValue
214 
215     override val targetValue: T
216         get() = mutableTargetValue
217 
218     /**
219      * Creates a [TargetBasedAnimation] with the given start/end conditions of the animation, and
220      * the provided [animationSpec].
221      *
222      * The resulting [Animation] assumes that the start value and velocity, as well as end value do
223      * not change throughout the animation, and cache these values. This caching enables much more
224      * convenient query for animation value and velocity (where only playtime needs to be passed
225      * into the methods).
226      *
227      * __Note__: When interruptions happen to the [TargetBasedAnimation], a new instance should be
228      * created that use the current value and velocity as the starting conditions. This type of
229      * interruption handling is the default behavior for both [Animatable] and [Transition].
230      * Consider using those APIs for the interruption handling, as well as built-in animation
231      * lifecycle management.
232      *
233      * @param animationSpec the [AnimationSpec] that will be used to calculate value/velocity
234      * @param typeConverter the [TwoWayConverter] that is used to convert animation type [T] from/to
235      *   [V]
236      * @param initialValue the start value of the animation
237      * @param targetValue the end value of the animation
238      * @param initialVelocityVector the start velocity vector, null by default (meaning 0 velocity).
239      */
240     public constructor(
241         animationSpec: AnimationSpec<T>,
242         typeConverter: TwoWayConverter<T, V>,
243         initialValue: T,
244         targetValue: T,
245         initialVelocityVector: V? = null
246     ) : this(
247         animationSpec.vectorize(typeConverter),
248         typeConverter,
249         initialValue,
250         targetValue,
251         initialVelocityVector
252     )
253 
254     private var initialValueVector = typeConverter.convertToVector(initialValue)
255     private var targetValueVector = typeConverter.convertToVector(targetValue)
256     private val initialVelocityVector =
257         initialVelocityVector?.copy() ?: typeConverter.convertToVector(initialValue).newInstance()
258 
259     override val isInfinite: Boolean
260         get() = animationSpec.isInfinite
261 
262     override fun getValueFromNanos(playTimeNanos: Long): T {
263         return if (!isFinishedFromNanos(playTimeNanos)) {
264             animationSpec
265                 .getValueFromNanos(
266                     playTimeNanos,
267                     initialValueVector,
268                     targetValueVector,
269                     initialVelocityVector
270                 )
271                 .let {
272                     // TODO: Remove after b/232030217
273                     for (i in 0 until it.size) {
274                         checkPrecondition(!it.get(i).isNaN()) {
275                             "AnimationVector cannot contain a NaN. $it. Animation: $this," +
276                                 " playTimeNanos: $playTimeNanos"
277                         }
278                     }
279                     typeConverter.convertFromVector(it)
280                 }
281         } else {
282             targetValue
283         }
284     }
285 
286     private var _durationNanos: Long = -1L
287 
288     @get:Suppress("MethodNameUnits")
289     override val durationNanos: Long
290         get() {
291             if (_durationNanos < 0L) {
292                 _durationNanos =
293                     animationSpec.getDurationNanos(
294                         initialValue = initialValueVector,
295                         targetValue = targetValueVector,
296                         initialVelocity = this.initialVelocityVector
297                     )
298             }
299             return _durationNanos
300         }
301 
302     private var _endVelocity: V? = null
303 
304     private val endVelocity
305         get() =
306             _endVelocity
307                 ?: animationSpec
308                     .getEndVelocity(
309                         initialValueVector,
310                         targetValueVector,
311                         this.initialVelocityVector
312                     )
313                     .also { _endVelocity = it }
314 
315     override fun getVelocityVectorFromNanos(playTimeNanos: Long): V {
316         return if (!isFinishedFromNanos(playTimeNanos)) {
317             animationSpec.getVelocityFromNanos(
318                 playTimeNanos,
319                 initialValueVector,
320                 targetValueVector,
321                 initialVelocityVector
322             )
323         } else {
324             endVelocity
325         }
326     }
327 
328     override fun toString(): String {
329         return "TargetBasedAnimation: $initialValue -> $targetValue," +
330             "initial velocity: $initialVelocityVector, duration: $durationMillis ms," +
331             "animationSpec: $animationSpec"
332     }
333 }
334 
335 /**
336  * [DecayAnimation] is an animation that slows down from [initialVelocityVector] as time goes on.
337  * [DecayAnimation] is stateless, and it does not have any concept of lifecycle. It serves as an
338  * animation calculation engine that supports convenient query of value/velocity given a play time.
339  * To achieve that, [DecayAnimation] stores all the animation related information: [initialValue],
340  * [initialVelocityVector], decay animation spec, [typeConverter].
341  *
342  * __Note__: Unless there's a need to control the timing manually, it's generally recommended to use
343  * higher level animation APIs that build on top [DecayAnimation], such as
344  * [Animatable.animateDecay], [AnimationState.animateDecay], etc.
345  *
346  * @see Animatable.animateDecay
347  * @see AnimationState.animateDecay
348  */
349 public class DecayAnimation<T, V : AnimationVector> /*@VisibleForTesting*/
350 constructor(
351     private val animationSpec: VectorizedDecayAnimationSpec<V>,
352     override val typeConverter: TwoWayConverter<T, V>,
353     public val initialValue: T,
354     initialVelocityVector: V
355 ) : Animation<T, V> {
356     private val initialValueVector: V = typeConverter.convertToVector(initialValue)
357     public val initialVelocityVector: V = initialVelocityVector.copy()
358     private val endVelocity: V
359 
360     override val targetValue: T =
361         typeConverter.convertFromVector(
362             animationSpec.getTargetValue(initialValueVector, initialVelocityVector)
363         )
364     @get:Suppress("MethodNameUnits") override val durationNanos: Long
365 
366     // DecayAnimation finishes by design
367     override val isInfinite: Boolean = false
368 
369     /**
370      * [DecayAnimation] is an animation that slows down from [initialVelocityVector] as time goes
371      * on. [DecayAnimation] is stateless, and it does not have any concept of lifecycle. It serves
372      * as an animation calculation engine that supports convenient query of value/velocity given a
373      * play time. To achieve that, [DecayAnimation] stores all the animation related information:
374      * [initialValue], [initialVelocityVector], decay animation spec, [typeConverter].
375      *
376      * __Note__: Unless there's a need to control the timing manually, it's generally recommended to
377      * use higher level animation APIs that build on top [DecayAnimation], such as
378      * [Animatable.animateDecay], [AnimationState.animateDecay], etc.
379      *
380      * @param animationSpec Decay animation spec that defines the slow-down curve of the animation
381      * @param typeConverter Type converter to convert the type [T] from and to [AnimationVector]
382      * @param initialValue The starting value of the animation
383      * @param initialVelocityVector The starting velocity of the animation in [AnimationVector] form
384      * @see Animatable.animateDecay
385      * @see AnimationState.animateDecay
386      */
387     public constructor(
388         animationSpec: DecayAnimationSpec<T>,
389         typeConverter: TwoWayConverter<T, V>,
390         initialValue: T,
391         initialVelocityVector: V
392     ) : this(
393         animationSpec.vectorize(typeConverter),
394         typeConverter,
395         initialValue,
396         initialVelocityVector
397     )
398 
399     /**
400      * [DecayAnimation] is an animation that slows down from [initialVelocity] as time goes on.
401      * [DecayAnimation] is stateless, and it does not have any concept of lifecycle. It serves as an
402      * animation calculation engine that supports convenient query of value/velocity given a play
403      * time. To achieve that, [DecayAnimation] stores all the animation related information:
404      * [initialValue], [initialVelocity], [animationSpec], [typeConverter].
405      *
406      * __Note__: Unless there's a need to control the timing manually, it's generally recommended to
407      * use higher level animation APIs that build on top [DecayAnimation], such as
408      * [Animatable.animateDecay], [AnimationState.animateDecay], etc.
409      *
410      * @param animationSpec Decay animation spec that defines the slow-down curve of the animation
411      * @param typeConverter Type converter to convert the type [T] from and to [AnimationVector]
412      * @param initialValue The starting value of the animation
413      * @param initialVelocity The starting velocity of the animation
414      * @see Animatable.animateDecay
415      * @see AnimationState.animateDecay
416      */
417     public constructor(
418         animationSpec: DecayAnimationSpec<T>,
419         typeConverter: TwoWayConverter<T, V>,
420         initialValue: T,
421         initialVelocity: T
422     ) : this(
423         animationSpec.vectorize(typeConverter),
424         typeConverter,
425         initialValue,
426         typeConverter.convertToVector(initialVelocity)
427     )
428 
429     init {
430         durationNanos = animationSpec.getDurationNanos(initialValueVector, initialVelocityVector)
431         endVelocity =
432             animationSpec
433                 .getVelocityFromNanos(durationNanos, initialValueVector, initialVelocityVector)
434                 .copy()
435         for (i in 0 until endVelocity.size) {
436             endVelocity[i] =
437                 endVelocity[i].coerceIn(
438                     -animationSpec.absVelocityThreshold,
439                     animationSpec.absVelocityThreshold
440                 )
441         }
442     }
443 
getValueFromNanosnull444     override fun getValueFromNanos(playTimeNanos: Long): T {
445         if (!isFinishedFromNanos(playTimeNanos)) {
446             return typeConverter.convertFromVector(
447                 animationSpec.getValueFromNanos(
448                     playTimeNanos,
449                     initialValueVector,
450                     initialVelocityVector
451                 )
452             )
453         } else {
454             return targetValue
455         }
456     }
457 
getVelocityVectorFromNanosnull458     override fun getVelocityVectorFromNanos(playTimeNanos: Long): V {
459         if (!isFinishedFromNanos(playTimeNanos)) {
460             return animationSpec.getVelocityFromNanos(
461                 playTimeNanos,
462                 initialValueVector,
463                 initialVelocityVector
464             )
465         } else {
466             return endVelocity
467         }
468     }
469 }
470 
471 /**
472  * [DecayAnimation] is an animation that slows down from [initialVelocity] as time goes on.
473  * [DecayAnimation] is stateless, and it does not have any concept of lifecycle. It serves as an
474  * animation calculation engine that supports convenient query of value/velocity given a play time.
475  * To achieve that, [DecayAnimation] stores all the animation related information: [initialValue],
476  * [initialVelocity], decay animation spec.
477  *
478  * __Note__: Unless there's a need to control the timing manually, it's generally recommended to use
479  * higher level animation APIs that build on top [DecayAnimation], such as
480  * [Animatable.animateDecay], [animateDecay], etc.
481  *
482  * @param animationSpec decay animation that will be used
483  * @param initialValue starting value that will be passed to the decay animation
484  * @param initialVelocity starting velocity for the decay animation, 0f by default
485  */
DecayAnimationnull486 public fun DecayAnimation(
487     animationSpec: FloatDecayAnimationSpec,
488     initialValue: Float,
489     initialVelocity: Float = 0f
490 ): DecayAnimation<Float, AnimationVector1D> =
491     DecayAnimation(
492         animationSpec.generateDecayAnimationSpec(),
493         Float.VectorConverter,
494         initialValue,
495         AnimationVector(initialVelocity)
496     )
497