diff options
15 files changed, 197 insertions, 75 deletions
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/gesture/NestedDraggable.kt b/packages/SystemUI/compose/core/src/com/android/compose/gesture/NestedDraggable.kt index 2ea9c487c27c..362748ec71b0 100644 --- a/packages/SystemUI/compose/core/src/com/android/compose/gesture/NestedDraggable.kt +++ b/packages/SystemUI/compose/core/src/com/android/compose/gesture/NestedDraggable.kt @@ -112,6 +112,16 @@ interface NestedDraggable { interface Controller { /** + * Whether this controller is ready to drag. [onDrag] will be called only if this returns + * `true`, and any drag event will be ignored until then. + * + * This can for instance be used to wait for the content we are dragging to to be composed + * before actually dragging, reducing perceivable jank at the beginning of a drag. + */ + val isReadyToDrag: Boolean + get() = true + + /** * Whether drags that were started from nested scrolls should be automatically * [stopped][onDragStopped] as soon as they don't consume the entire `delta` passed to * [onDrag]. @@ -274,6 +284,9 @@ private class NestedDraggableNode( /** The pointers currently down, in order of which they were done and mapping to their type. */ private val pointersDown = linkedMapOf<PointerId, PointerType>() + /** Whether the next drag event should be ignored. */ + private var ignoreNextDrag = false + init { delegate(nestedScrollModifierNode(this, nestedScrollDispatcher)) } @@ -426,6 +439,7 @@ private class NestedDraggableNode( velocityTracker: VelocityTracker, ) { velocityTracker.addPointerInputChange(change) + if (shouldIgnoreDrag(controller)) return scrollWithOverscroll(delta.toOffset()) { deltaFromOverscroll -> scrollWithNestedScroll(deltaFromOverscroll) { deltaFromNestedScroll -> @@ -434,6 +448,23 @@ private class NestedDraggableNode( } } + private fun shouldIgnoreDrag(controller: NestedDraggable.Controller): Boolean { + return when { + !controller.isReadyToDrag -> { + // The controller is not ready yet, so we are waiting for an expensive frame to be + // composed. We should ignore this drag and the next one, given that the first delta + // after an expensive frame will be large. + ignoreNextDrag = true + true + } + ignoreNextDrag -> { + ignoreNextDrag = false + true + } + else -> false + } + } + private fun onDragStopped(controller: NestedDraggable.Controller, velocity: Velocity) { // We launch in the scope of the dispatcher so that the fling is not cancelled if this node // is removed right after onDragStopped() is called. @@ -617,6 +648,8 @@ private class NestedDraggableNode( } private fun scrollWithOverscroll(controller: NestedScrollController, offset: Offset): Offset { + if (shouldIgnoreDrag(controller.controller)) return offset + return scrollWithOverscroll(offset) { delta -> val available = delta.toFloat() val consumed = controller.controller.onDrag(available) diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/gesture/NestedDraggableTest.kt b/packages/SystemUI/compose/core/tests/src/com/android/compose/gesture/NestedDraggableTest.kt index b247993de4e4..b31617369cdb 100644 --- a/packages/SystemUI/compose/core/tests/src/com/android/compose/gesture/NestedDraggableTest.kt +++ b/packages/SystemUI/compose/core/tests/src/com/android/compose/gesture/NestedDraggableTest.kt @@ -972,6 +972,36 @@ class NestedDraggableTest(override val orientation: Orientation) : OrientationAw } @Test + fun isReadyToDrag() { + var isReadyToDrag by mutableStateOf(false) + val draggable = TestDraggable(isReadyToDrag = { isReadyToDrag }) + val touchSlop = + rule.setContentWithTouchSlop { + Box(Modifier.fillMaxSize().nestedDraggable(draggable, orientation)) + } + + rule.onRoot().performTouchInput { + down(center) + moveBy((touchSlop + 10f).toOffset()) + } + + assertThat(draggable.onDragStartedCalled).isTrue() + assertThat(draggable.onDragDelta).isEqualTo(0f) + + rule.onRoot().performTouchInput { moveBy(20f.toOffset()) } + assertThat(draggable.onDragDelta).isEqualTo(0f) + + // Flag as ready to drag. We still ignore the next drag after that. + isReadyToDrag = true + rule.onRoot().performTouchInput { moveBy(30f.toOffset()) } + assertThat(draggable.onDragDelta).isEqualTo(0f) + + // Now we drag. + rule.onRoot().performTouchInput { moveBy(40f.toOffset()) } + assertThat(draggable.onDragDelta).isEqualTo(40f) + } + + @Test fun consumeNestedPreScroll() { var consumeNestedPreScroll by mutableStateOf(false) val draggable = TestDraggable(shouldConsumeNestedPreScroll = { consumeNestedPreScroll }) @@ -1060,6 +1090,7 @@ class NestedDraggableTest(override val orientation: Orientation) : OrientationAw }, private val shouldConsumeNestedPostScroll: (Float) -> Boolean = { true }, private val shouldConsumeNestedPreScroll: (Float) -> Boolean = { false }, + private val isReadyToDrag: () -> Boolean = { true }, private val autoStopNestedDrags: Boolean = false, ) : NestedDraggable { var shouldStartDrag = true @@ -1092,6 +1123,9 @@ class NestedDraggableTest(override val orientation: Orientation) : OrientationAw return object : NestedDraggable.Controller { override val autoStopNestedDrags: Boolean = this@TestDraggable.autoStopNestedDrags + override val isReadyToDrag: Boolean + get() = isReadyToDrag() + override fun onDrag(delta: Float): Float { onDragCalled = true onDragDelta += delta diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateContent.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateContent.kt index b04d89d8160f..0b0df06c2015 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateContent.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateContent.kt @@ -20,6 +20,7 @@ import androidx.compose.animation.core.Animatable import androidx.compose.animation.core.AnimationVector1D import androidx.compose.animation.core.SpringSpec import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi +import androidx.compose.runtime.withFrameNanos import com.android.compose.animation.scene.content.state.TransitionState import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job @@ -47,6 +48,11 @@ internal fun CoroutineScope.animateContent( oneOffAnimation.animatable = it } + if (layoutState.deferTransitionProgress) { + // Defer the animation by one frame so that the transition progress is changed only when + // the expensive first composition frame is done. + withFrameNanos {} + } animatable.animateTo(targetProgress, animationSpec, initialVelocity) } diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt index 024ca22069ae..36eafa400090 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt @@ -290,6 +290,15 @@ private class DragControllerImpl( val isDrivingTransition: Boolean get() = layoutState.transitionState == swipeAnimation.contentTransition + override val isReadyToDrag: Boolean + get() { + return !layoutState.deferTransitionProgress || + with(draggableHandler.layoutImpl.elementStateScope) { + swipeAnimation.fromContent.targetSize() != null && + swipeAnimation.toContent.targetSize() != null + } + } + init { check(!isDrivingTransition) { "Multiple controllers with the same SwipeTransition" } } diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt index 4da83c3a6fc9..a8b676d4ee45 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt @@ -462,7 +462,9 @@ internal class SceneTransitionLayoutImpl( // swipes. .swipeToScene(horizontalDraggableHandler) .swipeToScene(verticalDraggableHandler) - .then(LayoutElement(layoutImpl = this)) + .then( + LayoutElement(layoutImpl = this, transitionState = this.state.transitionState) + ) ) { LookaheadScope { if (_lookaheadScope == null) { @@ -623,23 +625,28 @@ internal class SceneTransitionLayoutImpl( @VisibleForTesting internal fun overlaysOrNullForTest(): Map<OverlayKey, Overlay>? = _overlays } -private data class LayoutElement(private val layoutImpl: SceneTransitionLayoutImpl) : - ModifierNodeElement<LayoutNode>() { - override fun create(): LayoutNode = LayoutNode(layoutImpl) +private data class LayoutElement( + private val layoutImpl: SceneTransitionLayoutImpl, + private val transitionState: TransitionState, +) : ModifierNodeElement<LayoutNode>() { + override fun create(): LayoutNode = LayoutNode(layoutImpl, transitionState) override fun update(node: LayoutNode) { node.layoutImpl = layoutImpl + node.transitionState = transitionState } } -private class LayoutNode(var layoutImpl: SceneTransitionLayoutImpl) : - Modifier.Node(), ApproachLayoutModifierNode, LayoutAwareModifierNode { +private class LayoutNode( + var layoutImpl: SceneTransitionLayoutImpl, + var transitionState: TransitionState, +) : Modifier.Node(), ApproachLayoutModifierNode, LayoutAwareModifierNode { override fun onRemeasured(size: IntSize) { layoutImpl.lastSize = size } override fun isMeasurementApproachInProgress(lookaheadSize: IntSize): Boolean { - return layoutImpl.state.isTransitioning() + return transitionState is TransitionState.Transition.ChangeScene } @ExperimentalComposeUiApi @@ -652,8 +659,7 @@ private class LayoutNode(var layoutImpl: SceneTransitionLayoutImpl) : val width: Int val height: Int - val transition = - layoutImpl.state.currentTransition as? TransitionState.Transition.ChangeScene + val transition = transitionState as? TransitionState.Transition.ChangeScene if (transition == null) { width = placeable.width height = placeable.height @@ -662,6 +668,9 @@ private class LayoutNode(var layoutImpl: SceneTransitionLayoutImpl) : val fromSize = layoutImpl.scene(transition.fromScene).targetSize val toSize = layoutImpl.scene(transition.toScene).targetSize + check(fromSize != Element.SizeUnspecified) { "fromSize is unspecified " } + check(toSize != Element.SizeUnspecified) { "toSize is unspecified" } + // Optimization: make sure we don't read state.progress if fromSize == // toSize to avoid running this code every frame when the layout size does // not change. diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt index 56e8c458ad67..4e28dd569f21 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt @@ -234,6 +234,10 @@ sealed interface MutableSceneTransitionLayoutState : SceneTransitionLayoutState * `from` overlay by `to` overlay. * @param stateLinks the [StateLink] connecting this [SceneTransitionLayoutState] to other * [SceneTransitionLayoutState]s. + * @param deferTransitionProgress whether we should wait for the first composition to be done before + * changing the progress of a transition. This can help reduce perceivable jank at the start of a + * transition in case the first composition of a content takes a lot of time and we are going to + * miss that first frame. */ fun MutableSceneTransitionLayoutState( initialScene: SceneKey, @@ -246,6 +250,9 @@ fun MutableSceneTransitionLayoutState( canReplaceOverlay: (from: OverlayKey, to: OverlayKey) -> Boolean = { _, _ -> true }, onTransitionStart: (TransitionState.Transition) -> Unit = {}, onTransitionEnd: (TransitionState.Transition) -> Unit = {}, + + // TODO(b/400688335): Turn on by default and remove this flag before flexiglass is released. + deferTransitionProgress: Boolean = false, ): MutableSceneTransitionLayoutState { return MutableSceneTransitionLayoutStateImpl( initialScene, @@ -258,6 +265,7 @@ fun MutableSceneTransitionLayoutState( canReplaceOverlay, onTransitionStart, onTransitionEnd, + deferTransitionProgress, ) } @@ -272,6 +280,9 @@ fun rememberMutableSceneTransitionLayoutState( canReplaceOverlay: (from: OverlayKey, to: OverlayKey) -> Boolean = { _, _ -> true }, onTransitionStart: (TransitionState.Transition) -> Unit = {}, onTransitionEnd: (TransitionState.Transition) -> Unit = {}, + + // TODO(b/400688335): Turn on by default and remove this flag before flexiglass is released. + deferTransitionProgress: Boolean = false, ): MutableSceneTransitionLayoutState { val motionScheme = MaterialTheme.motionScheme val layoutState = remember { @@ -286,6 +297,7 @@ fun rememberMutableSceneTransitionLayoutState( canReplaceOverlay = canReplaceOverlay, onTransitionStart = onTransitionStart, onTransitionEnd = onTransitionEnd, + deferTransitionProgress = deferTransitionProgress, ) } @@ -298,6 +310,7 @@ fun rememberMutableSceneTransitionLayoutState( layoutState.canReplaceOverlay = canReplaceOverlay layoutState.onTransitionStart = onTransitionStart layoutState.onTransitionEnd = onTransitionEnd + layoutState.deferTransitionProgress = deferTransitionProgress } return layoutState } @@ -317,6 +330,8 @@ internal class MutableSceneTransitionLayoutStateImpl( }, internal var onTransitionStart: (TransitionState.Transition) -> Unit = {}, internal var onTransitionEnd: (TransitionState.Transition) -> Unit = {}, + // TODO(b/400688335): Turn on by default and remove this flag before flexiglass is released. + internal var deferTransitionProgress: Boolean = false, ) : MutableSceneTransitionLayoutState { private val creationThread: Thread = Thread.currentThread() diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/UserActionDistanceScopeImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/UserActionDistanceScopeImpl.kt index 2d2a81542f84..5d4232d8a8b7 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/UserActionDistanceScopeImpl.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/UserActionDistanceScopeImpl.kt @@ -38,7 +38,7 @@ internal class ElementStateScopeImpl(private val layoutImpl: SceneTransitionLayo } override fun ContentKey.targetSize(): IntSize? { - return layoutImpl.content(this).targetSize.takeIf { it != IntSize.Zero } + return layoutImpl.content(this).targetSize.takeIf { it != Element.SizeUnspecified } } } diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt index 90bf92ae1dd0..7492f3737edc 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt @@ -93,9 +93,10 @@ internal sealed class Content( val containerState = ContainerState() // Important: All fields in this class should be backed by State given that contents are updated - // directly during composition, outside of a SideEffect. + // directly during composition, outside of a SideEffect, or are observed during composition, + // layout or drawing. var content by mutableStateOf(content) - var targetSize by mutableStateOf(IntSize.Zero) + var targetSize by mutableStateOf(Element.SizeUnspecified) var userActions by mutableStateOf(actions) var zIndex by mutableFloatStateOf(zIndex) @@ -212,9 +213,17 @@ private class ContentNode( return if (isElevationPossible) delegate(ContainerNode(content.containerState)) else null } + override fun onDetach() { + this.content.targetSize = Element.SizeUnspecified + } + fun update(content: Content, isElevationPossible: Boolean, isInvisible: Boolean) { - if (content != this.content || isElevationPossible != this.isElevationPossible) { + if (content != this.content) { + this.content.targetSize = Element.SizeUnspecified this.content = content + } + + if (content != this.content || isElevationPossible != this.isElevationPossible) { this.isElevationPossible = isElevationPossible containerDelegate?.let { undelegate(it) } diff --git a/packages/SystemUI/compose/scene/tests/goldens/edge_verticalReveal_gesture_dragFullyClose.json b/packages/SystemUI/compose/scene/tests/goldens/edge_verticalReveal_gesture_dragFullyClose.json index 5dbb01338090..770f85969ed1 100644 --- a/packages/SystemUI/compose/scene/tests/goldens/edge_verticalReveal_gesture_dragFullyClose.json +++ b/packages/SystemUI/compose/scene/tests/goldens/edge_verticalReveal_gesture_dragFullyClose.json @@ -76,7 +76,7 @@ }, { "x": 5.2, - "y": 5.6 + "y": 5.2 }, { "x": 5.2, @@ -326,11 +326,11 @@ }, { "width": 150, - "height": 293.2 + "height": 292.8 }, { "width": 150, - "height": 293.2 + "height": 292.8 }, { "width": 150, diff --git a/packages/SystemUI/compose/scene/tests/goldens/edge_verticalReveal_gesture_dragHalfClose.json b/packages/SystemUI/compose/scene/tests/goldens/edge_verticalReveal_gesture_dragHalfClose.json index 1543d186ea03..6b7de56c1da6 100644 --- a/packages/SystemUI/compose/scene/tests/goldens/edge_verticalReveal_gesture_dragHalfClose.json +++ b/packages/SystemUI/compose/scene/tests/goldens/edge_verticalReveal_gesture_dragHalfClose.json @@ -81,8 +81,8 @@ "y": 5.2 }, { - "x": 5.6, - "y": 6.8 + "x": 5.2, + "y": 5.2 }, { "x": 5.2, @@ -344,7 +344,7 @@ }, { "width": 150, - "height": 294 + "height": 292.4 }, { "width": 150, diff --git a/packages/SystemUI/compose/scene/tests/goldens/edge_verticalReveal_gesture_flingClose.json b/packages/SystemUI/compose/scene/tests/goldens/edge_verticalReveal_gesture_flingClose.json index 115483cf4013..015df8fd02fb 100644 --- a/packages/SystemUI/compose/scene/tests/goldens/edge_verticalReveal_gesture_flingClose.json +++ b/packages/SystemUI/compose/scene/tests/goldens/edge_verticalReveal_gesture_flingClose.json @@ -84,8 +84,8 @@ "y": 5.2 }, { - "x": 5.6, - "y": 6.4 + "x": 5.2, + "y": 5.2 }, { "x": 5.2, @@ -259,7 +259,7 @@ }, { "width": 150, - "height": 290 + "height": 288.8 }, { "width": 150, @@ -279,34 +279,34 @@ }, { "width": 150, - "height": 223.6 + "height": 224 }, { "width": 150, - "height": 182.8 + "height": 183.2 }, { "width": 150, - "height": 141.2 + "height": 141.6 }, { "width": 150, - "height": 104 + "height": 104.4 }, { - "width": 147.6, - "height": 74 + "width": 148, + "height": 74.4 }, { "width": 139.6, - "height": 50.8 + "height": 51.2 }, { "width": 133.6, - "height": 32 + "height": 32.4 }, { - "width": 129.2, + "width": 129.6, "height": 15.6 }, { @@ -409,19 +409,19 @@ 1, 1, 1, - 0.99479187, - 0.8575029, - 0.65572864, - 0.4691311, - 0.3215357, - 0.21380007, - 0.13896108, - 0.0887118, - 0.05580789, - 0.03467691, - 0.021318138, - 0.0129826665, - 0.007839739, + 0.99532944, + 0.8594856, + 0.65783304, + 0.47089338, + 0.32286334, + 0.21474117, + 0.139602, + 0.089136004, + 0.056082606, + 0.03485179, + 0.02142787, + 0.013050735, + 0.007881463, 0, 0, 0, diff --git a/packages/SystemUI/compose/scene/tests/goldens/floating_verticalReveal_gesture_dragFullyClose.json b/packages/SystemUI/compose/scene/tests/goldens/floating_verticalReveal_gesture_dragFullyClose.json index f44d4cd7c14e..5ac06217d161 100644 --- a/packages/SystemUI/compose/scene/tests/goldens/floating_verticalReveal_gesture_dragFullyClose.json +++ b/packages/SystemUI/compose/scene/tests/goldens/floating_verticalReveal_gesture_dragFullyClose.json @@ -72,7 +72,7 @@ }, { "x": 5.2, - "y": 5.6 + "y": 5.2 }, { "x": 5.2, @@ -306,11 +306,11 @@ }, { "width": 150, - "height": 293.2 + "height": 292.8 }, { "width": 150, - "height": 293.2 + "height": 292.8 }, { "width": 150, diff --git a/packages/SystemUI/compose/scene/tests/goldens/floating_verticalReveal_gesture_dragHalfClose.json b/packages/SystemUI/compose/scene/tests/goldens/floating_verticalReveal_gesture_dragHalfClose.json index 9b68c71a7a34..1cae67b3eba7 100644 --- a/packages/SystemUI/compose/scene/tests/goldens/floating_verticalReveal_gesture_dragHalfClose.json +++ b/packages/SystemUI/compose/scene/tests/goldens/floating_verticalReveal_gesture_dragHalfClose.json @@ -79,8 +79,8 @@ "y": 5.2 }, { - "x": 5.6, - "y": 6.8 + "x": 5.2, + "y": 5.2 }, { "x": 5.2, @@ -334,7 +334,7 @@ }, { "width": 150, - "height": 294 + "height": 292.4 }, { "width": 150, diff --git a/packages/SystemUI/compose/scene/tests/goldens/floating_verticalReveal_gesture_flingClose.json b/packages/SystemUI/compose/scene/tests/goldens/floating_verticalReveal_gesture_flingClose.json index 86805bd6ff29..ca87bc28c45d 100644 --- a/packages/SystemUI/compose/scene/tests/goldens/floating_verticalReveal_gesture_flingClose.json +++ b/packages/SystemUI/compose/scene/tests/goldens/floating_verticalReveal_gesture_flingClose.json @@ -83,8 +83,8 @@ "y": 5.2 }, { - "x": 5.6, - "y": 6.4 + "x": 5.2, + "y": 5.2 }, { "x": 5.2, @@ -254,7 +254,7 @@ }, { "width": 150, - "height": 290 + "height": 288.8 }, { "width": 150, @@ -274,27 +274,27 @@ }, { "width": 150, - "height": 223.6 + "height": 224 }, { "width": 150, - "height": 182.8 + "height": 183.2 }, { "width": 150, - "height": 141.2 + "height": 141.6 }, { "width": 150, - "height": 104 + "height": 104.4 }, { "width": 150, - "height": 72 + "height": 72.4 }, { "width": 150, - "height": 46 + "height": 46.4 }, { "width": 150, @@ -400,19 +400,19 @@ 1, 1, 1, - 0.99479187, - 0.8575029, - 0.65572864, - 0.4691311, - 0.3215357, - 0.21380007, - 0.13896108, - 0.0887118, - 0.05580789, - 0.03467691, - 0.021318138, - 0.0129826665, - 0.007839739, + 0.99532944, + 0.8594856, + 0.65783304, + 0.47089338, + 0.32286334, + 0.21474117, + 0.139602, + 0.089136004, + 0.056082606, + 0.03485179, + 0.02142787, + 0.013050735, + 0.007881463, 0, 0, 0, diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/testing/ElementStateAccessTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/testing/ElementStateAccessTest.kt index e4a87ba3a26b..7d9a32b3948a 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/testing/ElementStateAccessTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/testing/ElementStateAccessTest.kt @@ -58,17 +58,24 @@ class ElementStateAccessTest { assertThat(semanticNode.lastScaleForTesting).isEqualTo(Scale(1f, 1f)) } + at(16) { + val semanticNode = onElement(TestElements.Foo).fetchSemanticsNode() + assertThat(semanticNode.lastAlphaForTesting).isEqualTo(0.75f) + assertThat(semanticNode.lastScaleForTesting).isEqualTo(Scale(0.75f, 0.75f)) + } + at(32) { val semanticNode = onElement(TestElements.Foo).fetchSemanticsNode() assertThat(semanticNode.lastAlphaForTesting).isEqualTo(0.5f) assertThat(semanticNode.lastScaleForTesting).isEqualTo(Scale(0.5f, 0.5f)) } - at(64) { + at(48) { val semanticNode = onElement(TestElements.Foo).fetchSemanticsNode() - assertThat(semanticNode.lastAlphaForTesting).isEqualTo(0f) - assertThat(semanticNode.lastScaleForTesting).isEqualTo(Scale(0f, 0f)) + assertThat(semanticNode.lastAlphaForTesting).isEqualTo(0.25f) + assertThat(semanticNode.lastScaleForTesting).isEqualTo(Scale(0.25f, 0.25f)) } + after { onElement(TestElements.Foo).assertDoesNotExist() } } } |