Loading packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt +2 −2 Original line number Diff line number Diff line Loading @@ -692,7 +692,7 @@ internal class ElementNode( } /** The [TransitionState] that we should consider for [element]. */ private fun elementState( internal fun elementState( layoutImpl: SceneTransitionLayoutImpl, element: Element, transitionStates: List<List<TransitionState>>, Loading Loading @@ -1127,7 +1127,7 @@ private fun isElementOpaque( * [isElementOpaque] is checked during placement and we don't want to read the transition progress * in that phase. */ private fun elementAlpha( internal fun elementAlpha( layoutImpl: SceneTransitionLayoutImpl, element: Element, transition: TransitionState.Transition?, Loading packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt +9 −0 Original line number Diff line number Diff line Loading @@ -312,6 +312,15 @@ interface BaseContentScope : ElementStateScope { fun Modifier.disableSwipesWhenScrolling( bounds: NestedScrollableBound = NestedScrollableBound.Any ): Modifier /** * Return the alpha of [this] element in this content, given the current transition state and * transition transformations (e.g. fade). * * Important: This should *not* be read during composition and should instead be read during * layout, drawing or in a LaunchedEffect. */ fun ElementKey.currentAlpha(): Float? } @Stable Loading packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt +14 −0 Original line number Diff line number Diff line Loading @@ -76,8 +76,12 @@ import com.android.compose.animation.scene.UserAction import com.android.compose.animation.scene.UserActionResult import com.android.compose.animation.scene.ValueKey import com.android.compose.animation.scene.animateSharedValueAsState import com.android.compose.animation.scene.content.state.TransitionState import com.android.compose.animation.scene.effect.GestureEffect import com.android.compose.animation.scene.element import com.android.compose.animation.scene.elementAlpha import com.android.compose.animation.scene.elementState import com.android.compose.animation.scene.getAllNestedTransitionStates import com.android.compose.animation.scene.modifiers.noResizeDuringTransitions import com.android.compose.gesture.NestedScrollControlState import com.android.compose.gesture.NestedScrollableBound Loading Loading @@ -422,6 +426,16 @@ internal class ContentScopeImpl( it.fromContent == content || it.toContent == content } } override fun ElementKey.currentAlpha(): Float? { val element = layoutImpl.elements[this] ?: return null val stateInContent = element.stateByContent[contentKey] ?: return null val elementState = elementState(layoutImpl, element, getAllNestedTransitionStates(layoutImpl)) ?: return null val transition = elementState as? TransitionState.Transition return elementAlpha(layoutImpl, element, transition, stateInContent) } } /** A [LifecycleOwner] that follows its [parentLifecycle] but is capped at [maxLifecycleState]. */ Loading packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ContentTest.kt +53 −0 Original line number Diff line number Diff line Loading @@ -24,11 +24,13 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.SideEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.runtime.snapshotFlow import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.Offset import androidx.compose.ui.platform.LocalViewConfiguration Loading Loading @@ -380,4 +382,55 @@ class ContentTest { assertThat(nestedMiddle.ancestors).containsExactly(outerState) assertThat(nestedInner.ancestors).containsExactly(outerState, middleState).inOrder() } @Test fun currentElementAlpha() { var lastAlpha: Float? = null val state = rule.runOnUiThread { MutableSceneTransitionLayoutStateForTests( SceneA, transitions = transitions { from(SceneA, to = SceneB) { fade(TestElements.Foo) } }, ) } val scope = rule.setContentAndCreateMainScope { SceneTransitionLayout(state) { scene(SceneA) {} scene(SceneB) { LaunchedEffect(Unit) { snapshotFlow { TestElements.Foo.currentAlpha() } .collect { lastAlpha = it } } Box(Modifier.element(TestElements.Foo).fillMaxSize()) } } } assertThat(lastAlpha).isNull() var progress by mutableStateOf(0f) scope.launch { state.startTransition(transition(SceneA, SceneB, progress = { progress })) } rule.waitForIdle() assertThat(lastAlpha).isWithin(0.01f).of(0f) progress = 0.25f rule.waitForIdle() assertThat(lastAlpha).isWithin(0.01f).of(0.25f) progress = 0.5f rule.waitForIdle() assertThat(lastAlpha).isWithin(0.01f).of(0.5f) progress = 0.75f rule.waitForIdle() assertThat(lastAlpha).isWithin(0.01f).of(0.75f) progress = 1f rule.waitForIdle() assertThat(lastAlpha).isWithin(0.01f).of(1f) } } Loading
packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt +2 −2 Original line number Diff line number Diff line Loading @@ -692,7 +692,7 @@ internal class ElementNode( } /** The [TransitionState] that we should consider for [element]. */ private fun elementState( internal fun elementState( layoutImpl: SceneTransitionLayoutImpl, element: Element, transitionStates: List<List<TransitionState>>, Loading Loading @@ -1127,7 +1127,7 @@ private fun isElementOpaque( * [isElementOpaque] is checked during placement and we don't want to read the transition progress * in that phase. */ private fun elementAlpha( internal fun elementAlpha( layoutImpl: SceneTransitionLayoutImpl, element: Element, transition: TransitionState.Transition?, Loading
packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt +9 −0 Original line number Diff line number Diff line Loading @@ -312,6 +312,15 @@ interface BaseContentScope : ElementStateScope { fun Modifier.disableSwipesWhenScrolling( bounds: NestedScrollableBound = NestedScrollableBound.Any ): Modifier /** * Return the alpha of [this] element in this content, given the current transition state and * transition transformations (e.g. fade). * * Important: This should *not* be read during composition and should instead be read during * layout, drawing or in a LaunchedEffect. */ fun ElementKey.currentAlpha(): Float? } @Stable Loading
packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt +14 −0 Original line number Diff line number Diff line Loading @@ -76,8 +76,12 @@ import com.android.compose.animation.scene.UserAction import com.android.compose.animation.scene.UserActionResult import com.android.compose.animation.scene.ValueKey import com.android.compose.animation.scene.animateSharedValueAsState import com.android.compose.animation.scene.content.state.TransitionState import com.android.compose.animation.scene.effect.GestureEffect import com.android.compose.animation.scene.element import com.android.compose.animation.scene.elementAlpha import com.android.compose.animation.scene.elementState import com.android.compose.animation.scene.getAllNestedTransitionStates import com.android.compose.animation.scene.modifiers.noResizeDuringTransitions import com.android.compose.gesture.NestedScrollControlState import com.android.compose.gesture.NestedScrollableBound Loading Loading @@ -422,6 +426,16 @@ internal class ContentScopeImpl( it.fromContent == content || it.toContent == content } } override fun ElementKey.currentAlpha(): Float? { val element = layoutImpl.elements[this] ?: return null val stateInContent = element.stateByContent[contentKey] ?: return null val elementState = elementState(layoutImpl, element, getAllNestedTransitionStates(layoutImpl)) ?: return null val transition = elementState as? TransitionState.Transition return elementAlpha(layoutImpl, element, transition, stateInContent) } } /** A [LifecycleOwner] that follows its [parentLifecycle] but is capped at [maxLifecycleState]. */ Loading
packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ContentTest.kt +53 −0 Original line number Diff line number Diff line Loading @@ -24,11 +24,13 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.SideEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.runtime.snapshotFlow import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.Offset import androidx.compose.ui.platform.LocalViewConfiguration Loading Loading @@ -380,4 +382,55 @@ class ContentTest { assertThat(nestedMiddle.ancestors).containsExactly(outerState) assertThat(nestedInner.ancestors).containsExactly(outerState, middleState).inOrder() } @Test fun currentElementAlpha() { var lastAlpha: Float? = null val state = rule.runOnUiThread { MutableSceneTransitionLayoutStateForTests( SceneA, transitions = transitions { from(SceneA, to = SceneB) { fade(TestElements.Foo) } }, ) } val scope = rule.setContentAndCreateMainScope { SceneTransitionLayout(state) { scene(SceneA) {} scene(SceneB) { LaunchedEffect(Unit) { snapshotFlow { TestElements.Foo.currentAlpha() } .collect { lastAlpha = it } } Box(Modifier.element(TestElements.Foo).fillMaxSize()) } } } assertThat(lastAlpha).isNull() var progress by mutableStateOf(0f) scope.launch { state.startTransition(transition(SceneA, SceneB, progress = { progress })) } rule.waitForIdle() assertThat(lastAlpha).isWithin(0.01f).of(0f) progress = 0.25f rule.waitForIdle() assertThat(lastAlpha).isWithin(0.01f).of(0.25f) progress = 0.5f rule.waitForIdle() assertThat(lastAlpha).isWithin(0.01f).of(0.5f) progress = 0.75f rule.waitForIdle() assertThat(lastAlpha).isWithin(0.01f).of(0.75f) progress = 1f rule.waitForIdle() assertThat(lastAlpha).isWithin(0.01f).of(1f) } }