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