summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt9
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/PredictiveBackHandler.kt1
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt4
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt2
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeAnimation.kt70
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt7
6 files changed, 79 insertions, 14 deletions
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 ab3f6396e5c0..70ff47baa7a9 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
@@ -127,7 +127,14 @@ internal class DraggableHandler(
directionChangeSlop = layoutImpl.directionChangeSlop,
)
- return createSwipeAnimation(layoutImpl, result, isUpOrLeft, orientation, gestureContext)
+ return createSwipeAnimation(
+ layoutImpl,
+ result,
+ isUpOrLeft,
+ orientation,
+ gestureContext,
+ layoutImpl.decayAnimationSpec,
+ )
}
private fun resolveSwipeSource(startedPosition: Offset): SwipeSource.Resolved? {
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/PredictiveBackHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/PredictiveBackHandler.kt
index eee27b75a999..41279d338896 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/PredictiveBackHandler.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/PredictiveBackHandler.kt
@@ -70,6 +70,7 @@ internal fun PredictiveBackHandler(
distance = 1f,
gestureContext =
ProvidedGestureContext(dragOffset = 0f, direction = InputDirection.Max),
+ decayAnimationSpec = layoutImpl.decayAnimationSpec,
)
animateProgress(
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
index f6d40eef53a3..72bb82bd41bb 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
@@ -17,6 +17,7 @@
package com.android.compose.animation.scene
import androidx.annotation.FloatRange
+import androidx.compose.animation.rememberSplineBasedDecay
import androidx.compose.foundation.LocalOverscrollFactory
import androidx.compose.foundation.OverscrollEffect
import androidx.compose.foundation.OverscrollFactory
@@ -747,6 +748,7 @@ internal fun SceneTransitionLayoutForTesting(
val layoutDirection = LocalLayoutDirection.current
val defaultEffectFactory = checkNotNull(LocalOverscrollFactory.current)
val animationScope = rememberCoroutineScope()
+ val decayAnimationSpec = rememberSplineBasedDecay<Float>()
val layoutImpl = remember {
SceneTransitionLayoutImpl(
state = state as MutableSceneTransitionLayoutStateImpl,
@@ -762,6 +764,7 @@ internal fun SceneTransitionLayoutForTesting(
lookaheadScope = lookaheadScope,
directionChangeSlop = directionChangeSlop,
defaultEffectFactory = defaultEffectFactory,
+ decayAnimationSpec = decayAnimationSpec,
)
.also { onLayoutImpl?.invoke(it) }
}
@@ -801,6 +804,7 @@ internal fun SceneTransitionLayoutForTesting(
layoutImpl.swipeSourceDetector = swipeSourceDetector
layoutImpl.swipeDetector = swipeDetector
layoutImpl.transitionInterceptionThreshold = transitionInterceptionThreshold
+ layoutImpl.decayAnimationSpec = decayAnimationSpec
}
layoutImpl.Content(modifier)
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 585da0633131..53996d25afdb 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
@@ -17,6 +17,7 @@
package com.android.compose.animation.scene
import androidx.annotation.VisibleForTesting
+import androidx.compose.animation.core.DecayAnimationSpec
import androidx.compose.foundation.OverscrollFactory
import androidx.compose.foundation.clickable
import androidx.compose.foundation.gestures.Orientation
@@ -82,6 +83,7 @@ internal class SceneTransitionLayoutImpl(
internal var swipeSourceDetector: SwipeSourceDetector,
internal var swipeDetector: SwipeDetector,
internal var transitionInterceptionThreshold: Float,
+ internal var decayAnimationSpec: DecayAnimationSpec<Float>,
builder: SceneTransitionLayoutScope<InternalContentScope>.() -> Unit,
/**
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeAnimation.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeAnimation.kt
index 3bd37ad018b0..cb0d33cf5205 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeAnimation.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeAnimation.kt
@@ -21,6 +21,8 @@ package com.android.compose.animation.scene
import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.AnimationSpec
import androidx.compose.animation.core.AnimationVector1D
+import androidx.compose.animation.core.DecayAnimationSpec
+import androidx.compose.animation.core.calculateTargetValue
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.runtime.getValue
@@ -42,6 +44,7 @@ internal fun createSwipeAnimation(
orientation: Orientation,
distance: Float,
gestureContext: MutableDragOffsetGestureContext,
+ decayAnimationSpec: DecayAnimationSpec<Float>,
): SwipeAnimation<*> {
return createSwipeAnimation(
layoutState,
@@ -53,6 +56,7 @@ internal fun createSwipeAnimation(
error("Computing contentForUserActions requires a SceneTransitionLayoutImpl")
},
gestureContext = gestureContext,
+ decayAnimationSpec = decayAnimationSpec,
)
}
@@ -62,6 +66,7 @@ internal fun createSwipeAnimation(
isUpOrLeft: Boolean,
orientation: Orientation,
gestureContext: MutableDragOffsetGestureContext,
+ decayAnimationSpec: DecayAnimationSpec<Float>,
distance: Float = DistanceUnspecified,
): SwipeAnimation<*> {
var lastDistance = distance
@@ -106,6 +111,7 @@ internal fun createSwipeAnimation(
distance = ::distance,
contentForUserActions = { layoutImpl.contentForUserActions().key },
gestureContext = gestureContext,
+ decayAnimationSpec = decayAnimationSpec,
)
}
@@ -117,6 +123,7 @@ private fun createSwipeAnimation(
distance: (SwipeAnimation<*>) -> Float,
contentForUserActions: () -> ContentKey,
gestureContext: MutableDragOffsetGestureContext,
+ decayAnimationSpec: DecayAnimationSpec<Float>,
): SwipeAnimation<*> {
fun <T : ContentKey> swipeAnimation(fromContent: T, toContent: T): SwipeAnimation<T> {
return SwipeAnimation(
@@ -128,6 +135,7 @@ private fun createSwipeAnimation(
requiresFullDistanceSwipe = result.requiresFullDistanceSwipe,
distance = distance,
gestureContext = gestureContext,
+ decayAnimationSpec = decayAnimationSpec,
)
}
@@ -201,6 +209,7 @@ internal class SwipeAnimation<T : ContentKey>(
private val distance: (SwipeAnimation<T>) -> Float,
currentContent: T = fromContent,
private val gestureContext: MutableDragOffsetGestureContext,
+ private val decayAnimationSpec: DecayAnimationSpec<Float>,
) : MutableDragOffsetGestureContext by gestureContext {
/** The [TransitionState.Transition] whose implementation delegates to this [SwipeAnimation]. */
lateinit var contentTransition: TransitionState.Transition
@@ -367,20 +376,10 @@ internal class SwipeAnimation<T : ContentKey>(
check(isAnimatingOffset())
- val motionSpatialSpec = spec ?: layoutState.motionScheme.defaultSpatialSpec()
-
val velocityConsumed = CompletableDeferred<Float>()
offsetAnimationRunnable.complete {
- val result =
- animatable.animateTo(
- targetValue = targetOffset,
- animationSpec = motionSpatialSpec,
- initialVelocity = initialVelocity,
- )
-
- // We are no longer able to consume the velocity, the rest can be consumed by another
- // component in the hierarchy.
- velocityConsumed.complete(initialVelocity - result.endState.velocity)
+ val consumed = animateOffset(animatable, targetOffset, initialVelocity, spec)
+ velocityConsumed.complete(consumed)
// Wait for overscroll to finish so that the transition is removed from the STLState
// only after the overscroll is done, to avoid dropping frame right when the user lifts
@@ -391,6 +390,53 @@ internal class SwipeAnimation<T : ContentKey>(
return velocityConsumed.await()
}
+ private suspend fun animateOffset(
+ animatable: Animatable<Float, AnimationVector1D>,
+ targetOffset: Float,
+ initialVelocity: Float,
+ spec: AnimationSpec<Float>?,
+ ): Float {
+ val initialOffset = animatable.value
+ val decayOffset =
+ decayAnimationSpec.calculateTargetValue(
+ initialVelocity = initialVelocity,
+ initialValue = initialOffset,
+ )
+
+ val willDecayReachBounds =
+ when {
+ targetOffset > initialOffset -> decayOffset >= targetOffset
+ targetOffset < initialOffset -> decayOffset <= targetOffset
+ else -> true
+ }
+
+ if (willDecayReachBounds) {
+ val result = animatable.animateDecay(initialVelocity, decayAnimationSpec)
+ check(animatable.value == targetOffset) {
+ buildString {
+ appendLine(
+ "animatable.value = ${animatable.value} != $targetOffset = targetOffset"
+ )
+ appendLine(" initialOffset=$initialOffset")
+ appendLine(" targetOffset=$targetOffset")
+ appendLine(" initialVelocity=$initialVelocity")
+ appendLine(" decayOffset=$decayOffset")
+ }
+ }
+ return initialVelocity - result.endState.velocity
+ }
+
+ val motionSpatialSpec = spec ?: layoutState.motionScheme.defaultSpatialSpec()
+ animatable.animateTo(
+ targetValue = targetOffset,
+ animationSpec = motionSpatialSpec,
+ initialVelocity = initialVelocity,
+ )
+
+ // We consumed the whole velocity.
+ return initialVelocity
+ }
+
private fun canChangeContent(targetContent: ContentKey): Boolean {
return when (val transition = contentTransition) {
is TransitionState.Transition.ChangeScene ->
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt
index 6b439980cc68..969003cb92f3 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt
@@ -18,7 +18,9 @@
package com.android.compose.animation.scene
+import androidx.compose.animation.SplineBasedFloatDecayAnimationSpec
import androidx.compose.animation.core.Spring
+import androidx.compose.animation.core.generateDecayAnimationSpec
import androidx.compose.animation.core.spring
import androidx.compose.foundation.overscroll
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
@@ -119,10 +121,11 @@ class DraggableHandlerTest {
val transitionInterceptionThreshold = 0.05f
val directionChangeSlop = 10f
+ private val density = Density(1f)
private val layoutImpl =
SceneTransitionLayoutImpl(
state = layoutState,
- density = Density(1f),
+ density = density,
layoutDirection = LayoutDirection.Ltr,
swipeSourceDetector = DefaultEdgeDetector,
swipeDetector = DefaultSwipeDetector,
@@ -134,6 +137,8 @@ class DraggableHandlerTest {
animationScope = testScope,
directionChangeSlop = directionChangeSlop,
defaultEffectFactory = defaultEffectFactory,
+ decayAnimationSpec =
+ SplineBasedFloatDecayAnimationSpec(density).generateDecayAnimationSpec(),
)
.apply { setContentsAndLayoutTargetSizeForTest(LAYOUT_SIZE) }