1 /*
2  * Copyright 2024 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package androidx.compose.animation.core
18 
19 import androidx.compose.runtime.getValue
20 import androidx.compose.runtime.mutableStateOf
21 import androidx.compose.runtime.setValue
22 import kotlinx.coroutines.CoroutineScope
23 import kotlinx.coroutines.launch
24 
25 @RequiresOptIn(
26     message = "This is an experimental animation API for Transition. It may change in the future."
27 )
28 @Retention(AnnotationRetention.BINARY)
29 public annotation class ExperimentalAnimatableApi
30 
31 /**
32  * [DeferredTargetAnimation] is intended for animations where the target is unknown at the time of
33  * instantiation. Such use cases include, but are not limited to, size or position animations
34  * created during composition or the initialization of a Modifier.Node, yet the target size or
35  * position stays unknown until the later measure and placement phase.
36  *
37  * [DeferredTargetAnimation] offers a declarative [updateTarget] function, which requires a target
38  * to either set up the animation or update the animation, and to read the current value of the
39  * animation.
40  *
41  * @sample androidx.compose.animation.core.samples.DeferredTargetAnimationSample
42  */
43 @ExperimentalAnimatableApi
44 public class DeferredTargetAnimation<T, V : AnimationVector>(
45     private val vectorConverter: TwoWayConverter<T, V>
46 ) {
47     /** Returns the target value from the most recent [updateTarget] call. */
48     public val pendingTarget: T?
49         get() = _pendingTarget
50 
51     private var _pendingTarget: T? by mutableStateOf(null)
52     private val target: T?
53         get() = animatable?.targetValue
54 
55     private var animatable: Animatable<T, V>? = null
56 
57     /**
58      * [updateTarget] sets up an animation, or updates an already running animation, based on the
59      * [target] in the given [coroutineScope]. [pendingTarget] will be updated to track the last
60      * seen [target].
61      *
62      * [updateTarget] will return the current value of the animation after launching the animation
63      * in the given [coroutineScope].
64      *
65      * @return current value of the animation
66      */
updateTargetnull67     public fun updateTarget(
68         target: T,
69         coroutineScope: CoroutineScope,
70         animationSpec: FiniteAnimationSpec<T> = spring()
71     ): T {
72         _pendingTarget = target
73         val anim = animatable ?: Animatable(target, vectorConverter).also { animatable = it }
74         coroutineScope.launch {
75             if (anim.targetValue != _pendingTarget) {
76                 anim.animateTo(target, animationSpec)
77             }
78         }
79         return anim.value
80     }
81 
82     /**
83      * [isIdle] returns true when the animation has finished running and reached its
84      * [pendingTarget], or when the animation has not been set up (i.e. [updateTarget] has never
85      * been called).
86      */
87     public val isIdle: Boolean
88         get() = _pendingTarget == target && animatable?.isRunning != true
89 }
90