Loading packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt +2 −2 Original line number Original line Diff line number Diff line Loading @@ -692,7 +692,7 @@ internal class ElementNode( } } /** The [TransitionState] that we should consider for [element]. */ /** The [TransitionState] that we should consider for [element]. */ private fun elementState( internal fun elementState( layoutImpl: SceneTransitionLayoutImpl, layoutImpl: SceneTransitionLayoutImpl, element: Element, element: Element, transitionStates: List<List<TransitionState>>, 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 * [isElementOpaque] is checked during placement and we don't want to read the transition progress * in that phase. * in that phase. */ */ private fun elementAlpha( internal fun elementAlpha( layoutImpl: SceneTransitionLayoutImpl, layoutImpl: SceneTransitionLayoutImpl, element: Element, element: Element, transition: TransitionState.Transition?, transition: TransitionState.Transition?, Loading packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt +9 −0 Original line number Original line Diff line number Diff line Loading @@ -312,6 +312,15 @@ interface BaseContentScope : ElementStateScope { fun Modifier.disableSwipesWhenScrolling( fun Modifier.disableSwipesWhenScrolling( bounds: NestedScrollableBound = NestedScrollableBound.Any bounds: NestedScrollableBound = NestedScrollableBound.Any ): Modifier ): 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 @Stable Loading packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt +14 −0 Original line number Original line 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.UserActionResult import com.android.compose.animation.scene.ValueKey import com.android.compose.animation.scene.ValueKey import com.android.compose.animation.scene.animateSharedValueAsState 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.effect.GestureEffect import com.android.compose.animation.scene.element 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.animation.scene.modifiers.noResizeDuringTransitions import com.android.compose.gesture.NestedScrollControlState import com.android.compose.gesture.NestedScrollControlState import com.android.compose.gesture.NestedScrollableBound import com.android.compose.gesture.NestedScrollableBound Loading Loading @@ -422,6 +426,16 @@ internal class ContentScopeImpl( it.fromContent == content || it.toContent == content 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]. */ /** 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 Original line 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.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.SideEffect import androidx.compose.runtime.SideEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue import androidx.compose.runtime.snapshotFlow import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Offset import androidx.compose.ui.platform.LocalViewConfiguration import androidx.compose.ui.platform.LocalViewConfiguration Loading Loading @@ -380,4 +382,55 @@ class ContentTest { assertThat(nestedMiddle.ancestors).containsExactly(outerState) assertThat(nestedMiddle.ancestors).containsExactly(outerState) assertThat(nestedInner.ancestors).containsExactly(outerState, middleState).inOrder() 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 Original line Diff line number Diff line Loading @@ -692,7 +692,7 @@ internal class ElementNode( } } /** The [TransitionState] that we should consider for [element]. */ /** The [TransitionState] that we should consider for [element]. */ private fun elementState( internal fun elementState( layoutImpl: SceneTransitionLayoutImpl, layoutImpl: SceneTransitionLayoutImpl, element: Element, element: Element, transitionStates: List<List<TransitionState>>, 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 * [isElementOpaque] is checked during placement and we don't want to read the transition progress * in that phase. * in that phase. */ */ private fun elementAlpha( internal fun elementAlpha( layoutImpl: SceneTransitionLayoutImpl, layoutImpl: SceneTransitionLayoutImpl, element: Element, element: Element, transition: TransitionState.Transition?, transition: TransitionState.Transition?, Loading
packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt +9 −0 Original line number Original line Diff line number Diff line Loading @@ -312,6 +312,15 @@ interface BaseContentScope : ElementStateScope { fun Modifier.disableSwipesWhenScrolling( fun Modifier.disableSwipesWhenScrolling( bounds: NestedScrollableBound = NestedScrollableBound.Any bounds: NestedScrollableBound = NestedScrollableBound.Any ): Modifier ): 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 @Stable Loading
packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt +14 −0 Original line number Original line 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.UserActionResult import com.android.compose.animation.scene.ValueKey import com.android.compose.animation.scene.ValueKey import com.android.compose.animation.scene.animateSharedValueAsState 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.effect.GestureEffect import com.android.compose.animation.scene.element 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.animation.scene.modifiers.noResizeDuringTransitions import com.android.compose.gesture.NestedScrollControlState import com.android.compose.gesture.NestedScrollControlState import com.android.compose.gesture.NestedScrollableBound import com.android.compose.gesture.NestedScrollableBound Loading Loading @@ -422,6 +426,16 @@ internal class ContentScopeImpl( it.fromContent == content || it.toContent == content 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]. */ /** 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 Original line 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.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.SideEffect import androidx.compose.runtime.SideEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue import androidx.compose.runtime.snapshotFlow import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Offset import androidx.compose.ui.platform.LocalViewConfiguration import androidx.compose.ui.platform.LocalViewConfiguration Loading Loading @@ -380,4 +382,55 @@ class ContentTest { assertThat(nestedMiddle.ancestors).containsExactly(outerState) assertThat(nestedMiddle.ancestors).containsExactly(outerState) assertThat(nestedInner.ancestors).containsExactly(outerState, middleState).inOrder() 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) } } }