From 54d3cccb672dfbef9946213834f63a958b0ceedb Mon Sep 17 00:00:00 2001 From: FooIbar <118464521+FooIbar@users.noreply.github.com> Date: Fri, 18 Oct 2024 22:38:44 +0800 Subject: [PATCH] Fix AnchoredDraggable fling behavior --- .../gestures/AnchoredHorizontalDraggable.kt | 59 ++++++++++++------- .../androidx/compose/material3/SideDrawer.kt | 4 +- .../material3/fork/SwipeToDismissBox.fork.kt | 4 +- 3 files changed, 43 insertions(+), 24 deletions(-) diff --git a/app/src/main/kotlin/androidx/compose/foundation/gestures/AnchoredHorizontalDraggable.kt b/app/src/main/kotlin/androidx/compose/foundation/gestures/AnchoredHorizontalDraggable.kt index d4c34c068..d0867d5ca 100644 --- a/app/src/main/kotlin/androidx/compose/foundation/gestures/AnchoredHorizontalDraggable.kt +++ b/app/src/main/kotlin/androidx/compose/foundation/gestures/AnchoredHorizontalDraggable.kt @@ -24,6 +24,8 @@ import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.gestures.HorizontalDragEvent.DragDelta import androidx.compose.foundation.gestures.snapping.SnapLayoutInfoProvider import androidx.compose.foundation.gestures.snapping.snapFlingBehavior +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.Offset import androidx.compose.ui.input.pointer.PointerInputChange @@ -255,31 +257,49 @@ internal fun AnchoredDraggableState.newOffsetForDelta(delta: Float) = ((i @ExperimentalFoundationApi private fun DraggableAnchors.computeTarget( currentOffset: Float, - currentValue: T, velocity: Float, positionalThreshold: (totalDistance: Float) -> Float, velocityThreshold: () -> Float, ): T { val currentAnchors = this - val currentAnchorPosition = currentAnchors.positionOf(currentValue) - val velocityThresholdPx = velocityThreshold() - return if (currentAnchorPosition == currentOffset || currentAnchorPosition.isNaN()) { - currentValue + require(!currentOffset.isNaN()) { "The offset provided to computeTarget must not be NaN." } + val velocitySign = sign(velocity) + val isMovingForward = velocitySign == 1.0f + val isMoving = isMovingForward || velocitySign == -1.0f + // When we're not moving, just pick the closest anchor and don't consider directionality + return if (!isMoving) { + currentAnchors.closestAnchor(currentOffset)!! + } else if (abs(velocity) >= abs(velocityThreshold())) { + currentAnchors.closestAnchor(currentOffset, searchUpwards = isMovingForward)!! } else { - if (abs(velocity) >= abs(velocityThresholdPx)) { - currentAnchors.closestAnchor(currentOffset, sign(velocity) > 0)!! - } else { - val neighborAnchor = - currentAnchors.closestAnchor( - currentOffset, - currentOffset - currentAnchorPosition > 0, - )!! - val neighborAnchorPosition = currentAnchors.positionOf(neighborAnchor) - val distance = abs(currentAnchorPosition - neighborAnchorPosition) - val relativeThreshold = abs(positionalThreshold(distance)) - val relativePosition = abs(currentAnchorPosition - currentOffset) - if (relativePosition <= relativeThreshold) currentValue else neighborAnchor - } + val left = currentAnchors.closestAnchor(currentOffset, false)!! + val leftAnchorPosition = currentAnchors.positionOf(left) + val right = currentAnchors.closestAnchor(currentOffset, true)!! + val rightAnchorPosition = currentAnchors.positionOf(right) + val distance = abs(leftAnchorPosition - rightAnchorPosition) + val relativeThreshold = abs(positionalThreshold(distance)) + val closestAnchorFromStart = + if (isMovingForward) leftAnchorPosition else rightAnchorPosition + val relativePosition = abs(closestAnchorFromStart - currentOffset) + if (relativePosition <= relativeThreshold) left else right + } +} + +// https://issuetracker.google.com/374241709 +@Composable +fun flingBehavior( + state: AnchoredDraggableState, + positionalThreshold: (totalDistance: Float) -> Float = AnchoredDraggableDefaults.PositionalThreshold, + animationSpec: AnimationSpec = AnchoredDraggableDefaults.SnapAnimationSpec, +): TargetedFlingBehavior { + val density = LocalDensity.current + return remember(density, state, positionalThreshold, animationSpec) { + anchoredDraggableFlingBehavior( + state = state, + density = density, + positionalThreshold = positionalThreshold, + snapAnimationSpec = animationSpec, + ) } } @@ -325,7 +345,6 @@ private fun AnchoredDraggableLayoutInfoProvider( val target = state.anchors.computeTarget( currentOffset = currentOffset, - currentValue = state.currentValue, velocity = velocity, positionalThreshold = positionalThreshold, velocityThreshold = velocityThreshold, diff --git a/app/src/main/kotlin/androidx/compose/material3/SideDrawer.kt b/app/src/main/kotlin/androidx/compose/material3/SideDrawer.kt index 2fb332d2d..3e01cb46a 100644 --- a/app/src/main/kotlin/androidx/compose/material3/SideDrawer.kt +++ b/app/src/main/kotlin/androidx/compose/material3/SideDrawer.kt @@ -7,11 +7,11 @@ import androidx.compose.animation.core.TweenSpec import androidx.compose.animation.core.animate import androidx.compose.foundation.Canvas import androidx.compose.foundation.background -import androidx.compose.foundation.gestures.AnchoredDraggableDefaults import androidx.compose.foundation.gestures.AnchoredDraggableState import androidx.compose.foundation.gestures.DraggableAnchors import androidx.compose.foundation.gestures.anchoredHorizontalDraggable import androidx.compose.foundation.gestures.detectTapGestures +import androidx.compose.foundation.gestures.flingBehavior import androidx.compose.foundation.gestures.snapTo import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize @@ -257,7 +257,7 @@ fun ModalSideDrawer( enableDragFromStartToEnd = drawerState.isOpen, enableDragFromEndToStart = drawerState.isClosed, enabled = gesturesEnabled, - flingBehavior = AnchoredDraggableDefaults.flingBehavior( + flingBehavior = flingBehavior( state = drawerState.anchoredDraggableState, animationSpec = AnimationSpec, ), diff --git a/app/src/main/kotlin/androidx/compose/material3/fork/SwipeToDismissBox.fork.kt b/app/src/main/kotlin/androidx/compose/material3/fork/SwipeToDismissBox.fork.kt index e39c04e7a..7137cef47 100644 --- a/app/src/main/kotlin/androidx/compose/material3/fork/SwipeToDismissBox.fork.kt +++ b/app/src/main/kotlin/androidx/compose/material3/fork/SwipeToDismissBox.fork.kt @@ -18,13 +18,13 @@ package androidx.compose.material3.fork import androidx.annotation.FloatRange import androidx.compose.animation.core.SpringSpec -import androidx.compose.foundation.gestures.AnchoredDraggableDefaults import androidx.compose.foundation.gestures.AnchoredDraggableState import androidx.compose.foundation.gestures.DraggableAnchors import androidx.compose.foundation.gestures.Orientation import androidx.compose.foundation.gestures.anchoredHorizontalDraggable import androidx.compose.foundation.gestures.animateTo import androidx.compose.foundation.gestures.draggableAnchors +import androidx.compose.foundation.gestures.flingBehavior import androidx.compose.foundation.gestures.snapTo import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Row @@ -208,7 +208,7 @@ fun SwipeToDismissBox( enableDragFromStartToEnd = enableDismissFromStartToEnd, enableDragFromEndToStart = enableDismissFromEndToStart, enabled = gesturesEnabled && state.currentValue == SwipeToDismissBoxValue.Settled, - flingBehavior = AnchoredDraggableDefaults.flingBehavior( + flingBehavior = flingBehavior( state = state.anchoredDraggableState, positionalThreshold = SwipeToDismissBoxDefaults.positionalThreshold, animationSpec = SnapAnimationSpec,