summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Jordan Demeulenaere <jdemeulenaere@google.com> 2024-01-29 09:09:47 +0000
committer Android (Google) Code Review <android-gerrit@google.com> 2024-01-29 09:09:47 +0000
commit3c521f2e29ef950cee9e51f569293fc25db4d948 (patch)
treec2e72e1a6d3141b918ebbd83aaac98be23f2ec9f
parent0fd55d7e5658311bf0b92779ae6215b3da812c0b (diff)
parent18ecc210b772b0b8037f7ee2e8703c1364073a78 (diff)
Merge "Make it possible to change the swipe animation spec" into main
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt36
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt33
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt19
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt7
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt35
5 files changed, 111 insertions, 19 deletions
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt
index 8305428aa9c8..58c3be244725 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt
@@ -20,8 +20,7 @@ package com.android.compose.animation.scene
import android.util.Log
import androidx.compose.animation.core.Animatable
-import androidx.compose.animation.core.Spring
-import androidx.compose.animation.core.spring
+import androidx.compose.animation.core.SpringSpec
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableFloatStateOf
@@ -54,7 +53,20 @@ internal class SceneGestureHandler(
}
private fun updateTransition(newTransition: SwipeTransition, force: Boolean = false) {
- if (isDrivingTransition || force) layoutState.startTransition(newTransition)
+ if (isDrivingTransition || force) {
+ layoutState.startTransition(newTransition)
+
+ // Initialize SwipeTransition.swipeSpec. Note that this must be called right after
+ // layoutState.startTransition() is called, because it computes the
+ // layoutState.transformationSpec().
+ newTransition.swipeSpec =
+ layoutState.transformationSpec.swipeSpec ?: layoutState.transitions.defaultSwipeSpec
+ } else {
+ // We were not driving the transition and we don't force the update, so the spec won't
+ // be used and it doesn't matter which one we set here.
+ newTransition.swipeSpec = SceneTransitions.DefaultSwipeSpec
+ }
+
swipeTransition = newTransition
}
@@ -491,7 +503,7 @@ internal class SceneGestureHandler(
* The signed distance between [fromScene] and [toScene]. It is negative if [fromScene] is
* above or to the left of [toScene].
*/
- val distance: Float
+ val distance: Float,
) : TransitionState.Transition(_fromScene.key, _toScene.key) {
var _currentScene by mutableStateOf(_fromScene)
override val currentScene: SceneKey
@@ -524,6 +536,9 @@ internal class SceneGestureHandler(
/** Job to check that there is at most one offset animation in progress. */
private var offsetAnimationJob: Job? = null
+ /** The spec to use when animating this transition to either [fromScene] or [toScene]. */
+ lateinit var swipeSpec: SpringSpec<Float>
+
/** Ends any previous [offsetAnimationJob] and runs the new [job]. */
private fun startOffsetAnimation(job: () -> Job) {
cancelOffsetAnimation()
@@ -545,13 +560,6 @@ internal class SceneGestureHandler(
}
}
- // TODO(b/290184746): Make this spring spec configurable.
- private val animationSpec =
- spring(
- stiffness = Spring.StiffnessMediumLow,
- visibilityThreshold = OffsetVisibilityThreshold
- )
-
fun animateOffset(
// TODO(b/317063114) The CoroutineScope should be removed.
coroutineScope: CoroutineScope,
@@ -575,7 +583,7 @@ internal class SceneGestureHandler(
offsetAnimatable.animateTo(
targetValue = targetOffset,
- animationSpec = animationSpec,
+ animationSpec = swipeSpec,
initialVelocity = initialVelocity,
)
@@ -811,4 +819,6 @@ internal class SceneNestedScrollHandler(
* The number of pixels below which there won't be a visible difference in the transition and from
* which the animation can stop.
*/
-private const val OffsetVisibilityThreshold = 0.5f
+// TODO(b/290184746): Have a better default visibility threshold which takes the swipe distance into
+// account instead.
+internal const val OffsetVisibilityThreshold = 0.5f
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt
index 3a55567d69bb..ac423b70fd50 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt
@@ -17,7 +17,10 @@
package com.android.compose.animation.scene
import androidx.compose.animation.core.AnimationSpec
+import androidx.compose.animation.core.Spring
+import androidx.compose.animation.core.SpringSpec
import androidx.compose.animation.core.snap
+import androidx.compose.animation.core.spring
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.util.fastForEach
@@ -36,6 +39,7 @@ import com.android.compose.animation.scene.transformation.Translate
/** The transitions configuration of a [SceneTransitionLayout]. */
class SceneTransitions
internal constructor(
+ internal val defaultSwipeSpec: SpringSpec<Float>,
internal val transitionSpecs: List<TransitionSpecImpl>,
) {
private val cache = mutableMapOf<SceneKey, MutableMap<SceneKey, TransitionSpecImpl>>()
@@ -91,7 +95,13 @@ internal constructor(
TransitionSpecImpl(from, to, TransformationSpec.EmptyProvider)
companion object {
- val Empty = SceneTransitions(transitionSpecs = emptyList())
+ internal val DefaultSwipeSpec =
+ spring(
+ stiffness = Spring.StiffnessMediumLow,
+ visibilityThreshold = OffsetVisibilityThreshold,
+ )
+
+ val Empty = SceneTransitions(DefaultSwipeSpec, transitionSpecs = emptyList())
}
}
@@ -125,15 +135,30 @@ interface TransitionSpec {
}
interface TransformationSpec {
- /** The [AnimationSpec] used to animate the associated transition progress. */
+ /**
+ * The [AnimationSpec] used to animate the associated transition progress from `0` to `1` when
+ * the transition is triggered (i.e. it is not gesture-based).
+ */
val progressSpec: AnimationSpec<Float>
+ /**
+ * The [SpringSpec] used to animate the associated transition progress when the transition was
+ * started by a swipe and is now animating back to a scene because the user lifted their finger.
+ *
+ * If `null`, then the [SceneTransitions.defaultSwipeSpec] will be used.
+ */
+ val swipeSpec: SpringSpec<Float>?
+
/** The list of [Transformation] applied to elements during this transition. */
val transformations: List<Transformation>
companion object {
internal val Empty =
- TransformationSpecImpl(progressSpec = snap(), transformations = emptyList())
+ TransformationSpecImpl(
+ progressSpec = snap(),
+ swipeSpec = null,
+ transformations = emptyList(),
+ )
internal val EmptyProvider = { Empty }
}
}
@@ -151,6 +176,7 @@ internal class TransitionSpecImpl(
val reverse = transformationSpec.invoke()
TransformationSpecImpl(
progressSpec = reverse.progressSpec,
+ swipeSpec = reverse.swipeSpec,
transformations = reverse.transformations.map { it.reversed() }
)
}
@@ -166,6 +192,7 @@ internal class TransitionSpecImpl(
*/
internal class TransformationSpecImpl(
override val progressSpec: AnimationSpec<Float>,
+ override val swipeSpec: SpringSpec<Float>?,
override val transformations: List<Transformation>,
) : TransformationSpec {
private val cache = mutableMapOf<ElementKey, MutableMap<SceneKey, ElementTransformations>>()
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt
index a764a52723af..04e093721824 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt
@@ -17,6 +17,7 @@
package com.android.compose.animation.scene
import androidx.compose.animation.core.AnimationSpec
+import androidx.compose.animation.core.SpringSpec
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
@@ -31,6 +32,12 @@ fun transitions(builder: SceneTransitionsBuilder.() -> Unit): SceneTransitions {
@TransitionDsl
interface SceneTransitionsBuilder {
/**
+ * The default [AnimationSpec] used when after the user lifts their finger after starting a
+ * swipe to transition, to animate back into one of the 2 scenes we are transitioning to.
+ */
+ var defaultSwipeSpec: SpringSpec<Float>
+
+ /**
* Define the default animation to be played when transitioning [to] the specified scene, from
* any scene. For the animation specification to apply only when transitioning between two
* specific scenes, use [from] instead.
@@ -64,12 +71,20 @@ interface SceneTransitionsBuilder {
@TransitionDsl
interface TransitionBuilder : PropertyTransformationBuilder {
/**
- * The [AnimationSpec] used to animate the progress of this transition from `0` to `1` when
- * performing programmatic (not input pointer tracking) animations.
+ * The [AnimationSpec] used to animate the associated transition progress from `0` to `1` when
+ * the transition is triggered (i.e. it is not gesture-based).
*/
var spec: AnimationSpec<Float>
/**
+ * The [SpringSpec] used to animate the associated transition progress when the transition was
+ * started by a swipe and is now animating back to a scene because the user lifted their finger.
+ *
+ * If `null`, then the [SceneTransitionsBuilder.defaultSwipeSpec] will be used.
+ */
+ var swipeSpec: SpringSpec<Float>?
+
+ /**
* Define a progress-based range for the transformations inside [builder].
*
* For instance, the following will fade `Foo` during the first half of the transition then it
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt
index b96f9bebb08b..df186a15181e 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt
@@ -19,6 +19,7 @@ package com.android.compose.animation.scene
import androidx.compose.animation.core.AnimationSpec
import androidx.compose.animation.core.DurationBasedAnimationSpec
import androidx.compose.animation.core.Spring
+import androidx.compose.animation.core.SpringSpec
import androidx.compose.animation.core.VectorConverter
import androidx.compose.animation.core.spring
import androidx.compose.ui.geometry.Offset
@@ -40,10 +41,12 @@ internal fun transitionsImpl(
builder: SceneTransitionsBuilder.() -> Unit,
): SceneTransitions {
val impl = SceneTransitionsBuilderImpl().apply(builder)
- return SceneTransitions(impl.transitionSpecs)
+ return SceneTransitions(impl.defaultSwipeSpec, impl.transitionSpecs)
}
private class SceneTransitionsBuilderImpl : SceneTransitionsBuilder {
+ override var defaultSwipeSpec: SpringSpec<Float> = SceneTransitions.DefaultSwipeSpec
+
val transitionSpecs = mutableListOf<TransitionSpecImpl>()
override fun to(to: SceneKey, builder: TransitionBuilder.() -> Unit): TransitionSpec {
@@ -67,6 +70,7 @@ private class SceneTransitionsBuilderImpl : SceneTransitionsBuilder {
val impl = TransitionBuilderImpl().apply(builder)
return TransformationSpecImpl(
progressSpec = impl.spec,
+ swipeSpec = impl.swipeSpec,
transformations = impl.transformations,
)
}
@@ -80,6 +84,7 @@ private class SceneTransitionsBuilderImpl : SceneTransitionsBuilder {
internal class TransitionBuilderImpl : TransitionBuilder {
val transformations = mutableListOf<Transformation>()
override var spec: AnimationSpec<Float> = spring(stiffness = Spring.StiffnessLow)
+ override var swipeSpec: SpringSpec<Float>? = null
private var range: TransformationRange? = null
private var reversed = false
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt
index ef729921f4cd..140bacaec738 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt
@@ -18,6 +18,7 @@ package com.android.compose.animation.scene
import androidx.compose.animation.core.SpringSpec
import androidx.compose.animation.core.TweenSpec
+import androidx.compose.animation.core.spring
import androidx.compose.animation.core.tween
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.compose.animation.scene.transformation.Transformation
@@ -188,6 +189,40 @@ class TransitionDslTest {
)
}
+ @Test
+ fun springSpec() {
+ val defaultSpec = spring<Float>(stiffness = 1f)
+ val specFromAToC = spring<Float>(stiffness = 2f)
+ val transitions = transitions {
+ defaultSwipeSpec = defaultSpec
+
+ from(TestScenes.SceneA, to = TestScenes.SceneB) {
+ // Default swipe spec.
+ }
+ from(TestScenes.SceneA, to = TestScenes.SceneC) { swipeSpec = specFromAToC }
+ }
+
+ assertThat(transitions.defaultSwipeSpec).isSameInstanceAs(defaultSpec)
+
+ // A => B does not have a custom spec.
+ assertThat(
+ transitions
+ .transitionSpec(from = TestScenes.SceneA, to = TestScenes.SceneB)
+ .transformationSpec()
+ .swipeSpec
+ )
+ .isNull()
+
+ // A => C has a custom swipe spec.
+ assertThat(
+ transitions
+ .transitionSpec(from = TestScenes.SceneA, to = TestScenes.SceneC)
+ .transformationSpec()
+ .swipeSpec
+ )
+ .isSameInstanceAs(specFromAToC)
+ }
+
companion object {
private val TRANSFORMATION_RANGE =
Correspondence.transforming<Transformation, TransformationRange?>(