1 /*
2 * Copyright 2023 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 com.android.compose.animation.scene
18
19 import androidx.compose.runtime.Composable
20 import androidx.compose.runtime.DisposableEffect
21 import androidx.compose.runtime.DisposableEffectResult
22 import androidx.compose.runtime.DisposableEffectScope
23 import androidx.compose.runtime.State
24 import androidx.compose.runtime.derivedStateOf
25 import androidx.compose.runtime.remember
26 import androidx.compose.ui.graphics.Color
27 import androidx.compose.ui.graphics.lerp
28 import androidx.compose.ui.unit.Dp
29 import androidx.compose.ui.unit.lerp
30 import com.android.compose.ui.util.lerp
31
32 /**
33 * Animate a shared Int value.
34 *
35 * @see SceneScope.animateSharedValueAsState
36 */
37 @Composable
animateSharedIntAsStatenull38 fun SceneScope.animateSharedIntAsState(
39 value: Int,
40 key: ValueKey,
41 element: ElementKey,
42 canOverflow: Boolean = true,
43 ): State<Int> {
44 return animateSharedValueAsState(value, key, element, ::lerp, canOverflow)
45 }
46
47 /**
48 * Animate a shared Float value.
49 *
50 * @see SceneScope.animateSharedValueAsState
51 */
52 @Composable
animateSharedFloatAsStatenull53 fun SceneScope.animateSharedFloatAsState(
54 value: Float,
55 key: ValueKey,
56 element: ElementKey,
57 canOverflow: Boolean = true,
58 ): State<Float> {
59 return animateSharedValueAsState(value, key, element, ::lerp, canOverflow)
60 }
61
62 /**
63 * Animate a shared Dp value.
64 *
65 * @see SceneScope.animateSharedValueAsState
66 */
67 @Composable
SceneScopenull68 fun SceneScope.animateSharedDpAsState(
69 value: Dp,
70 key: ValueKey,
71 element: ElementKey,
72 canOverflow: Boolean = true,
73 ): State<Dp> {
74 return animateSharedValueAsState(value, key, element, ::lerp, canOverflow)
75 }
76
77 /**
78 * Animate a shared Color value.
79 *
80 * @see SceneScope.animateSharedValueAsState
81 */
82 @Composable
animateSharedColorAsStatenull83 fun SceneScope.animateSharedColorAsState(
84 value: Color,
85 key: ValueKey,
86 element: ElementKey,
87 ): State<Color> {
88 return animateSharedValueAsState(value, key, element, ::lerp, canOverflow = false)
89 }
90
91 @Composable
animateSharedValueAsStatenull92 internal fun <T> animateSharedValueAsState(
93 layoutImpl: SceneTransitionLayoutImpl,
94 scene: Scene,
95 element: Element,
96 key: ValueKey,
97 value: T,
98 lerp: (T, T, Float) -> T,
99 canOverflow: Boolean,
100 ): State<T> {
101 val sharedValue = remember(key) { Element.SharedValue(key, value) }
102 if (value != sharedValue.value) {
103 sharedValue.value = value
104 }
105
106 DisposableEffect(element, scene, sharedValue) {
107 addSharedValueToElement(element, scene, sharedValue)
108 }
109
110 return remember(layoutImpl, element, sharedValue, lerp, canOverflow) {
111 derivedStateOf { computeValue(layoutImpl, element, sharedValue, lerp, canOverflow) }
112 }
113 }
114
addSharedValueToElementnull115 private fun <T> DisposableEffectScope.addSharedValueToElement(
116 element: Element,
117 scene: Scene,
118 sharedValue: Element.SharedValue<T>,
119 ): DisposableEffectResult {
120 val sceneValues =
121 element.sceneValues[scene.key] ?: error("Element $element is not present in $scene")
122 val sharedValues = sceneValues.sharedValues
123
124 sharedValues[sharedValue.key] = sharedValue
125 return onDispose { sharedValues.remove(sharedValue.key) }
126 }
127
computeValuenull128 private fun <T> computeValue(
129 layoutImpl: SceneTransitionLayoutImpl,
130 element: Element,
131 sharedValue: Element.SharedValue<T>,
132 lerp: (T, T, Float) -> T,
133 canOverflow: Boolean,
134 ): T {
135 val state = layoutImpl.state.transitionState
136 if (
137 state !is TransitionState.Transition ||
138 state.fromScene == state.toScene ||
139 !layoutImpl.isTransitionReady(state)
140 ) {
141 return sharedValue.value
142 }
143
144 fun sceneValue(scene: SceneKey): Element.SharedValue<T>? {
145 val sceneValues = element.sceneValues[scene] ?: return null
146 val value = sceneValues.sharedValues[sharedValue.key] ?: return null
147 return value as Element.SharedValue<T>
148 }
149
150 val fromValue = sceneValue(state.fromScene)
151 val toValue = sceneValue(state.toScene)
152 return if (fromValue != null && toValue != null) {
153 val progress = if (canOverflow) state.progress else state.progress.coerceIn(0f, 1f)
154 lerp(fromValue.value, toValue.value, progress)
155 } else if (fromValue != null) {
156 fromValue.value
157 } else if (toValue != null) {
158 toValue.value
159 } else {
160 sharedValue.value
161 }
162 }
163