Set the scroll midpoint as soon as a finger lands or lifts so the next move still scrolls

This commit is contained in:
maxjivi05 committed 2026-10-30 15:05:03 -04:00
1 parent 10618b0f60
commit 83efbe3987
2 files changed
+23 -7

No files matched your search

@@ -26,7 +26,6 @@ class TwoFingerGestures(private val slopPx: Float, private val listener: Listene
private var startSpan = 0f
private var lastX = 0f
private var lastY = 0f
private var anchored = false
private val starts = HashMap<Int, Pair<Float, Float>>()
private val held = ArrayList<MotionEvent>()
private val hold = Runnable {
@@ -85,7 +84,7 @@ class TwoFingerGestures(private val slopPx: Float, private val listener: Listene
return true
}
State.SCROLLING -> {
anchored = false
anchor(event, -1)
return true
}
State.HELD, State.TAPPED -> return true
@@ -111,7 +110,6 @@ class TwoFingerGestures(private val slopPx: Float, private val listener: Listene
listener.cancelTouch()
lastX = midX(event)
lastY = midY(event)
anchored = true
listener.scrollStart(lastX, lastY)
return true
}
@@ -122,8 +120,7 @@ class TwoFingerGestures(private val slopPx: Float, private val listener: Listene
if (event.pointerCount >= 2) {
val mx = midX(event)
val my = midY(event)
if (anchored) listener.scroll(lastX - mx, lastY - my)
anchored = true
listener.scroll(lastX - mx, lastY - my)
lastX = mx
lastY = my
}
@@ -145,7 +142,7 @@ class TwoFingerGestures(private val slopPx: Float, private val listener: Listene
return true
}
State.SCROLLING -> {
anchored = false
anchor(event, event.actionIndex)
return true
}
State.HELD, State.TAPPED -> return true
@@ -204,6 +201,13 @@ class TwoFingerGestures(private val slopPx: Float, private val listener: Listene
hypot(event.getX(i) - start.first, event.getY(i) - start.second) > slopPx
}
private fun anchor(event: MotionEvent, lifted: Int) {
val kept = (0 until event.pointerCount).filter { it != lifted }
if (kept.size < 2) return
lastX = (event.getX(kept[0]) + event.getX(kept[1])) / 2f
lastY = (event.getY(kept[0]) + event.getY(kept[1])) / 2f
}
private fun span(event: MotionEvent) = hypot(event.getX(1) - event.getX(0), event.getY(1) - event.getY(0))
private fun midX(event: MotionEvent) = (event.getX(0) + event.getX(1)) / 2f
private fun midY(event: MotionEvent) = (event.getY(0) + event.getY(1)) / 2f
@@ -71,11 +71,23 @@ class TwoFingerGesturesTest {
assertTrue(touch(MotionEvent.ACTION_POINTER_UP, 1, 1 to (400f to 260f), 2 to (500f to 260f)))
assertTrue(touch(MotionEvent.ACTION_MOVE, 0, 1 to (400f to 250f)))
assertTrue(touch(MotionEvent.ACTION_POINTER_DOWN, 1, 1 to (400f to 250f), 3 to (800f to 600f)))
assertTrue(touch(MotionEvent.ACTION_MOVE, 0, 1 to (400f to 250f), 3 to (800f to 600f)))
assertTrue(touch(MotionEvent.ACTION_MOVE, 0, 1 to (400f to 230f), 3 to (800f to 580f)))
assertEquals(listOf("cancel", "start 450.0,260.0", "scroll 0.0,20.0"), calls)
}
@Test fun aThirdFingerLiftingDuringAScrollKeepsTheRemainingMidpoint() {
val c = 3 to (450f to 500f)
touch(MotionEvent.ACTION_DOWN, 0, a)
time += 20
touch(MotionEvent.ACTION_POINTER_DOWN, 1, a, b)
time += 20
touch(MotionEvent.ACTION_MOVE, 0, 1 to (400f to 260f), 2 to (500f to 260f))
assertTrue(touch(MotionEvent.ACTION_POINTER_DOWN, 2, 1 to (400f to 260f), 2 to (500f to 260f), c))
assertTrue(touch(MotionEvent.ACTION_POINTER_UP, 0, 1 to (400f to 260f), 2 to (500f to 260f), c))
assertTrue(touch(MotionEvent.ACTION_MOVE, 0, 2 to (500f to 250f), c))
assertEquals(listOf("cancel", "start 450.0,260.0", "scroll 0.0,5.0"), calls)
}
@Test fun pinchPassesThroughForZoom() {
touch(MotionEvent.ACTION_DOWN, 0, a)
time += 20