1 /*
<lambda>null2  * 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.annotation.IntRange
21 import androidx.collection.MutableIntList
22 import androidx.collection.MutableIntObjectMap
23 import androidx.collection.emptyIntObjectMap
24 import androidx.collection.intListOf
25 import androidx.collection.mutableIntObjectMapOf
26 import androidx.compose.animation.core.AnimationConstants.DefaultDurationMillis
27 import androidx.compose.animation.core.ArcMode.Companion.ArcBelow
28 import androidx.compose.animation.core.ArcMode.Companion.ArcLinear
29 import androidx.compose.animation.core.KeyframesSpec.KeyframesSpecConfig
30 import androidx.compose.runtime.Immutable
31 import androidx.compose.runtime.Stable
32 import androidx.compose.ui.geometry.Offset
33 import androidx.compose.ui.unit.IntOffset
34 import androidx.compose.ui.util.fastRoundToInt
35 import kotlin.math.abs
36 
37 public object AnimationConstants {
38     /** The default duration used in [VectorizedAnimationSpec]s and [AnimationSpec]. */
39     public const val DefaultDurationMillis: Int = 300
40 
41     /** The value that is used when the animation time is not yet set. */
42     public const val UnspecifiedTime: Long = Long.MIN_VALUE
43 }
44 
45 /**
46  * [AnimationSpec] stores the specification of an animation, including 1) the data type to be
47  * animated, and 2) the animation configuration (i.e. [VectorizedAnimationSpec]) that will be used
48  * once the data (of type [T]) has been converted to [AnimationVector].
49  *
50  * Any type [T] can be animated by the system as long as a [TwoWayConverter] is supplied to convert
51  * the data type [T] from and to an [AnimationVector]. There are a number of converters available
52  * out of the box. For example, to animate [androidx.compose.ui.unit.IntOffset] the system uses
53  * [IntOffset.VectorConverter][IntOffset.Companion.VectorConverter] to convert the object to
54  * [AnimationVector2D], so that both x and y dimensions are animated independently with separate
55  * velocity tracking. This enables multidimensional objects to be animated in a true
56  * multi-dimensional way. It is particularly useful for smoothly handling animation interruptions
57  * (such as when the target changes during the animation).
58  */
59 public interface AnimationSpec<T> {
60     /**
61      * Creates a [VectorizedAnimationSpec] with the given [TwoWayConverter].
62      *
63      * The underlying animation system operates on [AnimationVector]s. [T] will be converted to
64      * [AnimationVector] to animate. [VectorizedAnimationSpec] describes how the converted
65      * [AnimationVector] should be animated. E.g. The animation could simply interpolate between the
66      * start and end values (i.e.[TweenSpec]), or apply spring physics to produce the motion (i.e.
67      * [SpringSpec]), etc)
68      *
69      * @param converter converts the type [T] from and to [AnimationVector] type
70      */
vectorizenull71     public fun <V : AnimationVector> vectorize(
72         converter: TwoWayConverter<T, V>
73     ): VectorizedAnimationSpec<V>
74 }
75 
76 /**
77  * [FiniteAnimationSpec] is the interface that all non-infinite [AnimationSpec]s implement,
78  * including: [TweenSpec], [SpringSpec], [KeyframesSpec], [RepeatableSpec], [SnapSpec], etc. By
79  * definition, [InfiniteRepeatableSpec] __does not__ implement this interface.
80  *
81  * @see [InfiniteRepeatableSpec]
82  */
83 public interface FiniteAnimationSpec<T> : AnimationSpec<T> {
84     override fun <V : AnimationVector> vectorize(
85         converter: TwoWayConverter<T, V>
86     ): VectorizedFiniteAnimationSpec<V>
87 }
88 
89 /**
90  * Creates a TweenSpec configured with the given duration, delay, and easing curve.
91  *
92  * @param durationMillis duration of the [VectorizedTweenSpec] animation.
93  * @param delay the number of milliseconds the animation waits before starting, 0 by default.
94  * @param easing the easing curve used by the animation. [FastOutSlowInEasing] by default.
95  */
96 @Immutable
97 public class TweenSpec<T>(
98     public val durationMillis: Int = DefaultDurationMillis,
99     public val delay: Int = 0,
100     public val easing: Easing = FastOutSlowInEasing
101 ) : DurationBasedAnimationSpec<T> {
102 
vectorizenull103     override fun <V : AnimationVector> vectorize(
104         converter: TwoWayConverter<T, V>
105     ): VectorizedTweenSpec<V> = VectorizedTweenSpec<V>(durationMillis, delay, easing)
106 
107     override fun equals(other: Any?): Boolean =
108         if (other is TweenSpec<*>) {
109             other.durationMillis == this.durationMillis &&
110                 other.delay == this.delay &&
111                 other.easing == this.easing
112         } else {
113             false
114         }
115 
hashCodenull116     override fun hashCode(): Int {
117         return (durationMillis * 31 + easing.hashCode()) * 31 + delay
118     }
119 }
120 
121 /**
122  * This describes [AnimationSpec]s that are based on a fixed duration, such as [KeyframesSpec],
123  * [TweenSpec], and [SnapSpec]. These duration based specs can repeated when put into a
124  * [RepeatableSpec].
125  */
126 public interface DurationBasedAnimationSpec<T> : FiniteAnimationSpec<T> {
vectorizenull127     override fun <V : AnimationVector> vectorize(
128         converter: TwoWayConverter<T, V>
129     ): VectorizedDurationBasedAnimationSpec<V>
130 }
131 
132 /**
133  * Creates a [SpringSpec] that uses the given spring constants (i.e. [dampingRatio] and [stiffness].
134  * The optional [visibilityThreshold] defines when the animation should be considered to be visually
135  * close enough to round off to its target.
136  *
137  * @param dampingRatio damping ratio of the spring. [Spring.DampingRatioNoBouncy] by default.
138  * @param stiffness stiffness of the spring. [Spring.StiffnessMedium] by default.
139  * @param visibilityThreshold specifies the visibility threshold
140  */
141 // TODO: annotate damping/stiffness with FloatRange
142 @Immutable
143 public class SpringSpec<T>(
144     public val dampingRatio: Float = Spring.DampingRatioNoBouncy,
145     public val stiffness: Float = Spring.StiffnessMedium,
146     public val visibilityThreshold: T? = null
147 ) : FiniteAnimationSpec<T> {
148 
149     override fun <V : AnimationVector> vectorize(
150         converter: TwoWayConverter<T, V>
151     ): VectorizedSpringSpec<V> =
152         VectorizedSpringSpec(dampingRatio, stiffness, converter.convert(visibilityThreshold))
153 
154     override fun equals(other: Any?): Boolean =
155         if (other is SpringSpec<*>) {
156             other.dampingRatio == this.dampingRatio &&
157                 other.stiffness == this.stiffness &&
158                 other.visibilityThreshold == this.visibilityThreshold
159         } else {
160             false
161         }
162 
163     override fun hashCode(): Int =
164         (visibilityThreshold.hashCode() * 31 + dampingRatio.hashCode()) * 31 + stiffness.hashCode()
165 }
166 
convertnull167 private fun <T, V : AnimationVector> TwoWayConverter<T, V>.convert(data: T?): V? {
168     if (data == null) {
169         return null
170     } else {
171         return convertToVector(data)
172     }
173 }
174 
175 /**
176  * [DurationBasedAnimationSpec] that interpolates 2-dimensional values using arcs of quarter of an
177  * Ellipse.
178  *
179  * To interpolate with [keyframes] use [KeyframesSpecConfig.using] with an [ArcMode].
180  *
181  * &nbsp;
182  *
183  * As such, it's recommended that [ArcAnimationSpec] is only used for positional values such as:
184  * [Offset], [IntOffset] or [androidx.compose.ui.unit.DpOffset].
185  *
186  * &nbsp;
187  *
188  * The orientation of the arc is indicated by the given [mode].
189  *
190  * Do note, that if the target value being animated only changes in one dimension, you'll only be
191  * able to get a linear curve.
192  *
193  * Similarly, one-dimensional values will always only interpolate on a linear curve.
194  *
195  * @param mode Orientation of the arc.
196  * @param durationMillis Duration of the animation. [DefaultDurationMillis] by default.
197  * @param delayMillis Time the animation waits before starting. 0 by default.
198  * @param easing [Easing] applied on the animation curve. [FastOutSlowInEasing] by default.
199  * @sample androidx.compose.animation.core.samples.OffsetArcAnimationSpec
200  * @see ArcMode
201  * @see keyframes
202  */
203 @ExperimentalAnimationSpecApi
204 @Immutable
205 public class ArcAnimationSpec<T>(
206     public val mode: ArcMode = ArcBelow,
207     public val durationMillis: Int = DefaultDurationMillis,
208     public val delayMillis: Int = 0,
209     public val easing: Easing = FastOutSlowInEasing // Same default as tween()
210 ) : DurationBasedAnimationSpec<T> {
vectorizenull211     override fun <V : AnimationVector> vectorize(
212         converter: TwoWayConverter<T, V>
213     ): VectorizedDurationBasedAnimationSpec<V> =
214         VectorizedKeyframesSpec(
215             timestamps = intListOf(0, durationMillis),
216             keyframes = emptyIntObjectMap(),
217             durationMillis = durationMillis,
218             delayMillis = delayMillis,
219             defaultEasing = easing,
220             initialArcMode = mode
221         )
222 
223     override fun equals(other: Any?): Boolean {
224         if (this === other) return true
225         if (other !is ArcAnimationSpec<*>) return false
226 
227         if (mode != other.mode) return false
228         if (durationMillis != other.durationMillis) return false
229         if (delayMillis != other.delayMillis) return false
230         return easing == other.easing
231     }
232 
hashCodenull233     override fun hashCode(): Int {
234         var result = mode.hashCode()
235         result = 31 * result + durationMillis
236         result = 31 * result + delayMillis
237         result = 31 * result + easing.hashCode()
238         return result
239     }
240 }
241 
242 /**
243  * This class defines the two types of [StartOffset]: [StartOffsetType.Delay] and
244  * [StartOffsetType.FastForward]. [StartOffsetType.Delay] delays the start of the animation, whereas
245  * [StartOffsetType.FastForward] starts the animation right away from a given play time in the
246  * animation.
247  *
248  * @see repeatable
249  * @see infiniteRepeatable
250  * @see StartOffset
251  */
252 @kotlin.jvm.JvmInline
253 public value class StartOffsetType private constructor(internal val value: Int) {
254     public companion object {
255         /** Delays the start of the animation. */
256         public val Delay: StartOffsetType = StartOffsetType(-1)
257 
258         /** Fast forwards the animation to a given play time, and starts it immediately. */
259         public val FastForward: StartOffsetType = StartOffsetType(1)
260     }
261 }
262 
263 /**
264  * This class defines a start offset for [repeatable] and [infiniteRepeatable]. There are two types
265  * of start offsets: [StartOffsetType.Delay] and [StartOffsetType.FastForward].
266  * [StartOffsetType.Delay] delays the start of the animation, whereas [StartOffsetType.FastForward]
267  * fast forwards the animation to a given play time and starts it right away.
268  *
269  * @sample androidx.compose.animation.core.samples.InfiniteProgressIndicator
270  */
271 // This is an inline of Long so that when adding a StartOffset param to the end of constructor
272 // param list, it won't be confused with/clash with the mask param generated by constructors.
273 @kotlin.jvm.JvmInline
274 public value class StartOffset private constructor(internal val value: Long) {
275     /**
276      * This creates a start offset for [repeatable] and [infiniteRepeatable]. [offsetType] can be
277      * either of the following: [StartOffsetType.Delay] and [StartOffsetType.FastForward].
278      * [offsetType] defaults to [StartOffsetType.Delay].
279      *
280      * [StartOffsetType.Delay] delays the start of the animation by [offsetMillis], whereas
281      * [StartOffsetType.FastForward] starts the animation right away from [offsetMillis] in the
282      * animation.
283      */
284     public constructor(
285         offsetMillis: Int,
286         offsetType: StartOffsetType = StartOffsetType.Delay
287     ) : this((offsetMillis * offsetType.value).toLong())
288 
289     /** Returns the number of milliseconds to offset the start of the animation. */
290     public val offsetMillis: Int
291         get() = abs(this.value.toInt())
292 
293     /** Returns the offset type of the provided [StartOffset]. */
294     public val offsetType: StartOffsetType
295         get() =
296             when (this.value > 0) {
297                 true -> StartOffsetType.FastForward
298                 false -> StartOffsetType.Delay
299             }
300 }
301 
302 /**
303  * [RepeatableSpec] takes another [DurationBasedAnimationSpec] and plays it [iterations] times. For
304  * creating infinitely repeating animation spec, consider using [InfiniteRepeatableSpec].
305  *
306  * __Note__: When repeating in the [RepeatMode.Reverse] mode, it's highly recommended to have an
307  * __odd__ number of iterations. Otherwise, the animation may jump to the end value when it finishes
308  * the last iteration.
309  *
310  * [initialStartOffset] can be used to either delay the start of the animation or to fast forward
311  * the animation to a given play time. This start offset will **not** be repeated, whereas the delay
312  * in the [animation] (if any) will be repeated. By default, the amount of offset is 0.
313  *
314  * @param iterations the count of iterations. Should be at least 1.
315  * @param animation the [AnimationSpec] to be repeated
316  * @param repeatMode whether animation should repeat by starting from the beginning (i.e.
317  *   [RepeatMode.Restart]) or from the end (i.e. [RepeatMode.Reverse])
318  * @param initialStartOffset offsets the start of the animation
319  * @see repeatable
320  * @see InfiniteRepeatableSpec
321  * @see infiniteRepeatable
322  */
323 @Immutable
324 public class RepeatableSpec<T>(
325     public val iterations: Int,
326     public val animation: DurationBasedAnimationSpec<T>,
327     public val repeatMode: RepeatMode = RepeatMode.Restart,
328     public val initialStartOffset: StartOffset = StartOffset(0)
329 ) : FiniteAnimationSpec<T> {
330 
331     @Deprecated(level = DeprecationLevel.HIDDEN, message = "This constructor has been deprecated")
332     public constructor(
333         iterations: Int,
334         animation: DurationBasedAnimationSpec<T>,
335         repeatMode: RepeatMode = RepeatMode.Restart
336     ) : this(iterations, animation, repeatMode, StartOffset(0))
337 
vectorizenull338     override fun <V : AnimationVector> vectorize(
339         converter: TwoWayConverter<T, V>
340     ): VectorizedFiniteAnimationSpec<V> {
341         return VectorizedRepeatableSpec(
342             iterations,
343             animation.vectorize(converter),
344             repeatMode,
345             initialStartOffset
346         )
347     }
348 
equalsnull349     override fun equals(other: Any?): Boolean =
350         if (other is RepeatableSpec<*>) {
351             other.iterations == this.iterations &&
352                 other.animation == this.animation &&
353                 other.repeatMode == this.repeatMode &&
354                 other.initialStartOffset == this.initialStartOffset
355         } else {
356             false
357         }
358 
hashCodenull359     override fun hashCode(): Int {
360         return ((iterations * 31 + animation.hashCode()) * 31 + repeatMode.hashCode()) * 31 +
361             initialStartOffset.hashCode()
362     }
363 }
364 
365 /**
366  * [InfiniteRepeatableSpec] repeats the provided [animation] infinite amount of times. It will never
367  * naturally finish. This means the animation will only be stopped via some form of manual
368  * cancellation. When used with transition or other animation composables, the infinite animations
369  * will stop when the composable is removed from the compose tree.
370  *
371  * For non-infinite repeating animations, consider [RepeatableSpec].
372  *
373  * [initialStartOffset] can be used to either delay the start of the animation or to fast forward
374  * the animation to a given play time. This start offset will **not** be repeated, whereas the delay
375  * in the [animation] (if any) will be repeated. By default, the amount of offset is 0.
376  *
377  * @sample androidx.compose.animation.core.samples.InfiniteProgressIndicator
378  * @param animation the [AnimationSpec] to be repeated
379  * @param repeatMode whether animation should repeat by starting from the beginning (i.e.
380  *   [RepeatMode.Restart]) or from the end (i.e. [RepeatMode.Reverse])
381  * @param initialStartOffset offsets the start of the animation
382  * @see infiniteRepeatable
383  */
384 // TODO: Consider supporting repeating spring specs
385 public class InfiniteRepeatableSpec<T>(
386     public val animation: DurationBasedAnimationSpec<T>,
387     public val repeatMode: RepeatMode = RepeatMode.Restart,
388     public val initialStartOffset: StartOffset = StartOffset(0)
389 ) : AnimationSpec<T> {
390 
391     @Deprecated(level = DeprecationLevel.HIDDEN, message = "This constructor has been deprecated")
392     public constructor(
393         animation: DurationBasedAnimationSpec<T>,
394         repeatMode: RepeatMode = RepeatMode.Restart
395     ) : this(animation, repeatMode, StartOffset(0))
396 
vectorizenull397     override fun <V : AnimationVector> vectorize(
398         converter: TwoWayConverter<T, V>
399     ): VectorizedAnimationSpec<V> {
400         return VectorizedInfiniteRepeatableSpec(
401             animation.vectorize(converter),
402             repeatMode,
403             initialStartOffset
404         )
405     }
406 
equalsnull407     override fun equals(other: Any?): Boolean =
408         if (other is InfiniteRepeatableSpec<*>) {
409             other.animation == this.animation &&
410                 other.repeatMode == this.repeatMode &&
411                 other.initialStartOffset == this.initialStartOffset
412         } else {
413             false
414         }
415 
hashCodenull416     override fun hashCode(): Int {
417         return (animation.hashCode() * 31 + repeatMode.hashCode()) * 31 +
418             initialStartOffset.hashCode()
419     }
420 }
421 
422 /** Repeat mode for [RepeatableSpec] and [VectorizedRepeatableSpec]. */
423 public enum class RepeatMode {
424     /** [Restart] will restart the animation and animate from the start value to the end value. */
425     Restart,
426 
427     /** [Reverse] will reverse the last iteration as the animation repeats. */
428     Reverse
429 }
430 
431 /**
432  * [SnapSpec] describes a jump-cut type of animation. It immediately snaps the animating value to
433  * the end value.
434  *
435  * @param delay the amount of time (in milliseconds) that the animation should wait before it
436  *   starts. Defaults to 0.
437  */
438 @Immutable
439 public class SnapSpec<T>(public val delay: Int = 0) : DurationBasedAnimationSpec<T> {
vectorizenull440     override fun <V : AnimationVector> vectorize(
441         converter: TwoWayConverter<T, V>
442     ): VectorizedDurationBasedAnimationSpec<V> = VectorizedSnapSpec(delay)
443 
444     override fun equals(other: Any?): Boolean =
445         if (other is SnapSpec<*>) {
446             other.delay == this.delay
447         } else {
448             false
449         }
450 
hashCodenull451     override fun hashCode(): Int {
452         return delay
453     }
454 }
455 
456 /** Shared configuration class used as DSL for keyframe based animations. */
457 public sealed class KeyframesSpecBaseConfig<T, E : KeyframeBaseEntity<T>> {
458     /**
459      * Duration of the animation in milliseconds. The minimum is `0` and defaults to
460      * [DefaultDurationMillis]
461      */
462     @get:IntRange(from = 0L)
463     @setparam:IntRange(from = 0L)
464     public var durationMillis: Int = DefaultDurationMillis
465 
466     /**
467      * The amount of time that the animation should be delayed. The minimum is `0` and defaults
468      * to 0.
469      */
470     @get:IntRange(from = 0L) @setparam:IntRange(from = 0L) public var delayMillis: Int = 0
471 
472     internal val keyframes = mutableIntObjectMapOf<E>()
473 
474     /** Method used to delegate instantiation of [E] to implementing classes. */
createEntityFornull475     internal abstract fun createEntityFor(value: T): E
476 
477     /**
478      * Adds a keyframe so that animation value will be [this] at time: [timeStamp]. For example:
479      *
480      * @sample androidx.compose.animation.core.samples.floatAtSample
481      * @param timeStamp The time in the during when animation should reach value: [this], with a
482      *   minimum value of `0`.
483      * @return an instance of [E] so a custom [Easing] can be added by the [using] method.
484      */
485     // needed as `open` to guarantee binary compatibility in KeyframesSpecConfig
486     public open infix fun T.at(@IntRange(from = 0) timeStamp: Int): E {
487         val entity = createEntityFor(this)
488         keyframes[timeStamp] = entity
489         return entity
490     }
491 
492     /**
493      * Adds a keyframe so that the animation value will be the value specified at a fraction of the
494      * total [durationMillis] set. It's recommended that you always set [durationMillis] before
495      * calling [atFraction]. For example:
496      *
497      * @sample androidx.compose.animation.core.samples.floatAtFractionSample
498      * @param fraction The fraction when the animation should reach specified value.
499      * @return an instance of [E] so a custom [Easing] can be added by the [using] method
500      */
501     // needed as `open` to guarantee binary compatibility in KeyframesSpecConfig
atFractionnull502     public open infix fun T.atFraction(@FloatRange(from = 0.0, to = 1.0) fraction: Float): E {
503         return at((durationMillis * fraction).fastRoundToInt())
504     }
505 
506     /**
507      * Adds an [Easing] for the interval started with the just provided timestamp. For example: 0f
508      * at 50 using LinearEasing
509      *
510      * @sample androidx.compose.animation.core.samples.KeyframesBuilderWithEasing
511      * @param easing [Easing] to be used for the next interval.
512      * @return the same [E] instance so that other implementations can expand on the builder pattern
513      */
usingnull514     public infix fun E.using(easing: Easing): E {
515         this.easing = easing
516         return this
517     }
518 }
519 
520 /** Base holder class for building a keyframes animation. */
521 public sealed class KeyframeBaseEntity<T>(internal val value: T, internal var easing: Easing) {
toPairnull522     internal fun <V : AnimationVector> toPair(convertToVector: (T) -> V) =
523         convertToVector.invoke(value) to easing
524 }
525 
526 /**
527  * [KeyframesSpec] creates a [VectorizedKeyframesSpec] animation.
528  *
529  * [VectorizedKeyframesSpec] animates based on the values defined at different timestamps in the
530  * duration of the animation (i.e. different keyframes). Each keyframe can be defined using
531  * [KeyframesSpecConfig.at]. [VectorizedKeyframesSpec] allows very specific animation definitions
532  * with a precision to millisecond.
533  *
534  * @sample androidx.compose.animation.core.samples.FloatKeyframesBuilder
535  *
536  * For each interval, you may provide a custom [Easing] by use of the [KeyframesSpecConfig.using]
537  * function.
538  *
539  * @sample androidx.compose.animation.core.samples.KeyframesBuilderWithEasing
540  *
541  * By default, values are animated linearly from one interval to the next (similar to [tween]),
542  * however for 2-dimensional values you may animate them using arcs of quarter of an Ellipse with
543  * [KeyframesSpecConfig.using] and [ArcMode]:
544  *
545  * @sample androidx.compose.animation.core.samples.OffsetKeyframesWithArcsBuilder
546  *
547  * If instead, you wish to have a smooth curvy animation across all intervals, consider using
548  * [KeyframesWithSplineSpec].
549  */
550 @Immutable
551 public class KeyframesSpec<T>(public val config: KeyframesSpecConfig<T>) :
552     DurationBasedAnimationSpec<T> {
553     /**
554      * [KeyframesSpecConfig] stores a mutable configuration of the key frames, including
555      * [durationMillis], [delayMillis], and all the key frames. Each key frame defines what the
556      * animation value should be at a particular time. Once the key frames are fully configured, the
557      * [KeyframesSpecConfig] can be used to create a [KeyframesSpec].
558      *
559      * @sample androidx.compose.animation.core.samples.KeyframesBuilderForPosition
560      * @see keyframes
561      */
562     public class KeyframesSpecConfig<T> : KeyframesSpecBaseConfig<T, KeyframeEntity<T>>() {
563         @OptIn(ExperimentalAnimationSpecApi::class)
564         override fun createEntityFor(value: T): KeyframeEntity<T> = KeyframeEntity(value)
565 
566         /**
567          * Adds a keyframe so that animation value will be [this] at time: [timeStamp]. For example:
568          * 0.8f at 150 // ms
569          *
570          * @param timeStamp The time in the during when animation should reach value: [this], with a
571          *   minimum value of `0`.
572          * @return an [KeyframeEntity] so a custom [Easing] can be added by [with] method.
573          */
574         // TODO: Need a IntRange equivalent annotation
575         // overrides `at` for binary compatibility. It should explicitly return KeyframeEntity.
576         override infix fun T.at(@IntRange(from = 0) timeStamp: Int): KeyframeEntity<T> {
577             @OptIn(ExperimentalAnimationSpecApi::class)
578             return KeyframeEntity(this).also { keyframes[timeStamp] = it }
579         }
580 
581         /**
582          * Adds a keyframe so that the animation value will be the value specified at a fraction of
583          * the total [durationMillis] set. For example: 0.8f atFraction 0.50f // half of the overall
584          * duration set
585          *
586          * @param fraction The fraction when the animation should reach specified value.
587          * @return an [KeyframeEntity] so a custom [Easing] can be added by [with] method
588          */
589         // overrides `atFraction` for binary compatibility. It should explicitly return
590         // KeyframeEntity.
591         override infix fun T.atFraction(
592             @FloatRange(from = 0.0, to = 1.0) fraction: Float
593         ): KeyframeEntity<T> {
594             return at((durationMillis * fraction).fastRoundToInt())
595         }
596 
597         /**
598          * Adds an [Easing] for the interval started with the just provided timestamp. For example:
599          * 0f at 50 with LinearEasing
600          *
601          * @sample androidx.compose.animation.core.samples.KeyframesBuilderWithEasing
602          * @param easing [Easing] to be used for the next interval.
603          * @return the same [KeyframeEntity] instance so that other implementations can expand on
604          *   the builder pattern
605          */
606         @Deprecated(
607             message =
608                 "Use version that returns an instance of the entity so it can be re-used" +
609                     " in other keyframe builders.",
610             replaceWith = ReplaceWith("this using easing") // Expected usage pattern
611         )
612         public infix fun KeyframeEntity<T>.with(easing: Easing) {
613             this.easing = easing
614         }
615 
616         /**
617          * [ArcMode] applied from this keyframe to the next.
618          *
619          * Note that arc modes are meant for objects with even dimensions (such as [Offset] and its
620          * variants). Where each value pair is animated as an arc. So, if the object has odd
621          * dimensions the last value will always animate linearly.
622          *
623          * &nbsp;
624          *
625          * The order of each value in an object with multiple dimensions is given by the applied
626          * vector converter in [KeyframesSpec.vectorize].
627          *
628          * E.g.: [RectToVector] assigns its values as `[left, top, right, bottom]` so the pairs of
629          * dimensions animated as arcs are: `[left, top]` and `[right, bottom]`.
630          */
631         public infix fun KeyframeEntity<T>.using(arcMode: ArcMode): KeyframeEntity<T> {
632             this.arcMode = arcMode
633             return this
634         }
635     }
636 
637     override fun <V : AnimationVector> vectorize(
638         converter: TwoWayConverter<T, V>
639     ): VectorizedKeyframesSpec<V> {
640         // Max capacity is +2 to account for when the start/end timestamps are not included
641         val timestamps = MutableIntList(config.keyframes.size + 2)
642         val timeToInfoMap =
643             MutableIntObjectMap<VectorizedKeyframeSpecElementInfo<V>>(config.keyframes.size)
644         config.keyframes.forEach { key, value ->
645             timestamps.add(key)
646             timeToInfoMap[key] =
647                 VectorizedKeyframeSpecElementInfo(
648                     vectorValue = converter.convertToVector(value.value),
649                     easing = value.easing,
650                     arcMode = value.arcMode
651                 )
652         }
653 
654         if (!config.keyframes.contains(0)) {
655             timestamps.add(0, 0)
656         }
657         if (!config.keyframes.contains(config.durationMillis)) {
658             timestamps.add(config.durationMillis)
659         }
660         timestamps.sort()
661 
662         return VectorizedKeyframesSpec(
663             timestamps = timestamps,
664             keyframes = timeToInfoMap,
665             durationMillis = config.durationMillis,
666             delayMillis = config.delayMillis,
667             defaultEasing = LinearEasing,
668             initialArcMode = ArcLinear
669         )
670     }
671 
672     /** Holder class for building a keyframes animation. */
673     public class KeyframeEntity<T>
674     internal constructor(
675         value: T,
676         easing: Easing = LinearEasing,
677         internal var arcMode: ArcMode = ArcMode.ArcLinear
678     ) : KeyframeBaseEntity<T>(value = value, easing = easing) {
679 
680         override fun equals(other: Any?): Boolean {
681             if (other === this) return true
682             if (other !is KeyframeEntity<*>) return false
683 
684             return other.value == value && other.easing == easing && other.arcMode == arcMode
685         }
686 
687         override fun hashCode(): Int {
688             var result = value?.hashCode() ?: 0
689             result = 31 * result + arcMode.hashCode()
690             result = 31 * result + easing.hashCode()
691             return result
692         }
693     }
694 }
695 
696 /**
697  * [KeyframesWithSplineSpec] creates a keyframe based [DurationBasedAnimationSpec] using the
698  * Monotone cubic Hermite spline to interpolate between the values in [config].
699  *
700  * [KeyframesWithSplineSpec] may be used to animate any n-dimensional values, but you'll likely use
701  * it most to animate positional 2D values such as [Offset]. For example:
702  *
703  * @sample androidx.compose.animation.core.samples.KeyframesBuilderForOffsetWithSplines
704  *
705  * You may also provide a [periodicBias] value (between 0f and 1f) to make a periodic spline.
706  * Periodic splines adjust the initial and final velocity to be the same. This is useful to create
707  * smooth repeatable animations. Such as an infinite pulsating animation:
708  *
709  * @sample androidx.compose.animation.core.samples.PeriodicKeyframesWithSplines
710  *
711  * The [periodicBias] value (from 0.0 to 1.0) indicates how much of the original starting and final
712  * velocity are modified to achieve periodicity:
713  * - 0f: Modifies only the starting velocity to match the final velocity
714  * - 1f: Modifies only the final velocity to match the starting velocity
715  * - 0.5f: Modifies both velocities equally, picking the average between the two
716  *
717  * @sample androidx.compose.animation.core.samples.KeyframesBuilderForIntOffsetWithSplines
718  * @sample androidx.compose.animation.core.samples.KeyframesBuilderForDpOffsetWithSplines
719  * @see keyframesWithSpline
720  */
721 @Immutable
722 public class KeyframesWithSplineSpec<T>(
723     public val config: KeyframesWithSplineSpecConfig<T>,
724 ) : DurationBasedAnimationSpec<T> {
725     // Periodic bias property, NaN by default. Only meant to be set by secondary constructor
726     private var periodicBias: Float = Float.NaN
727 
728     /**
729      * Constructor that returns a periodic spline implementation.
730      *
731      * @param config Keyframe configuration of the spline, should contain the set of values,
732      *   timestamps and easing curves to animate through.
733      * @param periodicBias A value from 0f to 1f, indicating how much the starting or ending
734      *   velocities are modified respectively to achieve periodicity.
735      */
736     public constructor(
737         config: KeyframesWithSplineSpecConfig<T>,
738         @FloatRange(0.0, 1.0) periodicBias: Float
739     ) : this(config) {
740         this.periodicBias = periodicBias
741     }
742 
743     /**
744      * Keyframe configuration class for [KeyframesWithSplineSpec].
745      *
746      * Since [keyframesWithSpline] uses the values across all the given intervals to calculate the
747      * shape of the animation, [KeyframesWithSplineSpecConfig] does not allow setting a specific
748      * [ArcMode] between intervals (compared to [KeyframesSpecConfig] used for [keyframes]).
749      */
750     public class KeyframesWithSplineSpecConfig<T> :
751         KeyframesSpecBaseConfig<T, KeyframesSpec.KeyframeEntity<T>>() {
752 
createEntityFornull753         override fun createEntityFor(value: T): KeyframesSpec.KeyframeEntity<T> =
754             KeyframesSpec.KeyframeEntity(value)
755     }
756 
757     override fun <V : AnimationVector> vectorize(
758         converter: TwoWayConverter<T, V>
759     ): VectorizedDurationBasedAnimationSpec<V> {
760         // Allocate so that we don't resize the list even if the initial/last timestamps are missing
761         val keyframes = config.keyframes
762         val timestamps = MutableIntList(keyframes.size + 2)
763         val timeToVectorMap = MutableIntObjectMap<Pair<V, Easing>>(keyframes.size)
764         keyframes.forEach { key, value ->
765             timestamps.add(key)
766             timeToVectorMap[key] = Pair(converter.convertToVector(value.value), value.easing)
767         }
768         if (!keyframes.contains(0)) {
769             timestamps.add(0, 0)
770         }
771         if (!keyframes.contains(config.durationMillis)) {
772             timestamps.add(config.durationMillis)
773         }
774         timestamps.sort()
775         return VectorizedMonoSplineKeyframesSpec(
776             timestamps = timestamps,
777             keyframes = timeToVectorMap,
778             durationMillis = config.durationMillis,
779             delayMillis = config.delayMillis,
780             periodicBias = periodicBias
781         )
782     }
783 }
784 
785 /**
786  * Creates a [TweenSpec] configured with the given duration, delay and easing curve.
787  *
788  * @param durationMillis duration of the animation spec
789  * @param delayMillis the amount of time in milliseconds that animation waits before starting
790  * @param easing the easing curve that will be used to interpolate between start and end
791  */
792 @Stable
tweennull793 public fun <T> tween(
794     durationMillis: Int = DefaultDurationMillis,
795     delayMillis: Int = 0,
796     easing: Easing = FastOutSlowInEasing
797 ): TweenSpec<T> = TweenSpec(durationMillis, delayMillis, easing)
798 
799 /**
800  * Creates a [SpringSpec] that uses the given spring constants (i.e. [dampingRatio] and [stiffness].
801  * The optional [visibilityThreshold] defines when the animation should be considered to be visually
802  * close enough to round off to its target.
803  *
804  * @param dampingRatio damping ratio of the spring. [Spring.DampingRatioNoBouncy] by default.
805  * @param stiffness stiffness of the spring. [Spring.StiffnessMedium] by default.
806  * @param visibilityThreshold optionally specifies the visibility threshold.
807  */
808 @Stable
809 public fun <T> spring(
810     dampingRatio: Float = Spring.DampingRatioNoBouncy,
811     stiffness: Float = Spring.StiffnessMedium,
812     visibilityThreshold: T? = null
813 ): SpringSpec<T> = SpringSpec(dampingRatio, stiffness, visibilityThreshold)
814 
815 /**
816  * Creates a [KeyframesSpec] animation, initialized with [init]. For example:
817  *
818  * @sample androidx.compose.animation.core.samples.FloatKeyframesBuilderInline
819  *
820  * Keyframes can also be associated with a particular [Easing] function:
821  *
822  * @sample androidx.compose.animation.core.samples.KeyframesBuilderWithEasing
823  *
824  * Values can be animated using arcs of quarter of an Ellipse with [KeyframesSpecConfig.using] and
825  * [ArcMode]:
826  *
827  * @sample androidx.compose.animation.core.samples.OffsetKeyframesWithArcsBuilder
828  *
829  * For a smooth, curvy animation across all the intervals in the keyframes, consider using
830  * [keyframesWithSpline] instead.
831  *
832  * @param init Initialization function for the [KeyframesSpec] animation
833  * @see KeyframesSpec.KeyframesSpecConfig
834  */
835 @Stable
836 public fun <T> keyframes(init: KeyframesSpecConfig<T>.() -> Unit): KeyframesSpec<T> {
837     return KeyframesSpec(KeyframesSpecConfig<T>().apply(init))
838 }
839 
840 /**
841  * Creates a [KeyframesWithSplineSpec] animation, initialized with [init].
842  *
843  * For more details on implementation, see [KeyframesWithSplineSpec].
844  *
845  * Use overload that takes a [Float] parameter to use periodic splines.
846  *
847  * Example:
848  *
849  * @sample androidx.compose.animation.core.samples.KeyframesBuilderForOffsetWithSplines
850  * @param init Initialization function for the [KeyframesWithSplineSpec] animation
851  * @sample androidx.compose.animation.core.samples.KeyframesBuilderForIntOffsetWithSplines
852  * @sample androidx.compose.animation.core.samples.KeyframesBuilderForDpOffsetWithSplines
853  * @see KeyframesWithSplineSpec.KeyframesWithSplineSpecConfig
854  */
keyframesWithSplinenull855 public fun <T> keyframesWithSpline(
856     init: KeyframesWithSplineSpec.KeyframesWithSplineSpecConfig<T>.() -> Unit
857 ): KeyframesWithSplineSpec<T> =
858     KeyframesWithSplineSpec(
859         config = KeyframesWithSplineSpec.KeyframesWithSplineSpecConfig<T>().apply(init)
860     )
861 
862 /**
863  * Creates a *periodic* [KeyframesWithSplineSpec] animation, initialized with [init].
864  *
865  * Use overload without [periodicBias] parameter for the non-periodic implementation.
866  *
867  * A periodic spline is one such that the starting and ending velocities are equal. This makes them
868  * useful to create smooth repeatable animations. Such as an infinite pulsating animation:
869  *
870  * @sample androidx.compose.animation.core.samples.PeriodicKeyframesWithSplines
871  *
872  * The [periodicBias] value (from 0.0 to 1.0) indicates how much of the original starting and final
873  * velocity are modified to achieve periodicity:
874  * - 0f: Modifies only the starting velocity to match the final velocity
875  * - 1f: Modifies only the final velocity to match the starting velocity
876  * - 0.5f: Modifies both velocities equally, picking the average between the two
877  *
878  * @param periodicBias A value from 0f to 1f, indicating how much the starting or ending velocities
879  *   are modified respectively to achieve periodicity.
880  * @param init Initialization function for the [KeyframesWithSplineSpec] animation
881  * @see KeyframesWithSplineSpec.KeyframesWithSplineSpecConfig
882  */
883 public fun <T> keyframesWithSpline(
884     @FloatRange(0.0, 1.0) periodicBias: Float,
885     init: KeyframesWithSplineSpec.KeyframesWithSplineSpecConfig<T>.() -> Unit
886 ): KeyframesWithSplineSpec<T> =
887     KeyframesWithSplineSpec(
888         config = KeyframesWithSplineSpec.KeyframesWithSplineSpecConfig<T>().apply(init),
889         periodicBias = periodicBias,
890     )
891 
892 /**
893  * Creates a [RepeatableSpec] that plays a [DurationBasedAnimationSpec] (e.g. [TweenSpec],
894  * [KeyframesSpec]) the amount of iterations specified by [iterations].
895  *
896  * The iteration count describes the amount of times the animation will run. 1 means no repeat.
897  * Recommend [infiniteRepeatable] for creating an infinity repeating animation.
898  *
899  * __Note__: When repeating in the [RepeatMode.Reverse] mode, it's highly recommended to have an
900  * __odd__ number of iterations. Otherwise, the animation may jump to the end value when it finishes
901  * the last iteration.
902  *
903  * [initialStartOffset] can be used to either delay the start of the animation or to fast forward
904  * the animation to a given play time. This start offset will **not** be repeated, whereas the delay
905  * in the [animation] (if any) will be repeated. By default, the amount of offset is 0.
906  *
907  * @param iterations the total count of iterations, should be greater than 1 to repeat.
908  * @param animation animation that will be repeated
909  * @param repeatMode whether animation should repeat by starting from the beginning (i.e.
910  *   [RepeatMode.Restart]) or from the end (i.e. [RepeatMode.Reverse])
911  * @param initialStartOffset offsets the start of the animation
912  */
913 @Stable
914 public fun <T> repeatable(
915     iterations: Int,
916     animation: DurationBasedAnimationSpec<T>,
917     repeatMode: RepeatMode = RepeatMode.Restart,
918     initialStartOffset: StartOffset = StartOffset(0)
919 ): RepeatableSpec<T> = RepeatableSpec(iterations, animation, repeatMode, initialStartOffset)
920 
921 @Stable
922 @Deprecated(
923     level = DeprecationLevel.HIDDEN,
924     message =
925         "This method has been deprecated in favor of the repeatable function that accepts" +
926             " start offset."
927 )
928 public fun <T> repeatable(
929     iterations: Int,
930     animation: DurationBasedAnimationSpec<T>,
931     repeatMode: RepeatMode = RepeatMode.Restart
932 ): RepeatableSpec<T> = RepeatableSpec(iterations, animation, repeatMode, StartOffset(0))
933 
934 /**
935  * Creates a [InfiniteRepeatableSpec] that plays a [DurationBasedAnimationSpec] (e.g. [TweenSpec],
936  * [KeyframesSpec]) infinite amount of iterations.
937  *
938  * For non-infinitely repeating animations, consider [repeatable].
939  *
940  * [initialStartOffset] can be used to either delay the start of the animation or to fast forward
941  * the animation to a given play time. This start offset will **not** be repeated, whereas the delay
942  * in the [animation] (if any) will be repeated. By default, the amount of offset is 0.
943  *
944  * @sample androidx.compose.animation.core.samples.InfiniteProgressIndicator
945  * @param animation animation that will be repeated
946  * @param repeatMode whether animation should repeat by starting from the beginning (i.e.
947  *   [RepeatMode.Restart]) or from the end (i.e. [RepeatMode.Reverse])
948  * @param initialStartOffset offsets the start of the animation
949  */
950 @Stable
951 public fun <T> infiniteRepeatable(
952     animation: DurationBasedAnimationSpec<T>,
953     repeatMode: RepeatMode = RepeatMode.Restart,
954     initialStartOffset: StartOffset = StartOffset(0)
955 ): InfiniteRepeatableSpec<T> = InfiniteRepeatableSpec(animation, repeatMode, initialStartOffset)
956 
957 @Stable
958 @Deprecated(
959     level = DeprecationLevel.HIDDEN,
960     message =
961         "This method has been deprecated in favor of the infinite repeatable function that" +
962             " accepts start offset."
963 )
964 public fun <T> infiniteRepeatable(
965     animation: DurationBasedAnimationSpec<T>,
966     repeatMode: RepeatMode = RepeatMode.Restart
967 ): InfiniteRepeatableSpec<T> = InfiniteRepeatableSpec(animation, repeatMode, StartOffset(0))
968 
969 /**
970  * Creates a Snap animation for immediately switching the animating value to the end value.
971  *
972  * @param delayMillis the number of milliseconds to wait before the animation runs. 0 by default.
973  */
974 @Stable public fun <T> snap(delayMillis: Int = 0): SnapSpec<T> = SnapSpec<T>(delayMillis)
975 
976 /**
977  * Returns an [AnimationSpec] that is the same as [animationSpec] with a delay of [startDelayNanos].
978  */
979 @Stable
980 internal fun <T> delayed(animationSpec: AnimationSpec<T>, startDelayNanos: Long): AnimationSpec<T> =
981     StartDelayAnimationSpec(animationSpec, startDelayNanos)
982 
983 /**
984  * A [VectorizedAnimationSpec] that wraps [vectorizedAnimationSpec], giving it a start delay of
985  * [startDelayNanos].
986  */
987 @Immutable
988 private class StartDelayVectorizedAnimationSpec<V : AnimationVector>(
989     val vectorizedAnimationSpec: VectorizedAnimationSpec<V>,
990     val startDelayNanos: Long
991 ) : VectorizedAnimationSpec<V> {
992     override val isInfinite: Boolean
993         get() = vectorizedAnimationSpec.isInfinite
994 
995     override fun getDurationNanos(initialValue: V, targetValue: V, initialVelocity: V): Long =
996         vectorizedAnimationSpec.getDurationNanos(
997             initialValue = initialValue,
998             targetValue = targetValue,
999             initialVelocity = initialVelocity
1000         ) + startDelayNanos
1001 
1002     override fun getVelocityFromNanos(
1003         playTimeNanos: Long,
1004         initialValue: V,
1005         targetValue: V,
1006         initialVelocity: V
1007     ): V =
1008         if (playTimeNanos < startDelayNanos) {
1009             initialVelocity
1010         } else {
1011             vectorizedAnimationSpec.getVelocityFromNanos(
1012                 playTimeNanos = playTimeNanos - startDelayNanos,
1013                 initialValue = initialValue,
1014                 targetValue = targetValue,
1015                 initialVelocity = initialVelocity
1016             )
1017         }
1018 
1019     override fun getValueFromNanos(
1020         playTimeNanos: Long,
1021         initialValue: V,
1022         targetValue: V,
1023         initialVelocity: V
1024     ): V =
1025         if (playTimeNanos < startDelayNanos) {
1026             initialValue
1027         } else {
1028             vectorizedAnimationSpec.getValueFromNanos(
1029                 playTimeNanos = playTimeNanos - startDelayNanos,
1030                 initialValue = initialValue,
1031                 targetValue = targetValue,
1032                 initialVelocity = initialVelocity
1033             )
1034         }
1035 
1036     override fun hashCode(): Int {
1037         return 31 * vectorizedAnimationSpec.hashCode() + startDelayNanos.hashCode()
1038     }
1039 
1040     override fun equals(other: Any?): Boolean {
1041         if (other !is StartDelayVectorizedAnimationSpec<*>) {
1042             return false
1043         }
1044         return other.startDelayNanos == startDelayNanos &&
1045             other.vectorizedAnimationSpec == vectorizedAnimationSpec
1046     }
1047 }
1048 
1049 /** An [AnimationSpec] that wraps [animationSpec], giving it a start delay of [startDelayNanos]. */
1050 @Immutable
1051 private class StartDelayAnimationSpec<T>(
1052     val animationSpec: AnimationSpec<T>,
1053     val startDelayNanos: Long
1054 ) : AnimationSpec<T> {
vectorizenull1055     override fun <V : AnimationVector> vectorize(
1056         converter: TwoWayConverter<T, V>
1057     ): VectorizedAnimationSpec<V> {
1058         val vecSpec = animationSpec.vectorize(converter)
1059         return StartDelayVectorizedAnimationSpec(vecSpec, startDelayNanos)
1060     }
1061 
hashCodenull1062     override fun hashCode(): Int {
1063         return 31 * animationSpec.hashCode() + startDelayNanos.hashCode()
1064     }
1065 
equalsnull1066     override fun equals(other: Any?): Boolean {
1067         if (other !is StartDelayAnimationSpec<*>) {
1068             return false
1069         }
1070         return other.startDelayNanos == startDelayNanos && other.animationSpec == animationSpec
1071     }
1072 }
1073