Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit f1d48b54 authored by Treehugger Robot's avatar Treehugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Derive currentOverscrollSpec to avoid unnecessary layouts/placements" into main

parents 65dc85ec eb1be208
Loading
Loading
Loading
Loading
+22 −10
Original line number Diff line number Diff line
@@ -21,6 +21,7 @@ import androidx.compose.animation.core.AnimationVector1D
import androidx.compose.animation.core.spring
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.runtime.Stable
import androidx.compose.runtime.State
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import com.android.compose.animation.scene.ContentKey
@@ -249,18 +250,29 @@ sealed interface TransitionState {
        private var fromOverscrollSpec: OverscrollSpecImpl? = null
        private var toOverscrollSpec: OverscrollSpecImpl? = null

        /** The current [OverscrollSpecImpl], if this transition is currently overscrolling. */
        internal val currentOverscrollSpec: OverscrollSpecImpl?
            get() {
                if (this !is HasOverscrollProperties) return null
        /**
         * The current [OverscrollSpecImpl], if this transition is currently overscrolling.
         *
         * Note: This is backed by a State<OverscrollSpecImpl?> because the overscroll spec is
         * derived from progress, and we don't want readers of currentOverscrollSpec to recompose
         * every time progress is changed.
         */
        private val _currentOverscrollSpec: State<OverscrollSpecImpl?>? =
            if (this !is HasOverscrollProperties) {
                null
            } else {
                derivedStateOf {
                    val progress = progress
                    val bouncingContent = bouncingContent
                return when {
                    when {
                        progress < 0f || bouncingContent == fromContent -> fromOverscrollSpec
                        progress > 1f || bouncingContent == toContent -> toOverscrollSpec
                        else -> null
                    }
                }
            }
        internal val currentOverscrollSpec: OverscrollSpecImpl?
            get() = _currentOverscrollSpec?.value

        /**
         * An animatable that animates from 1f to 0f. This will be used to nicely animate the sudden
+60 −0
Original line number Diff line number Diff line
@@ -46,6 +46,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.layout.approachLayout
import androidx.compose.ui.layout.layout
import androidx.compose.ui.platform.LocalViewConfiguration
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.test.assertIsDisplayed
@@ -70,11 +71,13 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.compose.animation.scene.TestScenes.SceneA
import com.android.compose.animation.scene.TestScenes.SceneB
import com.android.compose.animation.scene.TestScenes.SceneC
import com.android.compose.animation.scene.content.state.TransitionState
import com.android.compose.animation.scene.subjects.assertThat
import com.android.compose.test.assertSizeIsEqualTo
import com.android.compose.test.setContentAndCreateMainScope
import com.android.compose.test.transition
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertWithMessage
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import org.junit.Assert.assertThrows
@@ -2581,4 +2584,61 @@ class ElementTest {
            }
        }
    }

    @Test
    fun staticSharedElementShouldNotRemeasureOrReplaceDuringOverscrollableTransition() {
        val size = 30.dp
        var numberOfMeasurements = 0
        var numberOfPlacements = 0

        // Foo is a simple element that does not move or resize during the transition.
        @Composable
        fun SceneScope.Foo(modifier: Modifier = Modifier) {
            Box(
                modifier
                    .element(TestElements.Foo)
                    .layout { measurable, constraints ->
                        numberOfMeasurements++
                        measurable.measure(constraints).run {
                            numberOfPlacements++
                            layout(width, height) { place(0, 0) }
                        }
                    }
                    .size(size)
            )
        }

        val state = rule.runOnUiThread { MutableSceneTransitionLayoutState(SceneA) }
        val scope =
            rule.setContentAndCreateMainScope {
                SceneTransitionLayout(state) {
                    scene(SceneA) { Box(Modifier.fillMaxSize()) { Foo() } }
                    scene(SceneB) { Box(Modifier.fillMaxSize()) { Foo() } }
                }
            }

        // Start an overscrollable transition driven by progress.
        var progress by mutableFloatStateOf(0f)
        val transition = transition(from = SceneA, to = SceneB, progress = { progress })
        assertThat(transition).isInstanceOf(TransitionState.HasOverscrollProperties::class.java)
        scope.launch { state.startTransition(transition) }

        // Reset the counters after the first animation frame.
        rule.waitForIdle()
        numberOfMeasurements = 0
        numberOfPlacements = 0

        // Change the progress a bunch of times.
        val nFrames = 20
        repeat(nFrames) { i ->
            progress = i / nFrames.toFloat()
            rule.waitForIdle()

            // We shouldn't have remeasured or replaced Foo.
            assertWithMessage("Frame $i didn't remeasure Foo")
                .that(numberOfMeasurements)
                .isEqualTo(0)
            assertWithMessage("Frame $i didn't replace Foo").that(numberOfPlacements).isEqualTo(0)
        }
    }
}