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 38 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 53 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 68 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 83 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 92 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 115 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 128 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