From 3f69c5258d333a299a09ce44bae23e230c559e4b Mon Sep 17 00:00:00 2001 From: usuiat Date: Wed, 27 Sep 2023 00:32:15 +0900 Subject: [PATCH] Fix unexpected fling behaviors. - Use a position relative to the root component to calculate velocity. - Reset VelocityTracker when drag gesture starts. - Cancel fling animation when another drag gesture starts. - Don't do scroll process when the position does not change. --- .../sessions/section/TimetableGrid.kt | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/feature/sessions/src/main/java/io/github/droidkaigi/confsched2023/sessions/section/TimetableGrid.kt b/feature/sessions/src/main/java/io/github/droidkaigi/confsched2023/sessions/section/TimetableGrid.kt index 891654ea9..b97917b62 100644 --- a/feature/sessions/src/main/java/io/github/droidkaigi/confsched2023/sessions/section/TimetableGrid.kt +++ b/feature/sessions/src/main/java/io/github/droidkaigi/confsched2023/sessions/section/TimetableGrid.kt @@ -47,6 +47,8 @@ import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.input.pointer.positionChange import androidx.compose.ui.input.pointer.util.VelocityTracker import androidx.compose.ui.layout.Placeable +import androidx.compose.ui.layout.onGloballyPositioned +import androidx.compose.ui.layout.positionInRoot import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.semantics.ScrollAxisRange @@ -228,8 +230,14 @@ fun TimetableGrid( } }, ) + .onGloballyPositioned { coordinates -> + timetableState.screenScrollState.componentPositionInRoot = coordinates.positionInRoot() + } .pointerInput(Unit) { detectDragGestures( + onDragStart = { + scrollState.resetTracking() + }, onDrag = { change, dragAmount -> if (timetableScreen.enableHorizontalScroll(dragAmount.x)) { if (change.positionChange() != Offset.Zero) change.consume() @@ -482,6 +490,8 @@ class ScreenScrollState( private val velocityTracker = VelocityTracker() private val _scrollX = Animatable(initialScrollX) private val _scrollY = Animatable(initialScrollY) + var componentPositionInRoot = Offset.Zero + private var cancelFling = false val scrollX: Float get() = _scrollX.value @@ -499,9 +509,11 @@ class ScreenScrollState( timeMillis: Long, position: Offset, ) { + cancelFling = true if (scrollX.isNaN().not() && scrollY.isNaN().not()) { coroutineScope { - velocityTracker.addPosition(timeMillis = timeMillis, position = position) + val positionInRoot = position + componentPositionInRoot + velocityTracker.addPosition(timeMillis = timeMillis, position = positionInRoot) launch { _scrollX.snapTo(scrollX) } @@ -513,6 +525,7 @@ class ScreenScrollState( } suspend fun flingIfPossible(nestedScrollDispatcher: NestedScrollDispatcher) = coroutineScope { + cancelFling = false val velocity = velocityTracker.calculateVelocity() launch { _scrollX.animateDecay( @@ -546,6 +559,10 @@ class ScreenScrollState( available = weAvailable - weConsumed, source = NestedScrollSource.Fling, ) + + if (cancelFling) { + this@animateDecay.cancelAnimation() + } } } } @@ -696,6 +713,9 @@ private class TimetableScreen( position: Offset, nestedScrollDispatcher: NestedScrollDispatcher, ) { + // If the position does not change, VelocityTracker malfunctions. Therefore return here. + if (dragAmount == Offset.Zero) return + val parentConsumed = nestedScrollDispatcher.dispatchPreScroll( available = dragAmount, source = NestedScrollSource.Drag,