Skip to content

Commit

Permalink
Fix fling animation for fast scrolling gestures (#1616)
Browse files Browse the repository at this point in the history
Fixes: https://youtrack.jetbrains.com/issue/CMP-6791

## Testing
- Check fast gesture for fling (see the issue description)
- Ensure that unintentional fling is not reproduced after the stop and
lift up gesture:
JetBrains/compose-multiplatform#3335

## Release Notes
### Fixes - iOS
- Fling animation works correctly for fast scrolling gestures
  • Loading branch information
ASalavei authored Oct 15, 2024
1 parent 2a766f0 commit a663b6f
Show file tree
Hide file tree
Showing 4 changed files with 23 additions and 12 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@

package androidx.compose.ui.input.pointer.util

internal actual const val AssumePointerMoveStoppedMilliseconds: Int = 40
internal actual const val HistorySize: Int = 20

/**
Expand All @@ -25,5 +24,6 @@ internal actual const val HistorySize: Int = 20
internal actual fun VelocityTracker1D.shouldUseDataPoints(
points: FloatArray,
times: FloatArray,
count: Int
count: Int,
afterPointerStop: Boolean
): Boolean = true
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import kotlin.math.abs
import kotlin.math.sign
import kotlin.math.sqrt

internal expect val AssumePointerMoveStoppedMilliseconds: Int
private const val AssumePointerMoveStoppedMilliseconds: Int = 40
internal expect val HistorySize: Int

// TODO(b/204895043): Keep value in sync with VelocityPathFinder.HorizonMilliSeconds
Expand Down Expand Up @@ -232,7 +232,7 @@ internal constructor(
val newestSample: DataPointAtTime = samples[index] ?: return 0f

var previousSample: DataPointAtTime = newestSample
var previousDirection: Boolean? = null
var afterPointerStop = false

// Starting with the most recent PointAtTime sample, iterate backwards while
// the samples represent continuous motion.
Expand All @@ -247,7 +247,11 @@ internal constructor(
} else {
newestSample
}
if (age > HorizonMilliseconds || delta > AssumePointerMoveStoppedMilliseconds) {
if (delta > AssumePointerMoveStoppedMilliseconds) {
afterPointerStop = true
break
}
if (age > HorizonMilliseconds) {
break
}

Expand All @@ -258,7 +262,13 @@ internal constructor(
sampleCount += 1
} while (sampleCount < HistorySize)

if (sampleCount >= minSampleSize && shouldUseDataPoints(dataPoints, time, sampleCount)) {
if (sampleCount >= minSampleSize && shouldUseDataPoints(
dataPoints,
time,
sampleCount,
afterPointerStop
)
) {
// Choose computation logic based on strategy.
return when (strategy) {
Strategy.Impulse -> {
Expand Down Expand Up @@ -351,7 +361,8 @@ private fun Array<DataPointAtTime?>.set(index: Int, time: Long, dataPoint: Float
internal expect fun VelocityTracker1D.shouldUseDataPoints(
points: FloatArray,
times: FloatArray,
count: Int
count: Int,
afterPointerStop: Boolean
): Boolean


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@

package androidx.compose.ui.input.pointer.util

internal actual const val AssumePointerMoveStoppedMilliseconds: Int = 40
internal actual const val HistorySize: Int = 20

/**
Expand All @@ -25,5 +24,6 @@ internal actual const val HistorySize: Int = 20
internal actual fun VelocityTracker1D.shouldUseDataPoints(
points: FloatArray,
times: FloatArray,
count: Int
count: Int,
afterPointerStop: Boolean
): Boolean = true
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ package androidx.compose.ui.input.pointer.util

import kotlin.math.abs

internal actual const val AssumePointerMoveStoppedMilliseconds: Int = 100
internal actual const val HistorySize: Int = 40 // Increased to store history on 120 Hz devices

private const val MinimumGestureDurationMilliseconds: Int = 50
Expand All @@ -30,14 +29,15 @@ private const val MinimumGestureSpeed: Float = 1.0f // Minimum tracking speed, d
internal actual fun VelocityTracker1D.shouldUseDataPoints(
points: FloatArray,
times: FloatArray,
count: Int
count: Int,
afterPointerStop: Boolean
): Boolean {
if (count == 0) {
return false
}

val timeDelta = abs(times[0] - times[count - 1])
if (timeDelta < MinimumGestureDurationMilliseconds) {
if (timeDelta < MinimumGestureDurationMilliseconds && afterPointerStop) {
return false
}

Expand Down

0 comments on commit a663b6f

Please sign in to comment.