Merge "Simplify the swipe offset animation logic" into main
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 b94e49b..391dd51 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
@@ -20,6 +20,7 @@
import android.util.Log
import androidx.compose.animation.core.Animatable
+import androidx.compose.animation.core.AnimationVector1D
import androidx.compose.animation.core.SpringSpec
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.runtime.getValue
@@ -573,7 +574,7 @@
// Important: If we are going to return early because distance is equal to 0, we should
// still make sure we read the offset before returning so that the calling code still
// subscribes to the offset value.
- val offset = if (isAnimatingOffset) offsetAnimatable.value else dragOffset
+ val offset = offsetAnimation?.animatable?.value ?: dragOffset
val distance = distance()
if (distance == DistanceUnspecified) {
@@ -588,20 +589,11 @@
/** The current offset caused by the drag gesture. */
var dragOffset by mutableFloatStateOf(0f)
- /**
- * Whether the offset is animated (the user lifted their finger) or if it is driven by gesture.
- */
- var isAnimatingOffset by mutableStateOf(false)
+ /** The offset animation that animates the offset once the user lifts their finger. */
+ private var offsetAnimation: OffsetAnimation? by mutableStateOf(null)
- // If we are not animating offset, it means the offset is being driven by the user's finger.
override val isUserInputOngoing: Boolean
- get() = !isAnimatingOffset
-
- /** The animatable used to animate the offset once the user lifted its finger. */
- val offsetAnimatable = Animatable(0f, OffsetVisibilityThreshold)
-
- /** Job to check that there is at most one offset animation in progress. */
- private var offsetAnimationJob: Job? = null
+ get() = offsetAnimation == null
/**
* The [TransformationSpecImpl] associated to this transition.
@@ -647,25 +639,21 @@
return distance
}
- /** Ends any previous [offsetAnimationJob] and runs the new [job]. */
- private fun startOffsetAnimation(job: () -> Job) {
+ /** Ends any previous [offsetAnimation] and runs the new [animation]. */
+ private fun startOffsetAnimation(animation: () -> OffsetAnimation) {
cancelOffsetAnimation()
- offsetAnimationJob = job()
+ offsetAnimation = animation()
}
/** Cancel any ongoing offset animation. */
// TODO(b/317063114) This should be a suspended function to avoid multiple jobs running at
// the same time.
fun cancelOffsetAnimation() {
- offsetAnimationJob?.cancel()
- finishOffsetAnimation()
- }
+ val animation = offsetAnimation ?: return
+ offsetAnimation = null
- fun finishOffsetAnimation() {
- if (isAnimatingOffset) {
- isAnimatingOffset = false
- dragOffset = offsetAnimatable.value
- }
+ dragOffset = animation.animatable.value
+ animation.job.cancel()
}
fun animateOffset(
@@ -676,28 +664,29 @@
onAnimationCompleted: () -> Unit,
) {
startOffsetAnimation {
- coroutineScope.launch {
- animateOffset(targetOffset, initialVelocity)
- onAnimationCompleted()
- }
+ val animatable = Animatable(dragOffset, OffsetVisibilityThreshold)
+ val job =
+ coroutineScope.launch {
+ animatable.animateTo(
+ targetValue = targetOffset,
+ animationSpec = swipeSpec,
+ initialVelocity = initialVelocity,
+ )
+
+ onAnimationCompleted()
+ }
+
+ OffsetAnimation(animatable, job)
}
}
- private suspend fun animateOffset(targetOffset: Float, initialVelocity: Float) {
- if (!isAnimatingOffset) {
- offsetAnimatable.snapTo(dragOffset)
- }
- isAnimatingOffset = true
+ private class OffsetAnimation(
+ /** The animatable used to animate the offset. */
+ val animatable: Animatable<Float, AnimationVector1D>,
- val animationSpec = transformationSpec
- offsetAnimatable.animateTo(
- targetValue = targetOffset,
- animationSpec = swipeSpec,
- initialVelocity = initialVelocity,
- )
-
- finishOffsetAnimation()
- }
+ /** The job in which [animatable] is animated. */
+ val job: Job,
+ )
companion object {
const val DistanceUnspecified = 0f
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 eb9b428..557545b1 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
@@ -951,4 +951,15 @@
assertThat(transition).isNotNull()
assertThat(transition!!.progress).isEqualTo(-0.1f)
}
+
+ @Test
+ fun transitionIsImmediatelyUpdatedWhenReleasingFinger() = runGestureTest {
+ // Swipe up from the middle to transition to scene B.
+ val middle = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f)
+ val dragController = onDragStarted(startedPosition = middle, overSlop = up(0.1f))
+ assertTransition(fromScene = SceneA, toScene = SceneB, isUserInputOngoing = true)
+
+ dragController.onDragStopped(velocity = 0f)
+ assertTransition(isUserInputOngoing = false)
+ }
}