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 @file:OptIn(InternalAnimationApi::class)
18
19 package androidx.compose.animation.core
20
21 import androidx.annotation.FloatRange
22 import androidx.annotation.RestrictTo
23 import androidx.collection.MutableObjectList
24 import androidx.compose.animation.core.internal.JvmDefaultWithCompatibility
25 import androidx.compose.runtime.Composable
26 import androidx.compose.runtime.DisposableEffect
27 import androidx.compose.runtime.LaunchedEffect
28 import androidx.compose.runtime.MutableState
29 import androidx.compose.runtime.Stable
30 import androidx.compose.runtime.State
31 import androidx.compose.runtime.derivedStateOf
32 import androidx.compose.runtime.getValue
33 import androidx.compose.runtime.mutableFloatStateOf
34 import androidx.compose.runtime.mutableLongStateOf
35 import androidx.compose.runtime.mutableStateListOf
36 import androidx.compose.runtime.mutableStateOf
37 import androidx.compose.runtime.remember
38 import androidx.compose.runtime.rememberCoroutineScope
39 import androidx.compose.runtime.setValue
40 import androidx.compose.runtime.snapshots.Snapshot
41 import androidx.compose.runtime.snapshots.SnapshotStateObserver
42 import androidx.compose.runtime.withFrameNanos
43 import androidx.compose.ui.geometry.Offset
44 import androidx.compose.ui.geometry.Rect
45 import androidx.compose.ui.geometry.Size
46 import androidx.compose.ui.unit.Dp
47 import androidx.compose.ui.unit.IntOffset
48 import androidx.compose.ui.unit.IntSize
49 import androidx.compose.ui.util.fastAny
50 import androidx.compose.ui.util.fastFold
51 import androidx.compose.ui.util.fastForEach
52 import kotlin.coroutines.coroutineContext
53 import kotlin.coroutines.resume
54 import kotlin.jvm.JvmName
55 import kotlin.math.max
56 import kotlin.math.roundToLong
57 import kotlinx.coroutines.CancellableContinuation
58 import kotlinx.coroutines.CancellationException
59 import kotlinx.coroutines.CoroutineStart
60 import kotlinx.coroutines.coroutineScope
61 import kotlinx.coroutines.isActive
62 import kotlinx.coroutines.launch
63 import kotlinx.coroutines.suspendCancellableCoroutine
64 import kotlinx.coroutines.sync.Mutex
65 import kotlinx.coroutines.sync.withLock
66
67 /**
68 * This sets up a [Transition], and updates it with the target provided by [targetState]. When
69 * [targetState] changes, [Transition] will run all of its child animations towards their target
70 * values specified for the new [targetState]. Child animations can be dynamically added using
71 * [Transition.animateFloat], [animateColor][ androidx.compose.animation.animateColor],
72 * [Transition.animateValue], etc.
73 *
74 * [label] is used to differentiate different transitions in Android Studio.
75 *
76 * __Note__: There is another [rememberTransition] overload that accepts a [MutableTransitionState].
77 * The difference between the two is that the [MutableTransitionState] variant: 1) supports a
78 * different initial state than target state (This would allow a transition to start as soon as it
79 * enters composition.) 2) can be recreated to intentionally trigger a re-start of the transition.
80 *
81 * @sample androidx.compose.animation.core.samples.GestureAnimationSample
82 * @return a [Transition] object, to which animations can be added.
83 * @see Transition
84 * @see Transition.animateFloat
85 * @see Transition.animateValue
86 */
87 @Composable
88 public fun <T> updateTransition(targetState: T, label: String? = null): Transition<T> {
89 val transition = remember { Transition(targetState, label = label) }
90 transition.animateTo(targetState)
91 DisposableEffect(transition) {
92 onDispose {
93 // Clean up on the way out, to ensure the observers are not stuck in an in-between
94 // state.
95 transition.onDisposed()
96 }
97 }
98 return transition
99 }
100
101 internal const val AnimationDebugDurationScale = 1
102
103 /**
104 * Use with [rememberTransition] to create a [Transition] that can be dynamically targeted with
105 * [MutableTransitionState] or seekable with [SeekableTransitionState].
106 */
107 public sealed class TransitionState<S> {
108 /**
109 * Current state of the transition. If there is an active transition, [currentState] and
110 * [targetState] are different.
111 */
112 public abstract var currentState: S
113 internal set
114
115 /**
116 * Target state of the transition. If this is the same as [currentState], no transition is
117 * active.
118 */
119 public abstract var targetState: S
120 internal set
121
122 // Updated from Transition
123 internal var isRunning: Boolean by mutableStateOf(false)
124
transitionConfigurednull125 internal abstract fun transitionConfigured(transition: Transition<S>)
126
127 internal abstract fun transitionRemoved()
128 }
129
130 /**
131 * This is used to prevent exhaustive `when` from limiting the use of [TransitionState] to only
132 * [MutableState] and [SeekableTransitionState]. The developer must always have an `else`. It is
133 * unlikely to be a concern, but this will alleviate any worries about expanding the subclasses of
134 * [TransitionState].
135 */
136 private class PreventExhaustiveWhenTransitionState : TransitionState<Any?>() {
137 override var currentState: Any?
138 get() = null
139 set(_) {}
140
141 override var targetState: Any?
142 get() = null
143 set(_) {}
144
145 override fun transitionConfigured(transition: Transition<Any?>) {}
146
147 override fun transitionRemoved() {}
148 }
149
150 /**
151 * MutableTransitionState contains two fields: [currentState] and [targetState]. [currentState] is
152 * initialized to the provided initialState, and can only be mutated by a [Transition].
153 * [targetState] is also initialized to initialState. It can be mutated to alter the course of a
154 * transition animation that is created with the [MutableTransitionState] using
155 * [rememberTransition]. Both [currentState] and [targetState] are backed by a [State] object.
156 *
157 * @sample androidx.compose.animation.core.samples.InitialStateSample
158 * @see rememberTransition
159 */
160 public class MutableTransitionState<S>(initialState: S) : TransitionState<S>() {
161 /**
162 * Current state of the transition. [currentState] is initialized to the initialState that the
163 * [MutableTransitionState] is constructed with.
164 *
165 * It will be updated by the Transition that is created with this [MutableTransitionState] when
166 * the transition arrives at a new state.
167 */
168 override var currentState: S by mutableStateOf(initialState)
169 internal set
170
171 /**
172 * Target state of the transition. [targetState] is initialized to the initialState that the
173 * [MutableTransitionState] is constructed with.
174 *
175 * It can be updated to a new state at any time. When that happens, the [Transition] that is
176 * created with this [MutableTransitionState] will update its [Transition.targetState] to the
177 * same and subsequently starts a transition animation to animate from the current values to the
178 * new target.
179 */
180 override var targetState: S by mutableStateOf(initialState)
181 public set
182
183 /**
184 * [isIdle] returns whether the transition has finished running. This will return false once the
185 * [targetState] has been set to a different value than [currentState].
186 *
187 * @sample androidx.compose.animation.core.samples.TransitionStateIsIdleSample
188 */
189 public val isIdle: Boolean
190 get() = (currentState == targetState) && !isRunning
191
transitionConfigurednull192 override fun transitionConfigured(transition: Transition<S>) {}
193
transitionRemovednull194 override fun transitionRemoved() {}
195 }
196
197 /**
198 * Lambda to call [SeekableTransitionState.onTotalDurationChanged] when the
199 * [Transition.totalDurationNanos] has changed.
200 */
<lambda>null201 private val SeekableTransitionStateTotalDurationChanged: (SeekableTransitionState<*>) -> Unit = {
202 it.onTotalDurationChanged()
203 }
204
205 // This observer is also accessed from test. It should be otherwise treated as private.
206 internal val SeekableStateObserver: SnapshotStateObserver by
<lambda>null207 lazy(LazyThreadSafetyMode.NONE) { SnapshotStateObserver { it() }.apply { start() } }
208
209 /**
210 * A [TransitionState] that can manipulate the progress of the [Transition] by seeking with [seekTo]
211 * or animating with [animateTo].
212 *
213 * A [SeekableTransitionState] can only be used with one [Transition] instance. Once assigned, it
214 * cannot be reassigned to a different [Transition] instance.
215 *
216 * @sample androidx.compose.animation.core.samples.SeekingAnimationSample
217 */
218 public class SeekableTransitionState<S>(initialState: S) : TransitionState<S>() {
219 override var targetState: S by mutableStateOf(initialState)
220 internal set
221
222 override var currentState: S by mutableStateOf(initialState)
223 internal set
224
225 /**
226 * The value of [targetState] that the composition knows about. Seeking cannot progress until
227 * the composition's target state matches [targetState].
228 */
229 internal var composedTargetState = initialState
230
231 /**
232 * The Transition that this is associated with. SeekableTransitionState can only be used with
233 * one Transition.
234 */
235 private var transition: Transition<S>? = null
236
237 // Used for seekToFraction calculations to avoid allocation
238 internal var totalDurationNanos = 0L
239
<lambda>null240 private val recalculateTotalDurationNanos: () -> Unit = {
241 totalDurationNanos = transition?.totalDurationNanos ?: 0L
242 }
243
244 /**
245 * The progress of the transition from [currentState] to [targetState] as a fraction of the
246 * entire duration.
247 *
248 * If [targetState] and [currentState] are the same, [fraction] will be 0.
249 */
250 @get:FloatRange(from = 0.0, to = 1.0)
251 public var fraction: Float by mutableFloatStateOf(0f)
252 private set
253
254 /** The continuation used when waiting for a composition. */
255 internal var compositionContinuation: CancellableContinuation<S>? = null
256
257 /**
258 * Used to lock the [compositionContinuation] while modifying or checking its value. Also used
259 * for locking the [composedTargetState].
260 */
261 internal val compositionContinuationMutex = Mutex()
262
263 /** Used to prevent [snapTo], [seekTo], and [animateTo] from running simultaneously. */
264 private val mutatorMutex = MutatorMutex()
265
266 /**
267 * When the animation is running, this contains the most recent frame time. When the animation
268 * has stopped, this is [AnimationConstants.UnspecifiedTime].
269 */
270 private var lastFrameTimeNanos: Long = AnimationConstants.UnspecifiedTime
271
272 /**
273 * List of animation of initial values. The list should be empty when [seekTo], [snapTo], or
274 * [animateTo] completes successfully.
275 */
276 private val initialValueAnimations = MutableObjectList<SeekingAnimationState>()
277
278 /**
279 * When [animateTo] is executing, this is non-null, providing the information being used to
280 * animate its value. This will be null while seeking or after [snapTo].
281 */
282 private var currentAnimation: SeekingAnimationState? = null
283
284 /** Lambda instance used for capturing the first frame of an animation. */
frameTimeNanosnull285 private val firstFrameLambda: (Long) -> Unit = { frameTimeNanos ->
286 lastFrameTimeNanos = frameTimeNanos
287 }
288
289 /**
290 * Used in [animateOneFrameLambda], the duration scale must be set immediately prior to invoking
291 * [withFrameNanos] with [animateOneFrameLambda].
292 */
293 private var durationScale: Float = 0f
294
295 /** Lambda instance used for animating a single frame within [withFrameNanos]. */
frameTimeNanosnull296 private val animateOneFrameLambda: (Long) -> Unit = { frameTimeNanos ->
297 val delta = frameTimeNanos - lastFrameTimeNanos
298 lastFrameTimeNanos = frameTimeNanos
299 val deltaPlayTimeNanos = (delta / durationScale.toDouble()).roundToLong()
300 if (initialValueAnimations.isNotEmpty()) {
301 initialValueAnimations.forEach { animation ->
302 // updateInitialValues will set to false if the animation isn't
303 // complete
304 recalculateAnimationValue(animation, deltaPlayTimeNanos)
305 animation.isComplete = true
306 }
307 transition?.updateInitialValues()
308 initialValueAnimations.removeIf { it.isComplete }
309 }
310 val currentAnimation = currentAnimation
311 if (currentAnimation != null) {
312 currentAnimation.durationNanos = totalDurationNanos
313 recalculateAnimationValue(currentAnimation, deltaPlayTimeNanos)
314 fraction = currentAnimation.value
315 if (currentAnimation.value == 1f) {
316 this@SeekableTransitionState.currentAnimation = null // all done!
317 }
318 seekToFraction()
319 }
320 }
321
322 /**
323 * Stops all animations, including the initial value animations and sets the [fraction] to `1`.
324 */
endAllAnimationsnull325 private fun endAllAnimations() {
326 transition?.clearInitialAnimations()
327 initialValueAnimations.clear()
328 val current = currentAnimation
329 if (current != null) {
330 currentAnimation = null
331 fraction = 1f
332 seekToFraction()
333 }
334 }
335
336 /**
337 * Starts the animation. It will advance both the currently-running animation and initial value
338 * animations. If the previous animation was stopped ([seekTo] or [snapTo] or no previous
339 * animation was running), then it will require one frame to capture the frame time before the
340 * animation starts.
341 */
runAnimationsnull342 private suspend fun runAnimations() {
343 if (initialValueAnimations.isEmpty() && currentAnimation == null) {
344 // nothing to animate
345 return
346 }
347 if (coroutineContext.durationScale == 0f) {
348 endAllAnimations()
349 lastFrameTimeNanos = AnimationConstants.UnspecifiedTime
350 return
351 }
352 if (lastFrameTimeNanos == AnimationConstants.UnspecifiedTime) {
353 // have to capture one frame to get the start time
354 withFrameNanos(firstFrameLambda)
355 }
356 while (initialValueAnimations.isNotEmpty() || currentAnimation != null) {
357 animateOneFrame()
358 }
359 lastFrameTimeNanos = AnimationConstants.UnspecifiedTime
360 }
361
362 /**
363 * Does one frame of work. If there is no previous animation, then it will capture the
364 * [lastFrameTimeNanos]. If there was a previous animation, it will advance by one frame.
365 */
doOneFramenull366 private suspend fun doOneFrame() {
367 if (lastFrameTimeNanos == AnimationConstants.UnspecifiedTime) {
368 // have to capture one frame to get the start time
369 withFrameNanos(firstFrameLambda)
370 } else {
371 animateOneFrame()
372 }
373 }
374
375 /** Advances all animations by one frame. */
animateOneFramenull376 private suspend fun animateOneFrame() {
377 val durationScale = coroutineContext.durationScale
378 if (durationScale <= 0f) {
379 endAllAnimations()
380 } else {
381 this@SeekableTransitionState.durationScale = durationScale
382 withFrameNanos(animateOneFrameLambda)
383 }
384 }
385
386 /**
387 * Calculates the [SeekingAnimationState.value] based on the [deltaPlayTimeNanos]. It uses the
388 * animation spec if one is provided, or the progress of the total duration if not. This method
389 * does not account for duration scale.
390 */
recalculateAnimationValuenull391 private fun recalculateAnimationValue(
392 animation: SeekingAnimationState,
393 deltaPlayTimeNanos: Long
394 ) {
395 val playTimeNanos = animation.progressNanos + deltaPlayTimeNanos
396 animation.progressNanos = playTimeNanos
397 val durationNanos = animation.animationSpecDuration
398 if (playTimeNanos >= durationNanos) {
399 animation.value = 1f
400 } else {
401 val animationSpec = animation.animationSpec
402 if (animationSpec != null) {
403 animation.value =
404 animationSpec
405 .getValueFromNanos(
406 playTimeNanos,
407 animation.start,
408 Target1,
409 animation.initialVelocity ?: ZeroVelocity
410 )[0]
411 .coerceIn(0f, 1f)
412 } else {
413 animation.value =
414 lerp(animation.start[0], 1f, playTimeNanos.toFloat() / durationNanos)
415 }
416 }
417 }
418
419 /**
420 * Sets [currentState] and [targetState] to `targetState` and snaps all values to those at that
421 * state. The transition will not have any animations running after running [snapTo].
422 *
423 * This can have a similar effect as [seekTo]. However, [seekTo] moves the [currentState] to the
424 * former [targetState] and animates the initial values of the animations from the current
425 * values to those at [currentState]. [seekTo] also allows the developer to move the state
426 * between any fraction between [currentState] and [targetState], while [snapTo] moves all state
427 * to [targetState] without any further seeking allowed.
428 *
429 * @sample androidx.compose.animation.core.samples.SnapToSample
430 * @see animateTo
431 */
snapTonull432 public suspend fun snapTo(targetState: S) {
433 val transition = transition ?: return
434 if (
435 currentState == targetState && this@SeekableTransitionState.targetState == targetState
436 ) {
437 return // nothing to change
438 }
439 mutatorMutex.mutate {
440 endAllAnimations()
441 lastFrameTimeNanos = AnimationConstants.UnspecifiedTime
442 fraction = 0f
443 val fraction =
444 when (targetState) {
445 currentState -> ResetAnimationSnapCurrent
446 this@SeekableTransitionState.targetState -> ResetAnimationSnapTarget
447 else -> ResetAnimationSnap
448 }
449 transition.updateTarget(targetState)
450 transition.playTimeNanos = 0L
451 this@SeekableTransitionState.targetState = targetState
452 this@SeekableTransitionState.fraction = 0f
453 currentState = targetState
454 transition.resetAnimationFraction(fraction)
455 if (fraction == ResetAnimationSnap) {
456 // completely changed the value, so we have to wait for a composition to have
457 // the correct animation values
458 waitForCompositionAfterTargetStateChange()
459 }
460 transition.onTransitionEnd()
461 }
462 }
463
464 /**
465 * Starts seeking the transition to [targetState] with [fraction] used to indicate the progress
466 * towards [targetState]. If the previous `targetState` was already [targetState] then [seekTo]
467 * only stops any current animation towards that state and snaps the fraction to the new value.
468 * Otherwise, the [currentState] is changed to the former `targetState` and `targetState` is
469 * changed to [targetState] and an animation is started, moving the start values towards the
470 * former `targetState`. This will return when the initial values have reached `currentState`
471 * and the [fraction] has been reached.
472 *
473 * [snapTo] also allows the developer to change the state, but does not animate any values.
474 * Instead, it instantly moves all values to those at the new [targetState].
475 *
476 * @sample androidx.compose.animation.core.samples.SeekToSample
477 * @see animateTo
478 */
seekTonull479 public suspend fun seekTo(
480 @FloatRange(from = 0.0, to = 1.0) fraction: Float,
481 targetState: S = this.targetState
482 ) {
483 requirePrecondition(fraction in 0f..1f) {
484 "Expecting fraction between 0 and 1. Got $fraction"
485 }
486 val transition = transition ?: return
487 val oldTargetState = this@SeekableTransitionState.targetState
488 mutatorMutex.mutate {
489 coroutineScope {
490 if (targetState != oldTargetState) {
491 moveAnimationToInitialState()
492 } else {
493 currentAnimation = null
494 if (currentState == targetState) {
495 return@coroutineScope // Can't seek when current state is target state
496 }
497 }
498 if (targetState != oldTargetState) {
499 // Change the target _and_ the fraction
500 transition.updateTarget(targetState)
501 transition.playTimeNanos = 0L
502 this@SeekableTransitionState.targetState = targetState
503 transition.resetAnimationFraction(fraction)
504 }
505 this@SeekableTransitionState.fraction = fraction
506 if (initialValueAnimations.isNotEmpty()) {
507 launch { runAnimations() }
508 } else {
509 lastFrameTimeNanos = AnimationConstants.UnspecifiedTime
510 }
511 waitForCompositionAfterTargetStateChange()
512 seekToFraction()
513 }
514 }
515 }
516
517 /** Wait for composition to set up the target values */
waitForCompositionAfterTargetStateChangenull518 private suspend fun waitForCompositionAfterTargetStateChange() {
519 val expectedState = targetState
520 compositionContinuationMutex.lock()
521 if (expectedState == composedTargetState) {
522 compositionContinuationMutex.unlock()
523 } else {
524 val state = suspendCancellableCoroutine { continuation ->
525 compositionContinuation = continuation
526 compositionContinuationMutex.unlock()
527 }
528 if (state != expectedState) {
529 lastFrameTimeNanos = AnimationConstants.UnspecifiedTime
530 throw CancellationException(
531 "snapTo() was canceled because state was changed to " +
532 "$state instead of $expectedState"
533 )
534 }
535 }
536 }
537
538 /**
539 * Waits for composition, irrespective of whether the target state has changed or not. This is
540 * important for when we're waiting for the currentState to change.
541 */
waitForCompositionnull542 private suspend fun waitForComposition() {
543 val expectedState = targetState
544 compositionContinuationMutex.lock()
545 val state = suspendCancellableCoroutine { continuation ->
546 compositionContinuation = continuation
547 compositionContinuationMutex.unlock()
548 }
549 if (state != expectedState) {
550 lastFrameTimeNanos = AnimationConstants.UnspecifiedTime
551 throw CancellationException("targetState while waiting for composition")
552 }
553 }
554
555 /** Change the animatedInitialFraction to use the animatedFraction, if it needs to be used. */
moveAnimationToInitialStatenull556 private fun moveAnimationToInitialState() {
557 val transition = transition ?: return
558 val animation =
559 currentAnimation
560 ?: if (totalDurationNanos <= 0 || fraction == 1f || currentState == targetState) {
561 null
562 } else {
563 SeekingAnimationState().also {
564 it.value = fraction
565 val totalDurationNanos = totalDurationNanos
566 it.durationNanos = totalDurationNanos
567 it.animationSpecDuration =
568 (totalDurationNanos * (1.0 - fraction)).roundToLong()
569 it.start[0] = fraction
570 }
571 }
572 if (animation != null) {
573 animation.durationNanos = totalDurationNanos
574 initialValueAnimations += animation
575 transition.setInitialAnimations(animation)
576 }
577 currentAnimation = null
578 }
579
580 /**
581 * Updates the current `targetState` to [targetState] and begins an animation to the new state.
582 * If the current `targetState` is the same as [targetState] then the current transition
583 * animation is continued. If a previous transition was interrupted, [currentState] is changed
584 * to the former `targetState` and the start values are animated toward the former
585 * `targetState`.
586 *
587 * Upon completion of the animation, [currentState] will be changed to [targetState].
588 *
589 * @param targetState The state to animate towards.
590 * @param animationSpec If provided, is used to animate the animation fraction. If `null`, the
591 * transition is linearly traversed based on the duration of the transition.
592 */
593 @Suppress("DocumentExceptions")
animateTonull594 public suspend fun animateTo(
595 targetState: S = this.targetState,
596 animationSpec: FiniteAnimationSpec<Float>? = null
597 ) {
598 val transition = transition ?: return
599 mutatorMutex.mutate {
600 coroutineScope {
601 val oldTargetState = this@SeekableTransitionState.targetState
602 if (targetState != oldTargetState) {
603 moveAnimationToInitialState()
604 fraction = 0f
605 transition.updateTarget(targetState)
606 transition.playTimeNanos = 0L
607 currentState = oldTargetState
608 this@SeekableTransitionState.targetState = targetState
609 }
610 val composedTargetState =
611 compositionContinuationMutex.withLock { composedTargetState }
612 if (targetState != composedTargetState) {
613 doOneFrame() // We have to wait a frame for the composition, so continue
614 // Now we shouldn't skip a frame while waiting for composition
615 waitForCompositionAfterTargetStateChange()
616 }
617 if (currentState != targetState) {
618 if (fraction < 1f) {
619 val runningAnimation = currentAnimation
620 val newSpec = animationSpec?.vectorize(Float.VectorConverter)
621 if (runningAnimation == null || newSpec != runningAnimation.animationSpec) {
622 // If there is a running animation, it has changed
623 val oldSpec = runningAnimation?.animationSpec
624 val oldVelocity: AnimationVector1D
625 if (oldSpec != null) {
626 oldVelocity =
627 oldSpec.getVelocityFromNanos(
628 playTimeNanos = runningAnimation.progressNanos,
629 initialValue = runningAnimation.start,
630 targetValue = Target1,
631 initialVelocity =
632 runningAnimation.initialVelocity ?: ZeroVelocity
633 )
634 } else if (
635 runningAnimation == null || runningAnimation.progressNanos == 0L
636 ) {
637 oldVelocity = ZeroVelocity
638 } else {
639 val oldDurationNanos = runningAnimation.durationNanos
640 val oldDuration =
641 if (oldDurationNanos == AnimationConstants.UnspecifiedTime) {
642 totalDurationNanos
643 } else {
644 oldDurationNanos
645 } / (1000f * MillisToNanos)
646 if (oldDuration <= 0L) {
647 oldVelocity = ZeroVelocity
648 } else {
649 oldVelocity = AnimationVector1D(1f / oldDuration)
650 }
651 }
652 val newAnimation = runningAnimation ?: SeekingAnimationState()
653 newAnimation.animationSpec = newSpec
654 newAnimation.isComplete = false
655 newAnimation.value = fraction
656 newAnimation.start[0] = fraction
657 newAnimation.durationNanos = totalDurationNanos
658 newAnimation.progressNanos = 0L
659 newAnimation.initialVelocity = oldVelocity
660 newAnimation.animationSpecDuration =
661 newSpec?.getDurationNanos(
662 initialValue = newAnimation.start,
663 targetValue = Target1,
664 initialVelocity = oldVelocity
665 ) ?: (totalDurationNanos * (1.0 - fraction)).roundToLong()
666 currentAnimation = newAnimation
667 }
668 }
669 runAnimations()
670 currentState = targetState
671 waitForComposition()
672 fraction = 0f
673 }
674 }
675 transition.onTransitionEnd()
676 }
677 }
678
transitionConfigurednull679 override fun transitionConfigured(transition: Transition<S>) {
680 checkPrecondition(this.transition == null || transition == this.transition) {
681 "An instance of SeekableTransitionState has been used in different Transitions. " +
682 "Previous instance: ${this.transition}, new instance: $transition"
683 }
684 this.transition = transition
685 }
686
transitionRemovednull687 override fun transitionRemoved() {
688 this.transition = null
689 SeekableStateObserver.clear(this)
690 }
691
observeTotalDurationnull692 internal fun observeTotalDuration() {
693 SeekableStateObserver.observeReads(
694 scope = this,
695 onValueChangedForScope = SeekableTransitionStateTotalDurationChanged,
696 block = recalculateTotalDurationNanos
697 )
698 }
699
onTotalDurationChangednull700 internal fun onTotalDurationChanged() {
701 val previousTotalDurationNanos = totalDurationNanos
702 observeTotalDuration()
703 if (previousTotalDurationNanos != totalDurationNanos) {
704 val animation = currentAnimation
705 if (animation != null) {
706 if (animation.progressNanos > totalDurationNanos) {
707 endAllAnimations()
708 } else {
709 animation.durationNanos = totalDurationNanos
710 if (animation.animationSpec == null) {
711 animation.animationSpecDuration =
712 ((1.0 - animation.start[0]) * totalDurationNanos).roundToLong()
713 }
714 }
715 } else if (totalDurationNanos != 0L) {
716 // seekTo() called with a fraction. If an animation is running, we can just wait
717 // for the animation to change the value. The fraction may not be the best way
718 // to advance a regular animation.
719 seekToFraction()
720 }
721 }
722 }
723
seekToFractionnull724 private fun seekToFraction() {
725 val transition = transition ?: return
726 val playTimeNanos = (fraction.toDouble() * transition.totalDurationNanos).roundToLong()
727 transition.seekAnimations(playTimeNanos)
728 }
729
730 /**
731 * Contains the state for a running animation. This can be the current animation from
732 * [animateTo] or an initial value animation.
733 */
734 internal class SeekingAnimationState {
735 // The current progress with respect to the animationSpec if it exists or
736 // durationNanos if animationSpec is null
737 var progressNanos: Long = 0L
738
739 // The AnimationSpec used in this animation, or null if it is a linear animation with
740 // duration of durationNanos
741 var animationSpec: VectorizedAnimationSpec<AnimationVector1D>? = null
742
743 // Used by initial value animations to mark when the animation should continue
744 var isComplete = false
745
746 // The current fraction of the animation
747 var value: Float = 0f
748
749 // The start value of the animation
750 var start: AnimationVector1D = AnimationVector1D(0f)
751
752 // The initial velocity of the animation
753 var initialVelocity: AnimationVector1D? = null
754
755 // The total duration of the transition's animations. This is the totalDurationNanos
756 // at the time that this was created for initial value animations. Note that this can
757 // be different from the animationSpec's duration.
758 var durationNanos: Long = 0L
759
760 // The total duration of the animationSpec. This is kept cached because Spring
761 // animations can take time to calculate their durations
762 var animationSpecDuration: Long = 0L
763
toStringnull764 override fun toString(): String {
765 return "progress nanos: $progressNanos, animationSpec: $animationSpec," +
766 " isComplete: $isComplete, value: $value, start: $start," +
767 " initialVelocity: $initialVelocity, durationNanos: $durationNanos," +
768 " animationSpecDuration: $animationSpecDuration"
769 }
770 }
771
772 private companion object {
773 // AnimationVector1D with 0 value, kept so that we don't have to allocate unnecessarily
774 val ZeroVelocity = AnimationVector1D(0f)
775
776 // AnimationVector1D with 1 value, used as the target value of 1f
777 val Target1 = AnimationVector1D(1f)
778 }
779 }
780
781 /**
782 * Creates a [Transition] and puts it in the [currentState][TransitionState.currentState] of the
783 * provided [transitionState]. If the [TransitionState.targetState] changes, the [Transition] will
784 * change where it will animate to.
785 *
786 * __Remember__: The provided [transitionState] needs to be [remember]ed.
787 *
788 * Compared to [updateTransition] that takes a targetState, this function supports a different
789 * initial state than the first targetState. Here is an example:
790 *
791 * @sample androidx.compose.animation.core.samples.InitialStateSample
792 *
793 * In most cases, it is recommended to reuse the same [transitionState] that is [remember]ed, such
794 * that [Transition] preserves continuity when [targetState][MutableTransitionState.targetState] is
795 * changed. However, in some rare cases it is more critical to immediately *snap* to a state change
796 * (e.g. in response to a user interaction). This can be achieved by creating a new
797 * [transitionState]:
798 *
799 * @sample androidx.compose.animation.core.samples.DoubleTapToLikeSample
800 */
801 @Composable
rememberTransitionnull802 public fun <T> rememberTransition(
803 transitionState: TransitionState<T>,
804 label: String? = null
805 ): Transition<T> {
806 val transition =
807 remember(transitionState) {
808 // Remember currently observes state reads in its calculation lambda, which leads to
809 // recomposition on some state changes even though the lambda will not be invoked again.
810 // Tracked at b/392921611. Until this is fixed, we need to explicitly disable state
811 // observation in remember.
812 Snapshot.withoutReadObservation { Transition(transitionState = transitionState, label) }
813 }
814 if (transitionState is SeekableTransitionState) {
815 LaunchedEffect(transitionState.currentState, transitionState.targetState) {
816 transitionState.observeTotalDuration()
817 transitionState.compositionContinuationMutex.withLock {
818 transitionState.composedTargetState = transitionState.targetState
819 transitionState.compositionContinuation?.resume(transitionState.targetState)
820 transitionState.compositionContinuation = null
821 }
822 }
823 } else {
824 transition.animateTo(transitionState.targetState)
825 }
826 DisposableEffect(transition) {
827 onDispose {
828 // Clean up on the way out, to ensure the observers are not stuck in an in-between
829 // state.
830 transition.onDisposed()
831 }
832 }
833 return transition
834 }
835
836 /**
837 * Creates a [Transition] and puts it in the [currentState][MutableTransitionState.currentState] of
838 * the provided [transitionState]. Whenever the [targetState][MutableTransitionState.targetState] of
839 * the [transitionState] changes, the [Transition] will animate to the new target state.
840 *
841 * __Remember__: The provided [transitionState] needs to be [remember]ed.
842 *
843 * Compared to the [rememberTransition] variant that takes a targetState, this function supports a
844 * different initial state than the first targetState. Here is an example:
845 *
846 * @sample androidx.compose.animation.core.samples.InitialStateSample
847 *
848 * In most cases, it is recommended to reuse the same [transitionState] that is [remember]ed, such
849 * that [Transition] preserves continuity when [targetState][MutableTransitionState.targetState] is
850 * changed. However, in some rare cases it is more critical to immediately *snap* to a state change
851 * (e.g. in response to a user interaction). This can be achieved by creating a new
852 * [transitionState]:
853 *
854 * @sample androidx.compose.animation.core.samples.DoubleTapToLikeSample
855 */
856 @Deprecated(
857 "Use rememberTransition() instead",
858 replaceWith = ReplaceWith("rememberTransition(transitionState, label)")
859 )
860 @Composable
updateTransitionnull861 public fun <T> updateTransition(
862 transitionState: MutableTransitionState<T>,
863 label: String? = null
864 ): Transition<T> {
865 val state: TransitionState<T> = transitionState
866 return rememberTransition(state, label)
867 }
868
869 /**
870 * [Transition] manages all the child animations on a state level. Child animations can be created
871 * in a declarative way using [Transition.animateFloat], [Transition.animateValue],
872 * [animateColor][androidx.compose.animation.animateColor] etc. When the [targetState] changes,
873 * [Transition] will automatically start or adjust course for all its child animations to animate to
874 * the new target values defined for each animation.
875 *
876 * After arriving at [targetState], [Transition] will be triggered to run if any child animation
877 * changes its target value (due to their dynamic target calculation logic, such as theme-dependent
878 * values).
879 *
880 * @sample androidx.compose.animation.core.samples.GestureAnimationSample
881 * @see rememberTransition
882 * @see Transition.animateFloat
883 * @see Transition.animateValue
884 * @see androidx.compose.animation.animateColor
885 */
886 // TODO: Support creating Transition outside of composition and support imperative use of Transition
887 @Stable
888 public class Transition<S>
889 internal constructor(
890 private val transitionState: TransitionState<S>,
891 @get:RestrictTo(RestrictTo.Scope.LIBRARY) public val parentTransition: Transition<*>?,
892 public val label: String? = null
893 ) {
894 @PublishedApi
895 internal constructor(
896 transitionState: TransitionState<S>,
897 label: String? = null
898 ) : this(transitionState, null, label)
899
900 internal constructor(
901 initialState: S,
902 label: String?
903 ) : this(MutableTransitionState(initialState), null, label)
904
905 @PublishedApi
906 internal constructor(
907 transitionState: MutableTransitionState<S>,
908 label: String? = null
909 ) : this(transitionState as TransitionState<S>, null, label)
910
911 /**
912 * Current state of the transition. This will always be the initialState of the transition until
913 * the transition is finished. Once the transition is finished, [currentState] will be set to
914 * [targetState]. [currentState] is backed by a [MutableState].
915 */
916 public val currentState: S
917 get() = transitionState.currentState
918
919 /**
920 * Target state of the transition. This will be read by all child animations to determine their
921 * most up-to-date target values.
922 */
923 public var targetState: S by mutableStateOf(currentState)
924 internal set
925
926 /**
927 * [segment] contains the initial state and the target state of the currently on-going
928 * transition.
929 */
930 public var segment: Segment<S> by mutableStateOf(SegmentImpl(currentState, currentState))
931 private set
932
933 /** Indicates whether there is any animation running in the transition. */
934 public val isRunning: Boolean
935 get() = startTimeNanos != AnimationConstants.UnspecifiedTime
936
937 private var _playTimeNanos by mutableLongStateOf(0L)
938
939 /**
940 * Play time in nano-seconds. [playTimeNanos] is always non-negative. It starts from 0L at the
941 * beginning of the transition and increment until all child animations have finished.
942 */
943 @get:RestrictTo(RestrictTo.Scope.LIBRARY)
944 @set:RestrictTo(RestrictTo.Scope.LIBRARY)
945 public var playTimeNanos: Long
946 get() {
947 return parentTransition?.playTimeNanos ?: _playTimeNanos
948 }
949 set(value) {
950 if (parentTransition == null) {
951 _playTimeNanos = value
952 }
953 }
954
955 // startTimeNanos is in real frame time nanos for the root transition and
956 // scaled frame time for child transitions (as offset from the root start)
957 internal var startTimeNanos by mutableLongStateOf(AnimationConstants.UnspecifiedTime)
958
959 // This gets calculated every time child is updated/added
960 private var updateChildrenNeeded: Boolean by mutableStateOf(false)
961
962 private val _animations = mutableStateListOf<TransitionAnimationState<*, *>>()
963 private val _transitions = mutableStateListOf<Transition<*>>()
964
965 /** List of child transitions in a [Transition]. */
966 public val transitions: List<Transition<*>>
967 get() = _transitions
968
969 /** List of [TransitionAnimationState]s that are in a [Transition]. */
970 public val animations: List<TransitionAnimationState<*, *>>
971 get() = _animations
972
973 // Seeking related
974 @get:RestrictTo(RestrictTo.Scope.LIBRARY)
975 @set:RestrictTo(RestrictTo.Scope.LIBRARY)
976 public var isSeeking: Boolean by mutableStateOf(false)
977 internal set
978
979 internal var lastSeekedTimeNanos = 0L
980
981 /**
982 * Used internally to know when a [SeekableTransitionState] is animating initial values after
983 * [SeekableTransitionState.animateTo] or [SeekableTransitionState.seekTo] has redirected a
984 * transition prior to it completing. This is important for knowing when child transitions must
985 * be maintained after a parent target state has changed, but the child target state hasn't
986 * changed.
987 */
988 @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
989 @get:Suppress("GetterSetterNames") // Don't care about Java name for this property
990 @InternalAnimationApi
991 @get:InternalAnimationApi
992 public val hasInitialValueAnimations: Boolean
993 get() =
<lambda>null994 _animations.fastAny { it.initialValueState != null } ||
<lambda>null995 _transitions.fastAny { it.hasInitialValueAnimations }
996
997 /**
998 * Total duration of the [Transition], accounting for all the animations and child transitions
999 * defined on the [Transition].
1000 *
1001 * Note: The total duration is subject to change as more animations/child transitions get added
1002 * to [Transition]. It's strongly recommended to query this *after* all the animations in the
1003 * [Transition] are set up.
1004 */
<lambda>null1005 public val totalDurationNanos: Long by derivedStateOf { calculateTotalDurationNanos() }
1006
calculateTotalDurationNanosnull1007 private fun calculateTotalDurationNanos(): Long {
1008 var maxDurationNanos = 0L
1009 _animations.fastForEach { maxDurationNanos = max(maxDurationNanos, it.durationNanos) }
1010 _transitions.fastForEach {
1011 maxDurationNanos = max(maxDurationNanos, it.calculateTotalDurationNanos())
1012 }
1013 return maxDurationNanos
1014 }
1015
1016 @OptIn(InternalAnimationApi::class)
onFramenull1017 internal fun onFrame(frameTimeNanos: Long, durationScale: Float) {
1018 if (startTimeNanos == AnimationConstants.UnspecifiedTime) {
1019 onTransitionStart(frameTimeNanos)
1020 }
1021
1022 val deltaT = frameTimeNanos - startTimeNanos
1023 val scaledPlayTimeNanos =
1024 if (durationScale == 0f) {
1025 deltaT
1026 } else {
1027 (deltaT / durationScale.toDouble()).roundToLong()
1028 }
1029 playTimeNanos = scaledPlayTimeNanos
1030 onFrame(scaledPlayTimeNanos, durationScale == 0f)
1031 }
1032
onFramenull1033 internal fun onFrame(scaledPlayTimeNanos: Long, scaleToEnd: Boolean) {
1034 if (startTimeNanos == AnimationConstants.UnspecifiedTime) {
1035 onTransitionStart(scaledPlayTimeNanos)
1036 } else if (!transitionState.isRunning) {
1037 transitionState.isRunning = true
1038 }
1039 updateChildrenNeeded = false
1040
1041 var allFinished = true
1042 // Pulse new playtime
1043 _animations.fastForEach {
1044 if (!it.isFinished) {
1045 it.onPlayTimeChanged(scaledPlayTimeNanos, scaleToEnd)
1046 }
1047 // Check isFinished flag again after the animation pulse
1048 if (!it.isFinished) {
1049 allFinished = false
1050 }
1051 }
1052 _transitions.fastForEach {
1053 if (it.targetState != it.currentState) {
1054 it.onFrame(scaledPlayTimeNanos, scaleToEnd)
1055 }
1056 if (it.targetState != it.currentState) {
1057 allFinished = false
1058 }
1059 }
1060 if (allFinished) {
1061 onTransitionEnd()
1062 }
1063 }
1064
1065 init {
1066 transitionState.transitionConfigured(this)
1067 }
1068
1069 // onTransitionStart and onTransitionEnd are symmetric. Both are called from onFrame
onTransitionStartnull1070 internal fun onTransitionStart(frameTimeNanos: Long) {
1071 startTimeNanos = frameTimeNanos
1072 transitionState.isRunning = true
1073 }
1074
1075 // Called when the Transition is being disposed to clean up any state
onDisposednull1076 internal fun onDisposed() {
1077 onTransitionEnd()
1078 transitionState.transitionRemoved()
1079 }
1080
1081 // onTransitionStart and onTransitionEnd are symmetric. Both are called from onFrame
1082 @OptIn(InternalAnimationApi::class)
onTransitionEndnull1083 internal fun onTransitionEnd() {
1084 startTimeNanos = AnimationConstants.UnspecifiedTime
1085 if (transitionState is MutableTransitionState) {
1086 transitionState.currentState = targetState
1087 }
1088 playTimeNanos = 0
1089 transitionState.isRunning = false
1090 _transitions.fastForEach { it.onTransitionEnd() }
1091 }
1092
1093 /**
1094 * This allows tools to set the transition (between initial and target state) to a specific
1095 * [playTimeNanos].
1096 *
1097 * Note: This function is intended for tooling use only.
1098 *
1099 * __Caveat:__ Once [initialState] or [targetState] changes, it needs to take a whole
1100 * composition pass for all the animations and child transitions to recompose with the new
1101 * [initialState] and [targetState]. Subsequently all the animations will be updated to the
1102 * given play time.
1103 *
1104 * __Caveat:__ This function puts [Transition] in a manual playtime setting mode. From then on
1105 * the [Transition] will not resume normal animation runs.
1106 */
1107 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
1108 @OptIn(InternalAnimationApi::class)
1109 @JvmName("seek")
setPlaytimeAfterInitialAndTargetStateEstablishednull1110 public fun setPlaytimeAfterInitialAndTargetStateEstablished(
1111 initialState: S,
1112 targetState: S,
1113 playTimeNanos: Long
1114 ) {
1115 // Reset running state
1116 startTimeNanos = AnimationConstants.UnspecifiedTime
1117 transitionState.isRunning = false
1118 if (!isSeeking || this.currentState != initialState || this.targetState != targetState) {
1119 // Reset all child animations
1120 if (currentState != initialState && transitionState is MutableTransitionState) {
1121 transitionState.currentState = initialState
1122 }
1123 this.targetState = targetState
1124 isSeeking = true
1125 segment = SegmentImpl(initialState, targetState)
1126 }
1127
1128 _transitions.fastForEach {
1129 @Suppress("UNCHECKED_CAST")
1130 (it as Transition<Any>).let {
1131 if (it.isSeeking) {
1132 it.setPlaytimeAfterInitialAndTargetStateEstablished(
1133 it.currentState,
1134 it.targetState,
1135 playTimeNanos
1136 )
1137 }
1138 }
1139 }
1140
1141 _animations.fastForEach { it.seekTo(playTimeNanos) }
1142 lastSeekedTimeNanos = playTimeNanos
1143 }
1144
addTransitionnull1145 internal fun addTransition(transition: Transition<*>) = _transitions.add(transition)
1146
1147 internal fun removeTransition(transition: Transition<*>) = _transitions.remove(transition)
1148
1149 internal fun addAnimation(animation: TransitionAnimationState<*, *>) =
1150 _animations.add(animation)
1151
1152 internal fun removeAnimation(animation: TransitionAnimationState<*, *>) {
1153 _animations.remove(animation)
1154 }
1155
1156 // This target state should only be used to modify "mutableState"s, as it could potentially
1157 // roll back. The
updateTargetnull1158 internal fun updateTarget(targetState: S) {
1159 // This is needed because child animations rely on this target state and the state pair to
1160 // update their animation specs
1161 if (this.targetState != targetState) {
1162 // Starting state should be the "next" state when waypoints are impl'ed
1163 segment = SegmentImpl(this.targetState, targetState)
1164 if (currentState != this.targetState) {
1165 transitionState.currentState = this.targetState
1166 }
1167 this.targetState = targetState
1168 if (!isRunning) {
1169 updateChildrenNeeded = true
1170 }
1171
1172 // If target state is changed, reset all the animations to be re-created in the
1173 // next frame w/ their new target value. Child animations target values are updated in
1174 // the side effect that may not have happened when this function in invoked.
1175 _animations.fastForEach { it.resetAnimation() }
1176 }
1177 }
1178
1179 // This should only be called if PlayTime comes from clock directly, instead of from a parent
1180 // Transition.
1181 @Suppress("ComposableNaming")
1182 @Composable
animateTonull1183 internal fun animateTo(targetState: S) {
1184 if (!isSeeking) {
1185 updateTarget(targetState)
1186 // target != currentState adds the effect into the tree in the same frame as
1187 // target change.
1188 val runFrameLoop by
1189 remember(this) {
1190 derivedStateOf {
1191 this.targetState != currentState || isRunning || updateChildrenNeeded
1192 }
1193 }
1194 if (runFrameLoop) {
1195 // We're using a composition-obtained scope + DisposableEffect here to give us
1196 // control over coroutine dispatching
1197 val coroutineScope = rememberCoroutineScope()
1198 DisposableEffect(coroutineScope, this) {
1199 // Launch the coroutine undispatched so the block is executed in the current
1200 // frame. This is important as this initializes the state.
1201 coroutineScope.launch(start = CoroutineStart.UNDISPATCHED) {
1202 val durationScale = coroutineContext.durationScale
1203 while (isActive) {
1204 withFrameNanos {
1205 // This check is very important, as isSeeking may be changed
1206 // off-band between the last check in composition and this callback
1207 // which happens in the animation callback the next frame.
1208 if (!isSeeking) {
1209 onFrame(it / AnimationDebugDurationScale, durationScale)
1210 }
1211 }
1212 }
1213 }
1214 onDispose {}
1215 }
1216 }
1217 }
1218 }
1219
1220 /**
1221 * Used by [SeekableTransitionState] to seek the current transition animation to
1222 * [playTimeNanos].
1223 */
seekAnimationsnull1224 internal fun seekAnimations(playTimeNanos: Long) {
1225 if (startTimeNanos == AnimationConstants.UnspecifiedTime) {
1226 startTimeNanos = playTimeNanos
1227 }
1228 this.playTimeNanos = playTimeNanos
1229 updateChildrenNeeded = false
1230
1231 // Pulse new playtime
1232 _animations.fastForEach { it.seekTo(playTimeNanos) }
1233 _transitions.fastForEach {
1234 if (it.targetState != it.currentState) {
1235 it.seekAnimations(playTimeNanos)
1236 }
1237 }
1238 }
1239
1240 /**
1241 * Changes the existing animations to be initial value animations. An existing animation was
1242 * interrupted, so the current animation is used only for the initial values. The current
1243 * animation is then changed to an unchanging animation that is only moved by the initial value.
1244 */
setInitialAnimationsnull1245 internal fun setInitialAnimations(
1246 animationState: SeekableTransitionState.SeekingAnimationState
1247 ) {
1248 _animations.fastForEach { it.setInitialValueAnimation(animationState) }
1249 _transitions.fastForEach { it.setInitialAnimations(animationState) }
1250 }
1251
1252 /**
1253 * Clears all animations. The state has been forced directly to a new value and the animations
1254 * are no longer valid.
1255 */
resetAnimationFractionnull1256 internal fun resetAnimationFraction(fraction: Float) {
1257 _animations.fastForEach { it.resetAnimationValue(fraction) }
1258 _transitions.fastForEach { it.resetAnimationFraction(fraction) }
1259 }
1260
1261 /** Clears all initial value animations. */
clearInitialAnimationsnull1262 internal fun clearInitialAnimations() {
1263 _animations.fastForEach { it.clearInitialAnimation() }
1264 _transitions.fastForEach { it.clearInitialAnimations() }
1265 }
1266
1267 /**
1268 * Changes the progress of the initial value.
1269 *
1270 * @return true if the animationState is animating anything or false if it isn't animating
1271 * anything.
1272 */
updateInitialValuesnull1273 internal fun updateInitialValues() {
1274 _animations.fastForEach { it.updateInitialValue() }
1275 _transitions.fastForEach { it.updateInitialValues() }
1276 }
1277
toStringnull1278 override fun toString(): String {
1279 return animations.fastFold("Transition animation values: ") { acc, anim -> "$acc$anim, " }
1280 }
1281
1282 @OptIn(InternalAnimationApi::class)
onChildAnimationUpdatednull1283 private fun onChildAnimationUpdated() {
1284 updateChildrenNeeded = true
1285 if (isSeeking) {
1286 // Update total duration
1287 var maxDurationNanos = 0L
1288 _animations.fastForEach {
1289 maxDurationNanos = max(maxDurationNanos, it.durationNanos)
1290 it.seekTo(lastSeekedTimeNanos)
1291 }
1292 // TODO: Is update duration the only thing that needs to be done during seeking to
1293 // accommodate update children?
1294 updateChildrenNeeded = false
1295 }
1296 }
1297
1298 /**
1299 * Each animation created using [animateFloat], [animateDp], etc is represented as a
1300 * [TransitionAnimationState] in [Transition].
1301 */
1302 @Stable
1303 public inner class TransitionAnimationState<T, V : AnimationVector>
1304 internal constructor(
1305 initialValue: T,
1306 initialVelocityVector: V,
1307 public val typeConverter: TwoWayConverter<T, V>,
1308 public val label: String
1309 ) : State<T> {
1310
1311 // Changed during composition, may rollback
1312 private var targetValue: T by mutableStateOf(initialValue)
1313
1314 private val defaultSpring = spring<T>()
1315
1316 /**
1317 * [AnimationSpec] that is used for current animation run. This can change when
1318 * [targetState] changes.
1319 */
1320 public var animationSpec: FiniteAnimationSpec<T> by mutableStateOf(defaultSpring)
1321 private set
1322
1323 /**
1324 * All the animation configurations including initial value/velocity & target value for
1325 * animating from [currentState] to [targetState] are captured in [animation].
1326 */
1327 public var animation: TargetBasedAnimation<T, V> by
1328 mutableStateOf(
1329 TargetBasedAnimation(
1330 animationSpec,
1331 typeConverter,
1332 initialValue,
1333 targetValue,
1334 initialVelocityVector
1335 )
1336 )
1337 private set
1338
1339 internal var initialValueState: SeekableTransitionState.SeekingAnimationState? = null
1340 private var initialValueAnimation: TargetBasedAnimation<T, V>? = null
1341
1342 internal var isFinished: Boolean by mutableStateOf(true)
1343 internal var resetSnapValue by mutableFloatStateOf(NoReset)
1344
1345 /**
1346 * When the target state has changed, but the target value remains the same, the initial
1347 * value animation completely controls the animated value. When this flag is `true`, the
1348 * [animation] can be ignored and only the [initialValueState] is needed to determine the
1349 * value. When this is `false`, if there is an [initialValueState], it is used only for
1350 * adjusting the initial value of [animation].
1351 */
1352 private var useOnlyInitialValue = false
1353
1354 // Changed during animation, no concerns of rolling back
1355 override var value: T by mutableStateOf(initialValue)
1356 internal set
1357
1358 private var velocityVector: V = initialVelocityVector
1359 internal var durationNanos by mutableLongStateOf(animation.durationNanos)
1360
1361 private var isSeeking = false
1362
onPlayTimeChangednull1363 internal fun onPlayTimeChanged(playTimeNanos: Long, scaleToEnd: Boolean) {
1364 val playTime = if (scaleToEnd) animation.durationNanos else playTimeNanos
1365 value = animation.getValueFromNanos(playTime)
1366 velocityVector = animation.getVelocityVectorFromNanos(playTime)
1367 if (animation.isFinishedFromNanos(playTime)) {
1368 isFinished = true
1369 }
1370 }
1371
seekTonull1372 internal fun seekTo(playTimeNanos: Long) {
1373 if (resetSnapValue != NoReset) {
1374 return
1375 }
1376 isSeeking = true // SeekableTransitionState won't use interrupted animation spec
1377 if (animation.targetValue == animation.initialValue) {
1378 // This is likely an interrupted animation and the initial value is changing, but
1379 // the target value remained the same. The initial value animation has the target
1380 // value, so only the initial value animation is changing the value.
1381 value = animation.targetValue
1382 } else {
1383 // TODO: unlikely but need to double check that animation returns the correct values
1384 // when play time is way past their durations.
1385 value = animation.getValueFromNanos(playTimeNanos)
1386 velocityVector = animation.getVelocityVectorFromNanos(playTimeNanos)
1387 }
1388 }
1389
1390 /**
1391 * Updates the initial value animation. When a SeekableTransitionState transition is
1392 * interrupted, the ongoing animation is moved to changing the initial value. The starting
1393 * point of the animation is then animated toward the value that would be set at the target
1394 * state, while the current value is controlled by seeking or animation.
1395 */
updateInitialValuenull1396 internal fun updateInitialValue() {
1397 val animState = initialValueState ?: return
1398 val animation = initialValueAnimation ?: return
1399
1400 val initialPlayTimeNanos =
1401 (
1402 // Single-precision floating point is not sufficient here as it only has about 7
1403 // decimal digits of precision. We are dealing with nanos which has at least 9
1404 // decimal digits in most cases.
1405 animState.durationNanos * animState.value.toDouble())
1406 .roundToLong()
1407 val initialValue = animation.getValueFromNanos(initialPlayTimeNanos)
1408 if (useOnlyInitialValue) {
1409 this.animation.mutableTargetValue = initialValue
1410 }
1411 this.animation.mutableInitialValue = initialValue
1412 durationNanos = this.animation.durationNanos
1413 if (resetSnapValue == ResetNoSnap || useOnlyInitialValue) {
1414 value = initialValue
1415 } else {
1416 seekTo(playTimeNanos)
1417 }
1418 if (initialPlayTimeNanos >= animState.durationNanos) {
1419 initialValueState = null
1420 initialValueAnimation = null
1421 } else {
1422 animState.isComplete = false
1423 }
1424 }
1425
1426 private val interruptionSpec: FiniteAnimationSpec<T>
1427
1428 init {
1429 val visibilityThreshold: T? =
<lambda>null1430 VisibilityThresholdMap.get(typeConverter)?.let {
1431 val vector = typeConverter.convertToVector(initialValue)
1432 for (id in 0 until vector.size) {
1433 vector[id] = it
1434 }
1435 typeConverter.convertFromVector(vector)
1436 }
1437 interruptionSpec = spring(visibilityThreshold = visibilityThreshold)
1438 }
1439
updateAnimationnull1440 private fun updateAnimation(
1441 initialValue: T = value,
1442 isInterrupted: Boolean = false,
1443 ) {
1444 if (initialValueAnimation?.targetValue == targetValue) {
1445 // This animation didn't change the target value, so let the initial value animation
1446 // take care of it.
1447 animation =
1448 TargetBasedAnimation(
1449 interruptionSpec,
1450 typeConverter,
1451 initialValue,
1452 initialValue,
1453 velocityVector.newInstance() // 0 velocity
1454 )
1455 useOnlyInitialValue = true
1456 durationNanos = animation.durationNanos
1457 return
1458 }
1459 val specWithoutDelay =
1460 if (isInterrupted && !isSeeking) {
1461 // When interrupted, use the default spring, unless the spec is also a spring.
1462 if (animationSpec is SpringSpec<*>) animationSpec else interruptionSpec
1463 } else {
1464 animationSpec
1465 }
1466 val spec =
1467 if (playTimeNanos <= 0L) {
1468 specWithoutDelay
1469 } else {
1470 delayed(specWithoutDelay, playTimeNanos)
1471 }
1472 animation =
1473 TargetBasedAnimation(spec, typeConverter, initialValue, targetValue, velocityVector)
1474 durationNanos = animation.durationNanos
1475 useOnlyInitialValue = false
1476 onChildAnimationUpdated()
1477 }
1478
resetAnimationnull1479 internal fun resetAnimation() {
1480 resetSnapValue = ResetNoSnap
1481 }
1482
1483 /**
1484 * Forces the value to the given fraction or reset value. If [fraction] is
1485 * [ResetAnimationSnapCurrent] or [ResetAnimationSnapTarget], the animated values are
1486 * directly moved to the start or end of the animation.
1487 */
resetAnimationValuenull1488 internal fun resetAnimationValue(fraction: Float) {
1489 if (fraction == ResetAnimationSnapCurrent || fraction == ResetAnimationSnapTarget) {
1490 val initAnim = initialValueAnimation
1491 if (initAnim != null) {
1492 animation.mutableInitialValue = initAnim.targetValue
1493 initialValueState = null
1494 initialValueAnimation = null
1495 }
1496
1497 val animationValue =
1498 if (fraction == ResetAnimationSnapCurrent) {
1499 animation.initialValue
1500 } else {
1501 animation.targetValue
1502 }
1503 animation.mutableInitialValue = animationValue
1504 animation.mutableTargetValue = animationValue
1505 value = animationValue
1506 durationNanos = animation.durationNanos
1507 } else {
1508 resetSnapValue = fraction
1509 }
1510 }
1511
setInitialValueAnimationnull1512 internal fun setInitialValueAnimation(
1513 animationState: SeekableTransitionState.SeekingAnimationState
1514 ) {
1515 if (animation.targetValue != animation.initialValue) {
1516 // Continue the animation from the current position to the target
1517 initialValueAnimation = animation
1518 initialValueState = animationState
1519 }
1520 animation =
1521 TargetBasedAnimation(
1522 interruptionSpec,
1523 typeConverter,
1524 value,
1525 value,
1526 velocityVector.newInstance() // 0 velocity
1527 )
1528 durationNanos = animation.durationNanos
1529 useOnlyInitialValue = true
1530 }
1531
clearInitialAnimationnull1532 internal fun clearInitialAnimation() {
1533 initialValueAnimation = null
1534 initialValueState = null
1535 useOnlyInitialValue = false
1536 }
1537
toStringnull1538 override fun toString(): String {
1539 return "current value: $value, target: $targetValue, spec: $animationSpec"
1540 }
1541
1542 // This gets called *during* composition
1543 @OptIn(InternalAnimationApi::class)
updateTargetValuenull1544 internal fun updateTargetValue(targetValue: T, animationSpec: FiniteAnimationSpec<T>) {
1545 if (useOnlyInitialValue && targetValue == initialValueAnimation?.targetValue) {
1546 return // we're already animating to the target value through the initial value
1547 }
1548 if (this.targetValue == targetValue && resetSnapValue == NoReset) {
1549 return // nothing to change. Just continue the existing animation.
1550 }
1551 this.targetValue = targetValue
1552 this.animationSpec = animationSpec
1553 val initialValue = if (resetSnapValue == ResetAnimationSnap) targetValue else value
1554 updateAnimation(initialValue, isInterrupted = !isFinished)
1555 isFinished = resetSnapValue == ResetAnimationSnap
1556 // This is needed because the target change could happen during a transition
1557 if (resetSnapValue >= 0f) {
1558 val duration = animation.durationNanos
1559 value = animation.getValueFromNanos((duration * resetSnapValue).toLong())
1560 } else if (resetSnapValue == ResetAnimationSnap) {
1561 value = targetValue
1562 }
1563 useOnlyInitialValue = false
1564 resetSnapValue = NoReset
1565 }
1566
1567 // This gets called *during* composition
updateInitialAndTargetValuenull1568 internal fun updateInitialAndTargetValue(
1569 initialValue: T,
1570 targetValue: T,
1571 animationSpec: FiniteAnimationSpec<T>
1572 ) {
1573 this.targetValue = targetValue
1574 this.animationSpec = animationSpec
1575 if (animation.initialValue == initialValue && animation.targetValue == targetValue) {
1576 return
1577 }
1578 updateAnimation(initialValue)
1579 }
1580 }
1581
1582 private class SegmentImpl<S>(override val initialState: S, override val targetState: S) :
1583 Segment<S> {
equalsnull1584 override fun equals(other: Any?): Boolean {
1585 return other is Segment<*> &&
1586 initialState == other.initialState &&
1587 targetState == other.targetState
1588 }
1589
hashCodenull1590 override fun hashCode(): Int {
1591 return initialState.hashCode() * 31 + targetState.hashCode()
1592 }
1593 }
1594
1595 /**
1596 * [Segment] holds [initialState] and [targetState], which are the beginning and end of a
1597 * transition. These states will be used to obtain the animation spec that will be used for this
1598 * transition from the child animations.
1599 */
1600 @JvmDefaultWithCompatibility
1601 public interface Segment<S> {
1602 /** Initial state of a Transition Segment. This is the state that transition starts from. */
1603 public val initialState: S
1604
1605 /** Target state of a Transition Segment. This is the state that transition will end on. */
1606 public val targetState: S
1607
1608 /**
1609 * Returns whether the provided state matches the [initialState] && the provided
1610 * [targetState] matches [Segment.targetState].
1611 */
isTransitioningTonull1612 public infix fun S.isTransitioningTo(targetState: S): Boolean {
1613 return this == initialState && targetState == this@Segment.targetState
1614 }
1615 }
1616
1617 /**
1618 * [DeferredAnimation] can be constructed using [Transition.createDeferredAnimation] during
1619 * composition and initialized later. It is useful for animations, the target values for which
1620 * are unknown at composition time (e.g. layout size/position, etc).
1621 *
1622 * Once a [DeferredAnimation] is created, it can be configured and updated as needed using
1623 * [DeferredAnimation.animate] method.
1624 */
1625 @RestrictTo(RestrictTo.Scope.LIBRARY)
1626 public inner class DeferredAnimation<T, V : AnimationVector>
1627 internal constructor(
1628 public val typeConverter: TwoWayConverter<T, V>,
1629 public val label: String
1630 ) {
1631 internal var data: DeferredAnimationData<T, V>? by mutableStateOf(null)
1632
1633 internal inner class DeferredAnimationData<T, V : AnimationVector>(
1634 val animation: Transition<S>.TransitionAnimationState<T, V>,
1635 var transitionSpec: Segment<S>.() -> FiniteAnimationSpec<T>,
1636 var targetValueByState: (state: S) -> T,
1637 ) : State<T> {
updateAnimationStatesnull1638 fun updateAnimationStates(segment: Segment<S>) {
1639 val targetValue = targetValueByState(segment.targetState)
1640 if (isSeeking) {
1641 val initialValue = targetValueByState(segment.initialState)
1642 // In the case of seeking, we also need to update initial value as needed
1643 animation.updateInitialAndTargetValue(
1644 initialValue,
1645 targetValue,
1646 segment.transitionSpec()
1647 )
1648 } else {
1649 animation.updateTargetValue(targetValue, segment.transitionSpec())
1650 }
1651 }
1652
1653 override val value: T
1654 get() {
1655 updateAnimationStates(segment)
1656 return animation.value
1657 }
1658 }
1659
1660 /**
1661 * [DeferredAnimation] allows the animation setup to be deferred until a later time after
1662 * composition. [animate] can be used to set up a [DeferredAnimation]. Like other Transition
1663 * animations such as [Transition.animateFloat], [DeferredAnimation] also expects
1664 * [transitionSpec] and [targetValueByState] for the mapping from target state to animation
1665 * spec and target value, respectively.
1666 */
animatenull1667 public fun animate(
1668 transitionSpec: Segment<S>.() -> FiniteAnimationSpec<T>,
1669 targetValueByState: (state: S) -> T
1670 ): State<T> {
1671 val animData: DeferredAnimationData<T, V> =
1672 data
1673 ?: DeferredAnimationData(
1674 TransitionAnimationState(
1675 targetValueByState(currentState),
1676 typeConverter.createZeroVectorFrom(
1677 targetValueByState(currentState)
1678 ),
1679 typeConverter,
1680 label
1681 ),
1682 transitionSpec,
1683 targetValueByState
1684 )
1685 .apply {
1686 data = this
1687 addAnimation(this.animation)
1688 }
1689 return animData.apply {
1690 // Update animtion data with the latest mapping
1691 this.targetValueByState = targetValueByState
1692 this.transitionSpec = transitionSpec
1693
1694 updateAnimationStates(segment)
1695 }
1696 }
1697
setupSeekingnull1698 internal fun setupSeeking() {
1699 data?.apply {
1700 animation.updateInitialAndTargetValue(
1701 targetValueByState(segment.initialState),
1702 targetValueByState(segment.targetState),
1703 segment.transitionSpec()
1704 )
1705 }
1706 }
1707 }
1708
removeAnimationnull1709 internal fun removeAnimation(deferredAnimation: DeferredAnimation<*, *>) {
1710 deferredAnimation.data?.animation?.let { removeAnimation(it) }
1711 }
1712 }
1713
1714 // When a TransitionAnimation doesn't need to be reset
1715 private const val NoReset = -1f
1716
1717 // When the animation needs to be changed because of a target update
1718 private const val ResetNoSnap = -2f
1719
1720 // When the animation should be reset to have the same start and end value
1721 private const val ResetAnimationSnap = -3f
1722
1723 // Snap to the current state and set the initial and target values to the same thing
1724 private const val ResetAnimationSnapCurrent = -4f
1725
1726 // Snap to the target state and set the initial and target values to the same thing
1727 private const val ResetAnimationSnapTarget = -5f
1728
1729 /**
1730 * This creates a [DeferredAnimation], which will not animate until it is set up using
1731 * [DeferredAnimation.animate]. Once the animation is set up, it will animate from the
1732 * [currentState][Transition.currentState] to [targetState][Transition.targetState]. If the
1733 * [Transition] has already arrived at its target state at the time when the animation added, there
1734 * will be no animation.
1735 *
1736 * @param typeConverter A converter to convert any value of type [T] from/to an [AnimationVector]
1737 * @param label A label for differentiating this animation from others in android studio.
1738 */
1739 @RestrictTo(RestrictTo.Scope.LIBRARY)
1740 @Composable
createDeferredAnimationnull1741 public fun <S, T, V : AnimationVector> Transition<S>.createDeferredAnimation(
1742 typeConverter: TwoWayConverter<T, V>,
1743 label: String = "DeferredAnimation"
1744 ): Transition<S>.DeferredAnimation<T, V> {
1745 val lazyAnim = remember(this) { DeferredAnimation(typeConverter, label) }
1746 DisposableEffect(lazyAnim) { onDispose { removeAnimation(lazyAnim) } }
1747 if (isSeeking) {
1748 lazyAnim.setupSeeking()
1749 }
1750 return lazyAnim
1751 }
1752
1753 /**
1754 * [createChildTransition] creates a child Transition based on the mapping between parent state to
1755 * child state provided in [transformToChildState]. This serves the following purposes:
1756 * 1) Hoist the child transition state into parent transition. Therefore the parent Transition will
1757 * be aware of whether there's any on-going animation due to the same target state change. This
1758 * will further allow sequential animation to be set up when all animations have finished.
1759 * 2) Separation of concerns. The child transition can respresent a much more simplified state
1760 * transition when, for example, mapping from an enum parent state to a Boolean visible state for
1761 * passing further down the compose tree. The child composables hence can be designed around
1762 * handling a more simple and a more relevant state change.
1763 *
1764 * [label] is used to differentiate from other animations in the same transition in Android Studio.
1765 *
1766 * @sample androidx.compose.animation.core.samples.CreateChildTransitionSample
1767 */
1768 @ExperimentalTransitionApi
1769 @Composable
createChildTransitionnull1770 public inline fun <S, T> Transition<S>.createChildTransition(
1771 label: String = "ChildTransition",
1772 transformToChildState: @Composable (parentState: S) -> T,
1773 ): Transition<T> {
1774 val initialParentState = remember(this) { this.currentState }
1775 val initialState = transformToChildState(if (isSeeking) currentState else initialParentState)
1776 val targetState = transformToChildState(this.targetState)
1777 return createChildTransitionInternal(initialState, targetState, label)
1778 }
1779
1780 @PublishedApi
1781 @Composable
createChildTransitionInternalnull1782 internal fun <S, T> Transition<S>.createChildTransitionInternal(
1783 initialState: T,
1784 targetState: T,
1785 childLabel: String,
1786 ): Transition<T> {
1787 val transition =
1788 remember(this) {
1789 Transition(MutableTransitionState(initialState), this, "${this.label} > $childLabel")
1790 }
1791
1792 DisposableEffect(transition) {
1793 addTransition(transition)
1794 onDispose { removeTransition(transition) }
1795 }
1796
1797 if (isSeeking) {
1798 transition.setPlaytimeAfterInitialAndTargetStateEstablished(
1799 initialState,
1800 targetState,
1801 this.lastSeekedTimeNanos
1802 )
1803 } else {
1804 transition.updateTarget(targetState)
1805 transition.isSeeking = false
1806 }
1807 return transition
1808 }
1809
1810 /**
1811 * Creates an animation of type [T] as a part of the given [Transition]. This means the states of
1812 * this animation will be managed by the [Transition]. [typeConverter] will be used to convert
1813 * between type [T] and [AnimationVector] so that the animation system knows how to animate it.
1814 *
1815 * [targetValueByState] is used as a mapping from a target state to the target value of this
1816 * animation. [Transition] will be using this mapping to determine what value to target this
1817 * animation towards. __Note__ that [targetValueByState] is a composable function. This means the
1818 * mapping function could access states, CompositionLocals, themes, etc. If the targetValue changes
1819 * outside of a [Transition] run (i.e. when the [Transition] already reached its targetState), the
1820 * [Transition] will start running again to ensure this animation reaches its new target smoothly.
1821 *
1822 * An optional [transitionSpec] can be provided to specify (potentially different) animation for
1823 * each pair of initialState and targetState. [FiniteAnimationSpec] includes any non-infinite
1824 * animation, such as [tween], [spring], [keyframes] and even [repeatable], but not
1825 * [infiniteRepeatable]. By default, [transitionSpec] uses a [spring] animation for all transition
1826 * destinations.
1827 *
1828 * [label] is used to differentiate from other animations in the same transition in Android Studio.
1829 *
1830 * @return A [State] object, the value of which is updated by animation
1831 * @see updateTransition
1832 * @see rememberTransition
1833 * @see Transition.animateFloat
1834 * @see androidx.compose.animation.animateColor
1835 */
1836 @Composable
animateValuenull1837 public inline fun <S, T, V : AnimationVector> Transition<S>.animateValue(
1838 typeConverter: TwoWayConverter<T, V>,
1839 noinline transitionSpec: @Composable Transition.Segment<S>.() -> FiniteAnimationSpec<T> = {
1840 spring()
1841 },
1842 label: String = "ValueAnimation",
1843 targetValueByState: @Composable (state: S) -> T
1844 ): State<T> {
1845
1846 val initialState =
1847 if (!isSeeking) {
1848 // For non-seeking use cases, we can avoid reading currentState frequently by querying
1849 // it only once when initializing TransitionAnimation.
<lambda>null1850 remember(this) { Snapshot.withoutReadObservation { currentState } }
1851 } else {
1852 currentState
1853 }
1854 val initialValue = targetValueByState(initialState)
1855
1856 // `targetState` is changed in composition in rememberTransition before consumed here. Due to
1857 // b/393642162, this consumption will cause a 2nd recomposition, meaning this read will cause
1858 // recomposition for both the first and second frame of the animation. As a temporary
1859 // workaround, `derivedStateOf` is added here to avoid recomposing when the state value is the
1860 // same.
<lambda>null1861 val targetValue = targetValueByState(remember(this) { derivedStateOf { targetState } }.value)
<lambda>null1862 val animationSpec = transitionSpec(remember(this) { derivedStateOf { segment } }.value)
1863
1864 return createTransitionAnimation(initialValue, targetValue, animationSpec, typeConverter, label)
1865 }
1866
1867 @PublishedApi
1868 @Composable
createTransitionAnimationnull1869 internal fun <S, T, V : AnimationVector> Transition<S>.createTransitionAnimation(
1870 initialValue: T,
1871 targetValue: T,
1872 animationSpec: FiniteAnimationSpec<T>,
1873 typeConverter: TwoWayConverter<T, V>,
1874 label: String
1875 ): State<T> {
1876 val transitionAnimation =
1877 remember(this) {
1878 // Remember currently observes state reads in its calculation lambda, which leads to
1879 // recomposition on some state changes even though the lambda will not be invoked again.
1880 // Tracked at b/392921611. Until this is fixed, we need to explicitly disable state
1881 // observation in remember.
1882 Snapshot.withoutReadObservation {
1883 // Initialize the animation state to initialState value, so if it's added during a
1884 // transition run, it'll participate in the animation.
1885 // This is preferred because it's easy to opt out - Simply adding new animation once
1886 // currentState == targetState would opt out.
1887 TransitionAnimationState(
1888 initialValue,
1889 typeConverter.createZeroVectorFrom(targetValue),
1890 typeConverter,
1891 label
1892 )
1893 }
1894 }
1895 UpdateInitialAndTargetValues(transitionAnimation, initialValue, targetValue, animationSpec)
1896
1897 DisposableEffect(transitionAnimation) {
1898 addAnimation(transitionAnimation)
1899 onDispose { removeAnimation(transitionAnimation) }
1900 }
1901 return transitionAnimation
1902 }
1903
1904 // This composable function is needed to contain recompositions caused by reads of the internal
1905 // animation states to an internal recomposition scope. Without this function, the caller of
1906 // animateValue gets recomposed when an animation starts.
1907 @Composable
UpdateInitialAndTargetValuesnull1908 private fun <S, T, V : AnimationVector> Transition<S>.UpdateInitialAndTargetValues(
1909 transitionAnimation: Transition<S>.TransitionAnimationState<T, V>,
1910 initialValue: T,
1911 targetValue: T,
1912 animationSpec: FiniteAnimationSpec<T>
1913 ) {
1914 if (isSeeking) {
1915 // In the case of seeking, we also need to update initial value as needed
1916 transitionAnimation.updateInitialAndTargetValue(initialValue, targetValue, animationSpec)
1917 } else {
1918 transitionAnimation.updateTargetValue(targetValue, animationSpec)
1919 }
1920 }
1921
1922 // TODO: Remove noinline when b/174814083 is fixed.
1923 /**
1924 * Creates a Float animation as a part of the given [Transition]. This means the states of this
1925 * animation will be managed by the [Transition].
1926 *
1927 * [targetValueByState] is used as a mapping from a target state to the target value of this
1928 * animation. [Transition] will be using this mapping to determine what value to target this
1929 * animation towards. __Note__ that [targetValueByState] is a composable function. This means the
1930 * mapping function could access states, CompositionLocals, themes, etc. If the targetValue changes
1931 * outside of a [Transition] run (i.e. when the [Transition] already reached its targetState), the
1932 * [Transition] will start running again to ensure this animation reaches its new target smoothly.
1933 *
1934 * An optional [transitionSpec] can be provided to specify (potentially different) animation for
1935 * each pair of initialState and targetState. [FiniteAnimationSpec] includes any non-infinite
1936 * animation, such as [tween], [spring], [keyframes] and even [repeatable], but not
1937 * [infiniteRepeatable]. By default, [transitionSpec] uses a [spring] animation for all transition
1938 * destinations.
1939 *
1940 * @sample androidx.compose.animation.core.samples.AnimateFloatSample
1941 *
1942 * [label] is used to differentiate from other animations in the same transition in Android Studio.
1943 *
1944 * @return A [State] object, the value of which is updated by animation
1945 * @see rememberTransition
1946 * @see updateTransition
1947 * @see Transition.animateValue
1948 * @see androidx.compose.animation.animateColor
1949 */
1950 @Composable
animateFloatnull1951 public inline fun <S> Transition<S>.animateFloat(
1952 noinline transitionSpec: @Composable Transition.Segment<S>.() -> FiniteAnimationSpec<Float> = {
1953 spring()
1954 },
1955 label: String = "FloatAnimation",
1956 targetValueByState: @Composable (state: S) -> Float
1957 ): State<Float> = animateValue(Float.VectorConverter, transitionSpec, label, targetValueByState)
1958
1959 /**
1960 * Creates a [Dp] animation as a part of the given [Transition]. This means the states of this
1961 * animation will be managed by the [Transition].
1962 *
1963 * [targetValueByState] is used as a mapping from a target state to the target value of this
1964 * animation. [Transition] will be using this mapping to determine what value to target this
1965 * animation towards. __Note__ that [targetValueByState] is a composable function. This means the
1966 * mapping function could access states, CompositionLocals, themes, etc. If the targetValue changes
1967 * outside of a [Transition] run (i.e. when the [Transition] already reached its targetState), the
1968 * [Transition] will start running again to ensure this animation reaches its new target smoothly.
1969 *
1970 * An optional [transitionSpec] can be provided to specify (potentially different) animation for
1971 * each pair of initialState and targetState. [FiniteAnimationSpec] includes any non-infinite
1972 * animation, such as [tween], [spring], [keyframes] and even [repeatable], but not
1973 * [infiniteRepeatable]. By default, [transitionSpec] uses a [spring] animation for all transition
1974 * destinations.
1975 *
1976 * [label] is used to differentiate from other animations in the same transition in Android Studio.
1977 *
1978 * @return A [State] object, the value of which is updated by animation
1979 */
1980 @Composable
animateDpnull1981 public inline fun <S> Transition<S>.animateDp(
1982 noinline transitionSpec: @Composable Transition.Segment<S>.() -> FiniteAnimationSpec<Dp> = {
1983 spring(visibilityThreshold = Dp.VisibilityThreshold)
1984 },
1985 label: String = "DpAnimation",
1986 targetValueByState: @Composable (state: S) -> Dp
1987 ): State<Dp> = animateValue(Dp.VectorConverter, transitionSpec, label, targetValueByState)
1988
1989 /**
1990 * Creates an [Offset] animation as a part of the given [Transition]. This means the states of this
1991 * animation will be managed by the [Transition].
1992 *
1993 * [targetValueByState] is used as a mapping from a target state to the target value of this
1994 * animation. [Transition] will be using this mapping to determine what value to target this
1995 * animation towards. __Note__ that [targetValueByState] is a composable function. This means the
1996 * mapping function could access states, CompositionLocals, themes, etc. If the targetValue changes
1997 * outside of a [Transition] run (i.e. when the [Transition] already reached its targetState), the
1998 * [Transition] will start running again to ensure this animation reaches its new target smoothly.
1999 *
2000 * An optional [transitionSpec] can be provided to specify (potentially different) animation for
2001 * each pair of initialState and targetState. [FiniteAnimationSpec] includes any non-infinite
2002 * animation, such as [tween], [spring], [keyframes] and even [repeatable], but not
2003 * [infiniteRepeatable]. By default, [transitionSpec] uses a [spring] animation for all transition
2004 * destinations.
2005 *
2006 * [label] is used to differentiate from other animations in the same transition in Android Studio.
2007 *
2008 * @return A [State] object, the value of which is updated by animation
2009 */
2010 @Composable
animateOffsetnull2011 public inline fun <S> Transition<S>.animateOffset(
2012 noinline transitionSpec: @Composable Transition.Segment<S>.() -> FiniteAnimationSpec<Offset> = {
2013 spring(visibilityThreshold = Offset.VisibilityThreshold)
2014 },
2015 label: String = "OffsetAnimation",
2016 targetValueByState: @Composable (state: S) -> Offset
2017 ): State<Offset> = animateValue(Offset.VectorConverter, transitionSpec, label, targetValueByState)
2018
2019 /**
2020 * Creates a [Size] animation as a part of the given [Transition]. This means the states of this
2021 * animation will be managed by the [Transition].
2022 *
2023 * [targetValueByState] is used as a mapping from a target state to the target value of this
2024 * animation. [Transition] will be using this mapping to determine what value to target this
2025 * animation towards. __Note__ that [targetValueByState] is a composable function. This means the
2026 * mapping function could access states, CompositionLocals, themes, etc. If the targetValue changes
2027 * outside of a [Transition] run (i.e. when the [Transition] already reached its targetState), the
2028 * [Transition] will start running again to ensure this animation reaches its new target smoothly.
2029 *
2030 * An optional [transitionSpec] can be provided to specify (potentially different) animation for
2031 * each pair of initialState and targetState. [FiniteAnimationSpec] includes any non-infinite
2032 * animation, such as [tween], [spring], [keyframes] and even [repeatable], but not
2033 * [infiniteRepeatable]. By default, [transitionSpec] uses a [spring] animation for all transition
2034 * destinations.
2035 *
2036 * [label] is used to differentiate from other animations in the same transition in Android Studio.
2037 *
2038 * @return A [State] object, the value of which is updated by animation
2039 */
2040 @Composable
animateSizenull2041 public inline fun <S> Transition<S>.animateSize(
2042 noinline transitionSpec: @Composable Transition.Segment<S>.() -> FiniteAnimationSpec<Size> = {
2043 spring(visibilityThreshold = Size.VisibilityThreshold)
2044 },
2045 label: String = "SizeAnimation",
2046 targetValueByState: @Composable (state: S) -> Size
2047 ): State<Size> = animateValue(Size.VectorConverter, transitionSpec, label, targetValueByState)
2048
2049 /**
2050 * Creates a [IntOffset] animation as a part of the given [Transition]. This means the states of
2051 * this animation will be managed by the [Transition].
2052 *
2053 * [targetValueByState] is used as a mapping from a target state to the target value of this
2054 * animation. [Transition] will be using this mapping to determine what value to target this
2055 * animation towards. __Note__ that [targetValueByState] is a composable function. This means the
2056 * mapping function could access states, CompositionLocals, themes, etc. If the targetValue changes
2057 * outside of a [Transition] run (i.e. when the [Transition] already reached its targetState), the
2058 * [Transition] will start running again to ensure this animation reaches its new target smoothly.
2059 *
2060 * An optional [transitionSpec] can be provided to specify (potentially different) animation for
2061 * each pair of initialState and targetState. [FiniteAnimationSpec] includes any non-infinite
2062 * animation, such as [tween], [spring], [keyframes] and even [repeatable], but not
2063 * [infiniteRepeatable]. By default, [transitionSpec] uses a [spring] animation for all transition
2064 * destinations.
2065 *
2066 * [label] is used to differentiate from other animations in the same transition in Android Studio.
2067 *
2068 * @return A [State] object, the value of which is updated by animation
2069 */
2070 @Composable
animateIntOffsetnull2071 public inline fun <S> Transition<S>.animateIntOffset(
2072 noinline transitionSpec:
2073 @Composable
2074 Transition.Segment<S>.() -> FiniteAnimationSpec<IntOffset> =
2075 {
2076 spring(visibilityThreshold = IntOffset(1, 1))
2077 },
2078 label: String = "IntOffsetAnimation",
2079 targetValueByState: @Composable (state: S) -> IntOffset
2080 ): State<IntOffset> =
2081 animateValue(IntOffset.VectorConverter, transitionSpec, label, targetValueByState)
2082
2083 /**
2084 * Creates a [Int] animation as a part of the given [Transition]. This means the states of this
2085 * animation will be managed by the [Transition].
2086 *
2087 * [targetValueByState] is used as a mapping from a target state to the target value of this
2088 * animation. [Transition] will be using this mapping to determine what value to target this
2089 * animation towards. __Note__ that [targetValueByState] is a composable function. This means the
2090 * mapping function could access states, CompositionLocals, themes, etc. If the targetValue changes
2091 * outside of a [Transition] run (i.e. when the [Transition] already reached its targetState), the
2092 * [Transition] will start running again to ensure this animation reaches its new target smoothly.
2093 *
2094 * An optional [transitionSpec] can be provided to specify (potentially different) animation for
2095 * each pair of initialState and targetState. [FiniteAnimationSpec] includes any non-infinite
2096 * animation, such as [tween], [spring], [keyframes] and even [repeatable], but not
2097 * [infiniteRepeatable]. By default, [transitionSpec] uses a [spring] animation for all transition
2098 * destinations.
2099 *
2100 * [label] is used to differentiate from other animations in the same transition in Android Studio.
2101 *
2102 * @return A [State] object, the value of which is updated by animation
2103 */
2104 @Composable
animateIntnull2105 public inline fun <S> Transition<S>.animateInt(
2106 noinline transitionSpec: @Composable Transition.Segment<S>.() -> FiniteAnimationSpec<Int> = {
2107 spring(visibilityThreshold = 1)
2108 },
2109 label: String = "IntAnimation",
2110 targetValueByState: @Composable (state: S) -> Int
2111 ): State<Int> = animateValue(Int.VectorConverter, transitionSpec, label, targetValueByState)
2112
2113 /**
2114 * Creates a [IntSize] animation as a part of the given [Transition]. This means the states of this
2115 * animation will be managed by the [Transition].
2116 *
2117 * [targetValueByState] is used as a mapping from a target state to the target value of this
2118 * animation. [Transition] will be using this mapping to determine what value to target this
2119 * animation towards. __Note__ that [targetValueByState] is a composable function. This means the
2120 * mapping function could access states, CompositionLocals, themes, etc. If the targetValue changes
2121 * outside of a [Transition] run (i.e. when the [Transition] already reached its targetState), the
2122 * [Transition] will start running again to ensure this animation reaches its new target smoothly.
2123 *
2124 * An optional [transitionSpec] can be provided to specify (potentially different) animation for
2125 * each pair of initialState and targetState. [FiniteAnimationSpec] includes any non-infinite
2126 * animation, such as [tween], [spring], [keyframes] and even [repeatable], but not
2127 * [infiniteRepeatable]. By default, [transitionSpec] uses a [spring] animation for all transition
2128 * destinations.
2129 *
2130 * [label] is used to differentiate from other animations in the same transition in Android Studio.
2131 *
2132 * @return A [State] object, the value of which is updated by animation
2133 */
2134 @Composable
animateIntSizenull2135 public inline fun <S> Transition<S>.animateIntSize(
2136 noinline transitionSpec: @Composable Transition.Segment<S>.() -> FiniteAnimationSpec<IntSize> =
2137 {
2138 spring(visibilityThreshold = IntSize(1, 1))
2139 },
2140 label: String = "IntSizeAnimation",
2141 targetValueByState: @Composable (state: S) -> IntSize
2142 ): State<IntSize> = animateValue(IntSize.VectorConverter, transitionSpec, label, targetValueByState)
2143
2144 /**
2145 * Creates a [Rect] animation as a part of the given [Transition]. This means the states of this
2146 * animation will be managed by the [Transition].
2147 *
2148 * [targetValueByState] is used as a mapping from a target state to the target value of this
2149 * animation. [Transition] will be using this mapping to determine what value to target this
2150 * animation towards. __Note__ that [targetValueByState] is a composable function. This means the
2151 * mapping function could access states, CompositionLocals, themes, etc. If the targetValue changes
2152 * outside of a [Transition] run (i.e. when the [Transition] already reached its targetState), the
2153 * [Transition] will start running again to ensure this animation reaches its new target smoothly.
2154 *
2155 * An optional [transitionSpec] can be provided to specify (potentially different) animation for
2156 * each pair of initialState and targetState. [FiniteAnimationSpec] includes any non-infinite
2157 * animation, such as [tween], [spring], [keyframes] and even [repeatable], but not
2158 * [infiniteRepeatable]. By default, [transitionSpec] uses a [spring] animation for all transition
2159 * destinations.
2160 *
2161 * [label] is used to differentiate from other animations in the same transition in Android Studio.
2162 *
2163 * @return A [State] object, the value of which is updated by animation
2164 */
2165 @Composable
animateRectnull2166 public inline fun <S> Transition<S>.animateRect(
2167 noinline transitionSpec: @Composable Transition.Segment<S>.() -> FiniteAnimationSpec<Rect> = {
2168 spring(visibilityThreshold = Rect.VisibilityThreshold)
2169 },
2170 label: String = "RectAnimation",
2171 targetValueByState: @Composable (state: S) -> Rect
2172 ): State<Rect> = animateValue(Rect.VectorConverter, transitionSpec, label, targetValueByState)
2173