1 /*
<lambda>null2 * 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.collection.IntList
20 import androidx.collection.IntObjectMap
21 import androidx.collection.MutableIntList
22 import androidx.collection.MutableIntObjectMap
23 import androidx.compose.animation.core.AnimationConstants.DefaultDurationMillis
24 import androidx.compose.animation.core.internal.JvmDefaultWithCompatibility
25 import androidx.compose.ui.util.fastCoerceIn
26 import kotlin.jvm.JvmInline
27 import kotlin.math.min
28
29 /**
30 * [VectorizedAnimationSpec]s are stateless vector based animation specifications. They do not
31 * assume any starting/ending conditions. Nor do they manage a lifecycle. All it stores is the
32 * configuration that is particular to the type of the animation. easing and duration for
33 * [VectorizedTweenSpec]s, or spring constants for [VectorizedSpringSpec]s. Its stateless nature
34 * allows the same [VectorizedAnimationSpec] to be reused by a few different running animations with
35 * different starting and ending values. More importantly, it allows the system to reuse the same
36 * animation spec when the animation target changes in-flight.
37 *
38 * Since [VectorizedAnimationSpec]s are stateless, it requires starting value/velocity and ending
39 * value to be passed in, along with playtime, to calculate the value or velocity at that time. Play
40 * time here is the progress of the animation in terms of milliseconds, where 0 means the start of
41 * the animation and [getDurationNanos] returns the play time for the end of the animation.
42 *
43 * __Note__: For use cases where the starting values/velocity and ending values aren't expected to
44 * change, it is recommended to use [Animation] that caches these static values and hence does not
45 * require them to be supplied in the value/velocity calculation.
46 *
47 * @see Animation
48 */
49 @JvmDefaultWithCompatibility
50 public interface VectorizedAnimationSpec<V : AnimationVector> {
51 /**
52 * Whether or not the [VectorizedAnimationSpec] specifies an infinite animation. That is, one
53 * that will not finish by itself, one that needs an external action to stop. For examples, an
54 * indeterminate progress bar, which will only stop when it is removed from the composition.
55 */
56 public val isInfinite: Boolean
57
58 /**
59 * Calculates the value of the animation at given the playtime, with the provided start/end
60 * values, and start velocity.
61 *
62 * @param playTimeNanos time since the start of the animation
63 * @param initialValue start value of the animation
64 * @param targetValue end value of the animation
65 * @param initialVelocity start velocity of the animation
66 */
67 public fun getValueFromNanos(
68 playTimeNanos: Long,
69 initialValue: V,
70 targetValue: V,
71 initialVelocity: V
72 ): V
73
74 /**
75 * Calculates the velocity of the animation at given the playtime, with the provided start/end
76 * values, and start velocity.
77 *
78 * @param playTimeNanos time since the start of the animation
79 * @param initialValue start value of the animation
80 * @param targetValue end value of the animation
81 * @param initialVelocity start velocity of the animation
82 */
83 public fun getVelocityFromNanos(
84 playTimeNanos: Long,
85 initialValue: V,
86 targetValue: V,
87 initialVelocity: V
88 ): V
89
90 /**
91 * Calculates the duration of an animation. For duration-based animations, this will return the
92 * pre-defined duration. For physics-based animations, the duration will be estimated based on
93 * the physics configuration (such as spring stiffness, damping ratio, visibility threshold) as
94 * well as the [initialValue], [targetValue] values, and [initialVelocity].
95 *
96 * @param initialValue start value of the animation
97 * @param targetValue end value of the animation
98 * @param initialVelocity start velocity of the animation
99 */
100 @Suppress("MethodNameUnits")
101 public fun getDurationNanos(initialValue: V, targetValue: V, initialVelocity: V): Long
102
103 /**
104 * Calculates the end velocity of the animation with the provided start/end values, and start
105 * velocity. For duration-based animations, end velocity will be the velocity of the animation
106 * at the duration time. This is also the default assumption. However, for physics-based
107 * animations, end velocity is an [AnimationVector] of 0s.
108 *
109 * @param initialValue start value of the animation
110 * @param targetValue end value of the animation
111 * @param initialVelocity start velocity of the animation
112 */
113 public fun getEndVelocity(initialValue: V, targetValue: V, initialVelocity: V): V =
114 getVelocityFromNanos(
115 getDurationNanos(initialValue, targetValue, initialVelocity),
116 initialValue,
117 targetValue,
118 initialVelocity
119 )
120 }
121
122 /**
123 * Calculates the duration of an animation. For duration-based animations, this will return the
124 * pre-defined duration. For physics-based animations, the duration will be estimated based on the
125 * physics configuration (such as spring stiffness, damping ratio, visibility threshold) as well as
126 * the [initialValue], [targetValue] values, and [initialVelocity].
127 *
128 * @param initialValue start value of the animation
129 * @param targetValue end value of the animation
130 * @param initialVelocity start velocity of the animation
131 */
getDurationMillisnull132 internal fun <V : AnimationVector> VectorizedAnimationSpec<V>.getDurationMillis(
133 initialValue: V,
134 targetValue: V,
135 initialVelocity: V
136 ): Long = getDurationNanos(initialValue, targetValue, initialVelocity) / MillisToNanos
137
138 /**
139 * Calculates the value of the animation at given the playtime, with the provided start/end values,
140 * and start velocity.
141 *
142 * @param playTimeMillis time since the start of the animation
143 * @param start start value of the animation
144 * @param end end value of the animation
145 * @param startVelocity start velocity of the animation
146 */
147 // TODO: Move tests off this API
148 internal fun <V : AnimationVector> VectorizedAnimationSpec<V>.getValueFromMillis(
149 playTimeMillis: Long,
150 start: V,
151 end: V,
152 startVelocity: V
153 ): V = getValueFromNanos(playTimeMillis * MillisToNanos, start, end, startVelocity)
154
155 /**
156 * All the finite [VectorizedAnimationSpec]s implement this interface, including:
157 * [VectorizedKeyframesSpec], [VectorizedTweenSpec], [VectorizedRepeatableSpec],
158 * [VectorizedSnapSpec], [VectorizedSpringSpec], etc. The [VectorizedAnimationSpec] that does
159 * __not__ implement this is: [InfiniteRepeatableSpec].
160 */
161 @JvmDefaultWithCompatibility
162 public interface VectorizedFiniteAnimationSpec<V : AnimationVector> : VectorizedAnimationSpec<V> {
163 override val isInfinite: Boolean
164 get() = false
165 }
166
167 /** Base class for [VectorizedAnimationSpec]s that are based on a fixed [durationMillis]. */
168 @JvmDefaultWithCompatibility
169 public interface VectorizedDurationBasedAnimationSpec<V : AnimationVector> :
170 VectorizedFiniteAnimationSpec<V> {
171 /** duration is the amount of time while animation is not yet finished. */
172 public val durationMillis: Int
173
174 /** delay defines the amount of time that animation can be delayed. */
175 public val delayMillis: Int
176
177 @Suppress("MethodNameUnits")
getDurationNanosnull178 override fun getDurationNanos(initialValue: V, targetValue: V, initialVelocity: V): Long =
179 (delayMillis + durationMillis) * MillisToNanos
180 }
181
182 /**
183 * Clamps the input [playTime] to the duration range of the given
184 * [VectorizedDurationBasedAnimationSpec].
185 */
186 internal fun VectorizedDurationBasedAnimationSpec<*>.clampPlayTime(playTime: Long): Long {
187 return (playTime - delayMillis).fastCoerceIn(0, durationMillis.toLong())
188 }
189
190 /**
191 * [VectorizedKeyframesSpec] class manages the animation based on the values defined at different
192 * timestamps in the duration of the animation (i.e. different keyframes). Each keyframe can be
193 * provided via [keyframes] parameter. [VectorizedKeyframesSpec] allows very specific animation
194 * definitions with a precision to millisecond.
195 *
196 * Here's an example of creating a [VectorizedKeyframesSpec] animation: ([keyframes] and
197 * [KeyframesSpec.KeyframesSpecConfig] could make defining key frames much more readable.)
198 *
199 * val delay = 120
200 * val startValue = AnimationVector3D(100f, 200f, 300f)
201 * val endValue = AnimationVector3D(200f, 100f, 0f)
202 * val keyframes = VectorizedKeyframesSpec<AnimationVector3D>(
203 * keyframes = mutableMapOf (
204 * 0 to (startValue to LinearEasing),
205 * 100 to (startValue to FastOutLinearInEasing)
206 * ),
207 * durationMillis = 200,
208 * delayMillis = delay
209 * )
210 *
211 * The interpolation between each value is dictated by [VectorizedKeyframeSpecElementInfo.arcMode]
212 * on each keyframe. If no keyframe information is provided, [initialArcMode] is used.
213 *
214 * @see [KeyframesSpec]
215 */
216 public class VectorizedKeyframesSpec<V : AnimationVector>
217 internal constructor(
218 // List of all timestamps. Must include start (time = 0), end (time = durationMillis) and all
219 // other timestamps found in [keyframes].
220 private val timestamps: IntList,
221 private val keyframes: IntObjectMap<VectorizedKeyframeSpecElementInfo<V>>,
222 override val durationMillis: Int,
223 override val delayMillis: Int,
224 // Easing used for any segment of time not covered by [keyframes].
225 private val defaultEasing: Easing,
226 // The [ArcMode] used from time `0` until the first keyframe. So, it applies
227 // for the entire duration if [keyframes] is empty.
228 private val initialArcMode: ArcMode
229 ) : VectorizedDurationBasedAnimationSpec<V> {
230 /**
231 * @param keyframes a map from time to a value/easing function pair. The value in each entry
232 * defines the animation value at that time, and the easing curve is used in the interval
233 * starting from that time.
234 * @param durationMillis total duration of the animation
235 * @param delayMillis the amount of the time the animation should wait before it starts.
236 * Defaults to 0.
237 */
238 public constructor(
239 keyframes: Map<Int, Pair<V, Easing>>,
240 durationMillis: Int,
241 delayMillis: Int = 0
242 ) : this(
243 timestamps =
<lambda>null244 kotlin.run {
245 val times = MutableIntList(keyframes.size + 2)
246 keyframes.forEach { (t, _) -> times.add(t) }
247 if (!keyframes.containsKey(0)) {
248 times.add(0, 0)
249 }
250 if (!keyframes.containsKey(durationMillis)) {
251 times.add(durationMillis)
252 }
253 times.sort()
254 return@run times
255 },
256 keyframes =
<lambda>null257 kotlin.run {
258 val timeToInfoMap = MutableIntObjectMap<VectorizedKeyframeSpecElementInfo<V>>()
259 keyframes.forEach { (time, valueEasing) ->
260 timeToInfoMap[time] =
261 VectorizedKeyframeSpecElementInfo(
262 vectorValue = valueEasing.first,
263 easing = valueEasing.second,
264 arcMode = ArcMode.ArcLinear
265 )
266 }
267
268 return@run timeToInfoMap
269 },
270 durationMillis = durationMillis,
271 delayMillis = delayMillis,
272 defaultEasing = LinearEasing,
273 initialArcMode = ArcMode.ArcLinear
274 )
275
276 /**
277 * List of time range for the given keyframes.
278 *
279 * This will be used to do a faster lookup for the corresponding Easing curves.
280 */
281 private var modes: IntArray = EmptyIntArray
282 private var times: FloatArray = EmptyFloatArray
283 private var valueVector: V? = null
284 private var velocityVector: V? = null
285
286 // Objects for ArcSpline
287 private var lastInitialValue: V? = null
288 private var lastTargetValue: V? = null
289 private var posArray: FloatArray = EmptyFloatArray
290 private var slopeArray: FloatArray = EmptyFloatArray
291 private var arcSpline: ArcSpline = EmptyArcSpline
292
initnull293 private fun init(initialValue: V, targetValue: V, initialVelocity: V) {
294 var requiresArcSpline = arcSpline !== EmptyArcSpline
295
296 // Only need to initialize once
297 if (valueVector == null) {
298 valueVector = initialValue.newInstance()
299 velocityVector = initialVelocity.newInstance()
300
301 times = FloatArray(timestamps.size) { timestamps[it].toFloat() / SecondsToMillis }
302
303 modes =
304 IntArray(timestamps.size) {
305 val mode = (keyframes[timestamps[it]]?.arcMode ?: initialArcMode)
306 if (mode != ArcMode.ArcLinear) {
307 requiresArcSpline = true
308 }
309
310 mode.value
311 }
312 }
313
314 if (!requiresArcSpline) {
315 return
316 }
317
318 // Initialize variables dependent on initial and/or target value
319 if (
320 arcSpline === EmptyArcSpline ||
321 lastInitialValue != initialValue ||
322 lastTargetValue != targetValue
323 ) {
324 lastInitialValue = initialValue
325 lastTargetValue = targetValue
326
327 // Force to the next even dimension
328 val dimensionCount = initialValue.size % 2 + initialValue.size
329 posArray = FloatArray(dimensionCount)
330 slopeArray = FloatArray(dimensionCount)
331
332 // TODO(b/299477780): Re-use objects, after the first pass, only the initial and target
333 // may change, and only if the keyframes does not overwrite it
334 val values =
335 Array(timestamps.size) {
336 val timestamp = timestamps[it]
337 val info = keyframes[timestamp]
338 // Start (zero) and end (durationMillis) may not have been declared in
339 // keyframes
340 if (timestamp == 0 && info == null) {
341 FloatArray(dimensionCount) { i -> initialValue[i] }
342 } else if (timestamp == durationMillis && info == null) {
343 FloatArray(dimensionCount) { i -> targetValue[i] }
344 } else {
345 // All other values are guaranteed to exist
346 val vectorValue = info!!.vectorValue
347 FloatArray(dimensionCount) { i -> vectorValue[i] }
348 }
349 }
350 arcSpline = ArcSpline(arcModes = modes, timePoints = times, y = values)
351 }
352 }
353
354 /**
355 * @Throws IllegalStateException When the initial or final value to animate within a keyframe is
356 * missing.
357 */
getValueFromNanosnull358 override fun getValueFromNanos(
359 playTimeNanos: Long,
360 initialValue: V,
361 targetValue: V,
362 initialVelocity: V
363 ): V {
364 val playTimeMillis = playTimeNanos / MillisToNanos
365 val clampedPlayTime = clampPlayTime(playTimeMillis).toInt()
366
367 // If there is a key frame defined with the given time stamp, return that value
368 val keyframe = keyframes[clampedPlayTime]
369 if (keyframe != null) {
370 return keyframe.vectorValue
371 }
372
373 if (clampedPlayTime >= durationMillis) {
374 return targetValue
375 } else if (clampedPlayTime <= 0) {
376 return initialValue
377 }
378
379 init(initialValue, targetValue, initialVelocity)
380
381 // Cannot be null after calling init()
382 val valueVector = valueVector!!
383
384 // ArcSpline is only initialized when necessary
385 if (arcSpline !== EmptyArcSpline) {
386 // ArcSpline requires eased play time in seconds
387 val easedTime = getEasedTime(clampedPlayTime)
388
389 val posArray = posArray
390 arcSpline.getPos(time = easedTime, v = posArray)
391 for (i in posArray.indices) {
392 valueVector[i] = posArray[i]
393 }
394 return valueVector
395 }
396
397 // If ArcSpline is not required we do a simple linear interpolation
398 val index = findEntryForTimeMillis(clampedPlayTime)
399
400 // For the `lerp` method we need the eased time as a fraction
401 val easedTime = getEasedTimeFromIndex(index, clampedPlayTime, true)
402
403 val timestampStart = timestamps[index]
404 val startKeyframe = keyframes[timestampStart]
405 // Use initial value if it wasn't overwritten by the user
406 // This is always the correct fallback assuming timestamps and keyframes were populated
407 // as expected
408 val startValue: V = startKeyframe?.vectorValue ?: initialValue
409
410 val timestampEnd = timestamps[index + 1]
411 val endKeyframe = keyframes[timestampEnd]
412 // Use target value if it wasn't overwritten by the user
413 // This is always the correct fallback assuming timestamps and keyframes were populated
414 // as expected
415 val endValue: V = endKeyframe?.vectorValue ?: targetValue
416
417 for (i in 0 until valueVector.size) {
418 valueVector[i] = lerp(startValue[i], endValue[i], easedTime)
419 }
420 return valueVector
421 }
422
getVelocityFromNanosnull423 override fun getVelocityFromNanos(
424 playTimeNanos: Long,
425 initialValue: V,
426 targetValue: V,
427 initialVelocity: V
428 ): V {
429 val playTimeMillis = playTimeNanos / MillisToNanos
430 val clampedPlayTime = clampPlayTime(playTimeMillis)
431 if (clampedPlayTime < 0L) {
432 return initialVelocity
433 }
434
435 init(initialValue, targetValue, initialVelocity)
436
437 // Cannot be null after calling init()
438 val velocityVector = velocityVector!!
439
440 // ArcSpline is only initialized when necessary
441 if (arcSpline !== EmptyArcSpline) {
442 val easedTime = getEasedTime(clampedPlayTime.toInt())
443 val slopeArray = slopeArray
444 arcSpline.getSlope(time = easedTime, v = slopeArray)
445 for (i in slopeArray.indices) {
446 velocityVector[i] = slopeArray[i]
447 }
448 return velocityVector
449 }
450
451 // Velocity calculation when ArcSpline is not used
452 val startNum =
453 getValueFromMillis(clampedPlayTime - 1, initialValue, targetValue, initialVelocity)
454 val endNum = getValueFromMillis(clampedPlayTime, initialValue, targetValue, initialVelocity)
455 for (i in 0 until startNum.size) {
456 velocityVector[i] = (startNum[i] - endNum[i]) * 1000f
457 }
458 return velocityVector
459 }
460
getEasedTimenull461 private fun getEasedTime(timeMillis: Int): Float {
462 // There's no promise on the nature of the given time, so we need to search for the correct
463 // time range at every call
464 val index = findEntryForTimeMillis(timeMillis)
465 return getEasedTimeFromIndex(index, timeMillis, false)
466 }
467
getEasedTimeFromIndexnull468 private fun getEasedTimeFromIndex(index: Int, timeMillis: Int, asFraction: Boolean): Float {
469 if (index >= timestamps.lastIndex) {
470 // Return the same value. This may only happen at the end of the animation.
471 return timeMillis.toFloat() / SecondsToMillis
472 }
473 val timeMin = timestamps[index]
474 val timeMax = timestamps[index + 1]
475
476 if (timeMillis == timeMin) {
477 return timeMin.toFloat() / SecondsToMillis
478 }
479
480 val timeRange = timeMax - timeMin
481 val easing = keyframes[timeMin]?.easing ?: defaultEasing
482 val rawFraction = (timeMillis - timeMin).toFloat() / timeRange
483 val easedFraction = easing.transform(rawFraction)
484
485 if (asFraction) {
486 return easedFraction
487 }
488 return (timeRange * easedFraction + timeMin) / SecondsToMillis
489 }
490
491 /**
492 * Returns the entry index such that:
493 *
494 * [timeMillis] >= Entry(i).key && [timeMillis] < Entry(i+1).key
495 */
findEntryForTimeMillisnull496 private fun findEntryForTimeMillis(timeMillis: Int): Int {
497 val index = timestamps.binarySearch(timeMillis)
498 return if (index < -1) -(index + 2) else index
499 }
500 }
501
502 internal data class VectorizedKeyframeSpecElementInfo<V : AnimationVector>(
503 val vectorValue: V,
504 val easing: Easing,
505 val arcMode: ArcMode
506 )
507
508 /**
509 * Interpolation mode for Arc-based animation spec.
510 *
511 * @see ArcAbove
512 * @see ArcBelow
513 * @see ArcLinear
514 * @see ArcAnimationSpec
515 */
516 @JvmInline
517 public value class ArcMode internal constructor(internal val value: Int) {
518
519 public companion object {
520 /**
521 * Interpolates using a quarter of an Ellipse where the curve is "above" the center of the
522 * Ellipse.
523 */
524 public val ArcAbove: ArcMode = ArcMode(ArcSplineArcAbove)
525
526 /**
527 * Interpolates using a quarter of an Ellipse where the curve is "below" the center of the
528 * Ellipse.
529 */
530 public val ArcBelow: ArcMode = ArcMode(ArcSplineArcBelow)
531
532 /**
533 * An [ArcMode] that forces linear interpolation.
534 *
535 * You'll likely only use this mode within a keyframe.
536 */
537 public val ArcLinear: ArcMode = ArcMode(ArcSplineArcStartLinear)
538 }
539 }
540
541 /**
542 * [VectorizedSnapSpec] immediately snaps the animating value to the end value.
543 *
544 * @param delayMillis the amount of time (in milliseconds) that the animation should wait before it
545 * starts. Defaults to 0.
546 */
547 public class VectorizedSnapSpec<V : AnimationVector>(override val delayMillis: Int = 0) :
548 VectorizedDurationBasedAnimationSpec<V> {
549
getValueFromNanosnull550 override fun getValueFromNanos(
551 playTimeNanos: Long,
552 initialValue: V,
553 targetValue: V,
554 initialVelocity: V
555 ): V {
556 return if (playTimeNanos < delayMillis * MillisToNanos) {
557 initialValue
558 } else {
559 targetValue
560 }
561 }
562
getVelocityFromNanosnull563 override fun getVelocityFromNanos(
564 playTimeNanos: Long,
565 initialValue: V,
566 targetValue: V,
567 initialVelocity: V
568 ): V {
569 return initialVelocity
570 }
571
572 override val durationMillis: Int
573 get() = 0
574 }
575
576 /**
577 * This animation takes another [VectorizedDurationBasedAnimationSpec] and plays it __infinite__
578 * times.
579 *
580 * initialStartOffset can be used to either delay the start of the animation or to fast forward the
581 * animation to a given play time. This start offset will **not** be repeated, whereas the delay in
582 * the [animation] (if any) will be repeated. By default, the amount of offset is 0.
583 *
584 * @param animation the [VectorizedAnimationSpec] describing each repetition iteration.
585 * @param repeatMode whether animation should repeat by starting from the beginning (i.e.
586 * [RepeatMode.Restart]) or from the end (i.e. [RepeatMode.Reverse])
587 * @param initialStartOffset offsets the start of the animation
588 */
589 public class VectorizedInfiniteRepeatableSpec<V : AnimationVector>(
590 private val animation: VectorizedDurationBasedAnimationSpec<V>,
591 private val repeatMode: RepeatMode = RepeatMode.Restart,
592 initialStartOffset: StartOffset = StartOffset(0)
593 ) : VectorizedAnimationSpec<V> {
594 @Deprecated(
595 level = DeprecationLevel.HIDDEN,
596 message =
597 "This method has been deprecated in favor of the constructor that" +
598 " accepts start offset."
599 )
600 public constructor(
601 animation: VectorizedDurationBasedAnimationSpec<V>,
602 repeatMode: RepeatMode = RepeatMode.Restart
603 ) : this(animation, repeatMode, StartOffset(0))
604
605 override val isInfinite: Boolean
606 get() = true
607
608 /** Single iteration duration */
609 internal val durationNanos: Long =
610 (animation.delayMillis + animation.durationMillis) * MillisToNanos
611
612 private val initialOffsetNanos = initialStartOffset.value * MillisToNanos
613
repetitionPlayTimeNanosnull614 private fun repetitionPlayTimeNanos(playTimeNanos: Long): Long {
615 if (playTimeNanos + initialOffsetNanos <= 0) {
616 return 0
617 } else {
618 val postOffsetPlayTimeNanos = playTimeNanos + initialOffsetNanos
619 val repeatsCount = postOffsetPlayTimeNanos / durationNanos
620 if (repeatMode == RepeatMode.Restart || repeatsCount % 2 == 0L) {
621 return postOffsetPlayTimeNanos - repeatsCount * durationNanos
622 } else {
623 return (repeatsCount + 1) * durationNanos - postOffsetPlayTimeNanos
624 }
625 }
626 }
627
repetitionStartVelocitynull628 private fun repetitionStartVelocity(
629 playTimeNanos: Long,
630 start: V,
631 startVelocity: V,
632 end: V
633 ): V =
634 if (playTimeNanos + initialOffsetNanos > durationNanos) {
635 // Start velocity of the 2nd and subsequent iteration will be the velocity at the end
636 // of the first iteration, instead of the initial velocity.
637 animation.getVelocityFromNanos(
638 playTimeNanos = durationNanos - initialOffsetNanos,
639 initialValue = start,
640 targetValue = end,
641 initialVelocity = startVelocity
642 )
643 } else {
644 startVelocity
645 }
646
getValueFromNanosnull647 override fun getValueFromNanos(
648 playTimeNanos: Long,
649 initialValue: V,
650 targetValue: V,
651 initialVelocity: V
652 ): V {
653 return animation.getValueFromNanos(
654 repetitionPlayTimeNanos(playTimeNanos),
655 initialValue,
656 targetValue,
657 repetitionStartVelocity(playTimeNanos, initialValue, initialVelocity, targetValue)
658 )
659 }
660
getVelocityFromNanosnull661 override fun getVelocityFromNanos(
662 playTimeNanos: Long,
663 initialValue: V,
664 targetValue: V,
665 initialVelocity: V
666 ): V {
667 return animation.getVelocityFromNanos(
668 repetitionPlayTimeNanos(playTimeNanos),
669 initialValue,
670 targetValue,
671 repetitionStartVelocity(playTimeNanos, initialValue, initialVelocity, targetValue)
672 )
673 }
674
675 @Suppress("MethodNameUnits")
getDurationNanosnull676 override fun getDurationNanos(initialValue: V, targetValue: V, initialVelocity: V): Long =
677 Long.MAX_VALUE
678 }
679
680 /**
681 * This animation takes another [VectorizedDurationBasedAnimationSpec] and plays it [iterations]
682 * times. For infinitely repeating animation spec, [VectorizedInfiniteRepeatableSpec] is
683 * recommended.
684 *
685 * __Note__: When repeating in the [RepeatMode.Reverse] mode, it's highly recommended to have an
686 * __odd__ number of iterations. Otherwise, the animation may jump to the end value when it finishes
687 * the last iteration.
688 *
689 * initialStartOffset can be used to either delay the start of the animation or to fast forward the
690 * animation to a given play time. This start offset will **not** be repeated, whereas the delay in
691 * the [animation] (if any) will be repeated. By default, the amount of offset is 0.
692 *
693 * @param iterations the count of iterations. Should be at least 1.
694 * @param animation the [VectorizedAnimationSpec] describing each repetition iteration.
695 * @param repeatMode whether animation should repeat by starting from the beginning (i.e.
696 * [RepeatMode.Restart]) or from the end (i.e. [RepeatMode.Reverse])
697 * @param initialStartOffset offsets the start of the animation
698 */
699 public class VectorizedRepeatableSpec<V : AnimationVector>(
700 private val iterations: Int,
701 private val animation: VectorizedDurationBasedAnimationSpec<V>,
702 private val repeatMode: RepeatMode = RepeatMode.Restart,
703 initialStartOffset: StartOffset = StartOffset(0)
704 ) : VectorizedFiniteAnimationSpec<V> {
705 @Deprecated(
706 level = DeprecationLevel.HIDDEN,
707 message =
708 "This method has been deprecated in favor of the constructor that accepts" +
709 " start offset."
710 )
711 public constructor(
712 iterations: Int,
713 animation: VectorizedDurationBasedAnimationSpec<V>,
714 repeatMode: RepeatMode = RepeatMode.Restart
715 ) : this(iterations, animation, repeatMode, StartOffset(0))
716
717 init {
718 if (iterations < 1) {
719 throw IllegalArgumentException("Iterations count can't be less than 1")
720 }
721 }
722
723 // Per-iteration duration
724 internal val durationNanos: Long =
725 (animation.delayMillis + animation.durationMillis) * MillisToNanos
726
727 // Fast forward amount. Delay type => negative offset
728 private val initialOffsetNanos = initialStartOffset.value * MillisToNanos
729
730 private fun repetitionPlayTimeNanos(playTimeNanos: Long): Long {
731 if (playTimeNanos + initialOffsetNanos <= 0) {
732 return 0
733 } else {
734 val postOffsetPlayTimeNanos = playTimeNanos + initialOffsetNanos
735 val repeatsCount = min(postOffsetPlayTimeNanos / durationNanos, iterations - 1L)
736 return if (repeatMode == RepeatMode.Restart || repeatsCount % 2 == 0L) {
737 postOffsetPlayTimeNanos - repeatsCount * durationNanos
738 } else {
739 (repeatsCount + 1) * durationNanos - postOffsetPlayTimeNanos
740 }
741 }
742 }
743
744 private fun repetitionStartVelocity(
745 playTimeNanos: Long,
746 start: V,
747 startVelocity: V,
748 end: V
749 ): V =
750 if (playTimeNanos + initialOffsetNanos > durationNanos) {
751 // Start velocity of the 2nd and subsequent iteration will be the velocity at the end
752 // of the first iteration, instead of the initial velocity.
753 getVelocityFromNanos(durationNanos - initialOffsetNanos, start, startVelocity, end)
754 } else startVelocity
755
756 override fun getValueFromNanos(
757 playTimeNanos: Long,
758 initialValue: V,
759 targetValue: V,
760 initialVelocity: V
761 ): V {
762 return animation.getValueFromNanos(
763 repetitionPlayTimeNanos(playTimeNanos),
764 initialValue,
765 targetValue,
766 repetitionStartVelocity(playTimeNanos, initialValue, initialVelocity, targetValue)
767 )
768 }
769
770 override fun getVelocityFromNanos(
771 playTimeNanos: Long,
772 initialValue: V,
773 targetValue: V,
774 initialVelocity: V
775 ): V {
776 return animation.getVelocityFromNanos(
777 repetitionPlayTimeNanos(playTimeNanos),
778 initialValue,
779 targetValue,
780 repetitionStartVelocity(playTimeNanos, initialValue, initialVelocity, targetValue)
781 )
782 }
783
784 @Suppress("MethodNameUnits")
785 override fun getDurationNanos(initialValue: V, targetValue: V, initialVelocity: V): Long {
786 return iterations * durationNanos - initialOffsetNanos
787 }
788 }
789
790 /** Physics class contains a number of recommended configurations for physics animations. */
791 public object Spring {
792 /** Stiffness constant for extremely stiff spring */
793 public const val StiffnessHigh: Float = 10_000f
794
795 /**
796 * Stiffness constant for medium stiff spring. This is the default stiffness for spring force.
797 */
798 public const val StiffnessMedium: Float = 1500f
799
800 /**
801 * Stiffness constant for medium-low stiff spring. This is the default stiffness for springs
802 * used in enter/exit transitions.
803 */
804 public const val StiffnessMediumLow: Float = 400f
805
806 /** Stiffness constant for a spring with low stiffness. */
807 public const val StiffnessLow: Float = 200f
808
809 /** Stiffness constant for a spring with very low stiffness. */
810 public const val StiffnessVeryLow: Float = 50f
811
812 /**
813 * Damping ratio for a very bouncy spring. Note for under-damped springs (i.e. damping ratio <
814 * 1), the lower the damping ratio, the more bouncy the spring.
815 */
816 public const val DampingRatioHighBouncy: Float = 0.2f
817
818 /**
819 * Damping ratio for a medium bouncy spring. This is also the default damping ratio for spring
820 * force. Note for under-damped springs (i.e. damping ratio < 1), the lower the damping ratio,
821 * the more bouncy the spring.
822 */
823 public const val DampingRatioMediumBouncy: Float = 0.5f
824
825 /**
826 * Damping ratio for a spring with low bounciness. Note for under-damped springs (i.e. damping
827 * ratio < 1), the lower the damping ratio, the higher the bounciness.
828 */
829 public const val DampingRatioLowBouncy: Float = 0.75f
830
831 /**
832 * Damping ratio for a spring with no bounciness. This damping ratio will create a critically
833 * damped spring that returns to equilibrium within the shortest amount of time without
834 * oscillating.
835 */
836 public const val DampingRatioNoBouncy: Float = 1f
837
838 /** Default cutoff for rounding off physics based animations */
839 public const val DefaultDisplacementThreshold: Float = 0.01f
840 }
841
842 /** Internal data structure for storing different FloatAnimations for different dimensions. */
843 internal interface Animations {
getnull844 operator fun get(index: Int): FloatAnimationSpec
845 }
846
847 /**
848 * [VectorizedSpringSpec] uses spring animations to animate (each dimension of) [AnimationVector]s.
849 */
850 public class VectorizedSpringSpec<V : AnimationVector>
851 private constructor(
852 public val dampingRatio: Float,
853 public val stiffness: Float,
854 anims: Animations
855 ) : VectorizedFiniteAnimationSpec<V> by VectorizedFloatAnimationSpec(anims) {
856
857 /**
858 * Creates a [VectorizedSpringSpec] that uses the same spring constants (i.e. [dampingRatio] and
859 * [stiffness] on all dimensions. The optional [visibilityThreshold] defines when the animation
860 * should be considered to be visually close enough to target to stop. By default,
861 * [Spring.DefaultDisplacementThreshold] is used on all dimensions of the [AnimationVector].
862 *
863 * @param dampingRatio damping ratio of the spring. [Spring.DampingRatioNoBouncy] by default.
864 * @param stiffness stiffness of the spring. [Spring.StiffnessMedium] by default.
865 * @param visibilityThreshold specifies the visibility threshold for each dimension.
866 */
867 public constructor(
868 dampingRatio: Float = Spring.DampingRatioNoBouncy,
869 stiffness: Float = Spring.StiffnessMedium,
870 visibilityThreshold: V? = null
871 ) : this(
872 dampingRatio,
873 stiffness,
874 createSpringAnimations(visibilityThreshold, dampingRatio, stiffness)
875 )
876 }
877
createSpringAnimationsnull878 private fun <V : AnimationVector> createSpringAnimations(
879 visibilityThreshold: V?,
880 dampingRatio: Float,
881 stiffness: Float
882 ): Animations {
883 return if (visibilityThreshold != null) {
884 object : Animations {
885 private val anims =
886 Array(visibilityThreshold.size) { index ->
887 FloatSpringSpec(dampingRatio, stiffness, visibilityThreshold[index])
888 }
889
890 override fun get(index: Int): FloatSpringSpec = anims[index]
891 }
892 } else {
893 object : Animations {
894 private val anim = FloatSpringSpec(dampingRatio, stiffness)
895
896 override fun get(index: Int): FloatSpringSpec = anim
897 }
898 }
899 }
900
901 /**
902 * [VectorizedTweenSpec] animates a [AnimationVector] value by interpolating the start and end
903 * value, in the given [durationMillis] using the given [easing] curve.
904 *
905 * @param durationMillis duration of the [VectorizedTweenSpec] animation. Defaults to
906 * [DefaultDurationMillis].
907 * @param delayMillis the amount of time the animation should wait before it starts running, 0 by
908 * default.
909 * @param easing the easing curve used by the animation. [FastOutSlowInEasing] by default.
910 */
911 // TODO: Support different tween on different dimens
912 public class VectorizedTweenSpec<V : AnimationVector>(
913 override val durationMillis: Int = DefaultDurationMillis,
914 override val delayMillis: Int = 0,
915 public val easing: Easing = FastOutSlowInEasing
916 ) : VectorizedDurationBasedAnimationSpec<V> {
917
918 private val anim =
919 VectorizedFloatAnimationSpec<V>(FloatTweenSpec(durationMillis, delayMillis, easing))
920
getValueFromNanosnull921 override fun getValueFromNanos(
922 playTimeNanos: Long,
923 initialValue: V,
924 targetValue: V,
925 initialVelocity: V
926 ): V {
927 return anim.getValueFromNanos(playTimeNanos, initialValue, targetValue, initialVelocity)
928 }
929
getVelocityFromNanosnull930 override fun getVelocityFromNanos(
931 playTimeNanos: Long,
932 initialValue: V,
933 targetValue: V,
934 initialVelocity: V
935 ): V {
936 return anim.getVelocityFromNanos(playTimeNanos, initialValue, targetValue, initialVelocity)
937 }
938 }
939
940 /**
941 * A convenient implementation of [VectorizedFloatAnimationSpec] that turns a [FloatAnimationSpec]
942 * into a multi-dimensional [VectorizedFloatAnimationSpec], by using the same [FloatAnimationSpec]
943 * on each dimension of the [AnimationVector] that is being animated.
944 */
945 public class VectorizedFloatAnimationSpec<V : AnimationVector>
946 internal constructor(private val anims: Animations) : VectorizedFiniteAnimationSpec<V> {
947 private lateinit var valueVector: V
948 private lateinit var velocityVector: V
949 private lateinit var endVelocityVector: V
950
951 /**
952 * Creates a [VectorizedAnimationSpec] from a [FloatAnimationSpec]. The given
953 * [FloatAnimationSpec] will be used to animate every dimension of the [AnimationVector].
954 *
955 * @param anim the animation spec for animating each dimension of the [AnimationVector]
956 */
957 public constructor(
958 anim: FloatAnimationSpec
959 ) : this(
960 object : Animations {
getnull961 override fun get(index: Int): FloatAnimationSpec {
962 return anim
963 }
964 }
965 )
966
getValueFromNanosnull967 override fun getValueFromNanos(
968 playTimeNanos: Long,
969 initialValue: V,
970 targetValue: V,
971 initialVelocity: V
972 ): V {
973 if (!::valueVector.isInitialized) {
974 valueVector = initialValue.newInstance()
975 }
976 for (i in 0 until valueVector.size) {
977 valueVector[i] =
978 anims[i].getValueFromNanos(
979 playTimeNanos,
980 initialValue[i],
981 targetValue[i],
982 initialVelocity[i]
983 )
984 }
985 return valueVector
986 }
987
getVelocityFromNanosnull988 override fun getVelocityFromNanos(
989 playTimeNanos: Long,
990 initialValue: V,
991 targetValue: V,
992 initialVelocity: V
993 ): V {
994 if (!::velocityVector.isInitialized) {
995 velocityVector = initialVelocity.newInstance()
996 }
997 for (i in 0 until velocityVector.size) {
998 velocityVector[i] =
999 anims[i].getVelocityFromNanos(
1000 playTimeNanos,
1001 initialValue[i],
1002 targetValue[i],
1003 initialVelocity[i]
1004 )
1005 }
1006 return velocityVector
1007 }
1008
getEndVelocitynull1009 override fun getEndVelocity(initialValue: V, targetValue: V, initialVelocity: V): V {
1010 if (!::endVelocityVector.isInitialized) {
1011 endVelocityVector = initialVelocity.newInstance()
1012 }
1013 for (i in 0 until endVelocityVector.size) {
1014 endVelocityVector[i] =
1015 anims[i].getEndVelocity(initialValue[i], targetValue[i], initialVelocity[i])
1016 }
1017 return endVelocityVector
1018 }
1019
1020 @Suppress("MethodNameUnits")
getDurationNanosnull1021 override fun getDurationNanos(initialValue: V, targetValue: V, initialVelocity: V): Long {
1022 var maxDuration = 0L
1023 for (i in 0 until initialValue.size) {
1024 maxDuration =
1025 maxOf(
1026 maxDuration,
1027 anims[i].getDurationNanos(initialValue[i], targetValue[i], initialVelocity[i])
1028 )
1029 }
1030 return maxDuration
1031 }
1032 }
1033
1034 private val EmptyIntArray: IntArray = IntArray(0)
1035 private val EmptyFloatArray: FloatArray = FloatArray(0)
1036 private val EmptyArcSpline =
1037 ArcSpline(IntArray(2), FloatArray(2), arrayOf(FloatArray(2), FloatArray(2)))
1038