diff options
| -rw-r--r-- | packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt | 151 |
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 }, ) } |