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