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 *
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 *
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 *
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