summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt151
1 files changed, 74 insertions, 77 deletions
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
index 0ea3295a7144..c776ae98dcb7 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
@@ -196,6 +196,8 @@ class SceneGestureHandler(
}
internal fun onDrag(delta: Float) {
+ if (delta == 0f) return
+
swipeTransition.dragOffset += delta
// First check transition.fromScene should be changed for the case where the user quickly
@@ -293,48 +295,77 @@ class SceneGestureHandler(
return
}
- // We were not animating.
- if (swipeTransition._fromScene == swipeTransition._toScene) {
- transitionState = TransitionState.Idle(swipeTransition._fromScene.key)
- return
+ fun animateTo(targetScene: Scene, targetOffset: Float) {
+ // If the effective current scene changed, it should be reflected right now in the
+ // current scene state, even before the settle animation is ongoing. That way all the
+ // swipeables and back handlers will be refreshed and the user can for instance quickly
+ // swipe vertically from A => B then horizontally from B => C, or swipe from A => B then
+ // immediately go back B => A.
+ if (targetScene != swipeTransition._currentScene) {
+ swipeTransition._currentScene = targetScene
+ layoutImpl.onChangeScene(targetScene.key)
+ }
+
+ animateOffset(
+ initialVelocity = velocity,
+ targetOffset = targetOffset,
+ targetScene = targetScene.key
+ )
}
- // Compute the destination scene (and therefore offset) to settle in.
- val targetOffset: Float
- val targetScene: Scene
- val offset = swipeTransition.dragOffset
- val distance = swipeTransition.distance
- if (
- canChangeScene &&
+ val fromScene = swipeTransition._fromScene
+ if (canChangeScene) {
+ // If we are halfway between two scenes, we check what the target will be based on the
+ // velocity and offset of the transition, then we launch the animation.
+
+ val toScene = swipeTransition._toScene
+ if (fromScene == toScene) {
+ // We were not animating.
+ transitionState = TransitionState.Idle(fromScene.key)
+ return
+ }
+
+ // Compute the destination scene (and therefore offset) to settle in.
+ val offset = swipeTransition.dragOffset
+ val distance = swipeTransition.distance
+ if (
shouldCommitSwipe(
offset,
distance,
velocity,
- wasCommitted = swipeTransition._currentScene == swipeTransition._toScene,
+ wasCommitted = swipeTransition._currentScene == toScene,
)
- ) {
- targetOffset = distance
- targetScene = swipeTransition._toScene
+ ) {
+ // Animate to the next scene
+ animateTo(targetScene = toScene, targetOffset = distance)
+ } else {
+ // Animate to the initial scene
+ animateTo(targetScene = fromScene, targetOffset = 0f)
+ }
} else {
- targetOffset = 0f
- targetScene = swipeTransition._fromScene
- }
-
- // If the effective current scene changed, it should be reflected right now in the current
- // scene state, even before the settle animation is ongoing. That way all the swipeables and
- // back handlers will be refreshed and the user can for instance quickly swipe vertically
- // from A => B then horizontally from B => C, or swipe from A => B then immediately go back
- // B => A.
- if (targetScene != swipeTransition._currentScene) {
- swipeTransition._currentScene = targetScene
- layoutImpl.onChangeScene(targetScene.key)
+ // We are doing an overscroll animation between scenes. In this case, we can also start
+ // from the idle position.
+
+ val startFromIdlePosition = swipeTransition.dragOffset == 0f
+
+ if (startFromIdlePosition) {
+ // If there is a next scene, we start the overscroll animation.
+ val target = fromScene.findTargetSceneAndDistance(velocity)
+ val isValidTarget = target.distance != 0f && target.sceneKey != fromScene.key
+ if (isValidTarget) {
+ swipeTransition._toScene = layoutImpl.scene(target.sceneKey)
+ swipeTransition._distance = target.distance
+
+ animateTo(targetScene = fromScene, targetOffset = 0f)
+ } else {
+ // We will not animate
+ transitionState = TransitionState.Idle(fromScene.key)
+ }
+ } else {
+ // We were between two scenes: animate to the initial scene.
+ animateTo(targetScene = fromScene, targetOffset = 0f)
+ }
}
-
- animateOffset(
- initialVelocity = velocity,
- targetOffset = targetOffset,
- targetScene = targetScene.key
- )
}
/**
@@ -407,47 +438,6 @@ class SceneGestureHandler(
}
}
- internal fun animateOverscroll(velocity: Velocity): Velocity {
- val velocityAmount =
- when (orientation) {
- Orientation.Vertical -> velocity.y
- Orientation.Horizontal -> velocity.x
- }
-
- if (velocityAmount == 0f) {
- // There is no remaining velocity
- return Velocity.Zero
- }
-
- val fromScene = currentScene
- val target = fromScene.findTargetSceneAndDistance(velocityAmount)
- val isValidTarget = target.distance != 0f && target.sceneKey != fromScene.key
-
- if (!isValidTarget || isDrivingTransition) {
- // We have not found a valid target or we are already in a transition
- return Velocity.Zero
- }
-
- swipeTransition._currentScene = fromScene
- swipeTransition._fromScene = fromScene
- swipeTransition._toScene = layoutImpl.scene(target.sceneKey)
- swipeTransition._distance = target.distance
- swipeTransition.absoluteDistance = target.distance.absoluteValue
- swipeTransition.stopOffsetAnimation()
- swipeTransition.dragOffset = 0f
-
- transitionState = swipeTransition
-
- animateOffset(
- initialVelocity = velocityAmount,
- targetOffset = 0f,
- targetScene = fromScene.key
- )
-
- // The animateOffset animation consumes any remaining velocity.
- return velocity
- }
-
private class SwipeTransition(initialScene: Scene) : TransitionState.Transition {
var _currentScene by mutableStateOf(initialScene)
override val currentScene: SceneKey
@@ -623,9 +613,16 @@ class SceneNestedScrollHandler(
velocityAvailable
},
onPostFling = { velocityAvailable ->
- // If there is any velocity left, we can try running an overscroll animation between
- // scenes.
- gestureHandler.animateOverscroll(velocity = velocityAvailable)
+ val velocityAmount = velocityAvailable.toAmount()
+ if (velocityAmount != 0f) {
+ // If there is any velocity left, we can try running an overscroll animation
+ // between scenes.
+ gestureHandler.onDragStarted()
+ gestureHandler.onDragStopped(velocity = velocityAmount, canChangeScene = false)
+ }
+
+ // We consumed any remaining velocity.
+ velocityAvailable
},
)
}