Skip to content

fix(scrollbar): stop the list jumping back on slow drags - #22625

Open
criticalAY wants to merge 1 commit into
ankidroid:mainfrom
criticalAY:fix/fast-scroller-drag-jitter
Open

criticalAY wants to merge 1 commit into
ankidroid:mainfrom
criticalAY:fix/fast-scroller-drag-jitter

Conversation

@criticalAY

@criticalAY criticalAY commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

Note

Assisted-by: Opus 5.5

Purpose / Description

Dragging the scrollbar slowly in the Card Browser made the list shake. Every small move pushed it up a bit and then back down, so instead of gliding it kept going back and forth. This started in 2.24.0.

The same code had two smaller problems. The list hit the bottom before your finger did, so the last part of the scrollbar did nothing. On short lists the scrollbar also ran ahead of your finger.

Fixes

Approach

The list now moves once for each finger movement instead of twice, so it only ever goes the way you're dragging. It also leaves room for the last screenful of cards, so you reach the bottom right when your finger does. That second part is based on #21367.

This works alongside #22612 (the scrollbar size fix). Neither depends on the other, and they merge cleanly.

How Has This Been Tested?

Before

fast-scroller-drag-before-small.mp4

After:

fast-scroller-drag-after.mp4

Checklist

Please, go through these checks before submitting the PR.

  • You have a descriptive commit message with a short title (first line, max 50 chars).
  • You have commented your code, particularly in hard-to-understand areas
  • You have performed a self-review of your own code
  • UI changes: include screenshots of all affected screens (in particular showing any new or changed strings)
  • UI Changes: You have tested your change using the Google Accessibility Scanner

@ZornHadNoChoice

Copy link
Copy Markdown
Collaborator

The new recording seems to have some lag, but otherwise it's much better.

david-allison

This comment was marked as resolved.

@david-allison david-allison added the Needs Author Reply Waiting for a reply from the original author label Oct 20, 2026
@criticalAY
criticalAY force-pushed the fix/fast-scroller-drag-jitter branch from 1bd75a9 to fa68024 Compare October 21, 2026 16:58
@criticalAY

Copy link
Copy Markdown
Contributor Author

thankyou for the patch

@criticalAY criticalAY removed the Needs Author Reply Waiting for a reply from the original author label Oct 21, 2026
david-allison

This comment was marked as resolved.

@david-allison david-allison added the Needs Author Reply Waiting for a reply from the original author label Oct 21, 2026
@criticalAY
criticalAY force-pushed the fix/fast-scroller-drag-jitter branch from fa68024 to d47a9b7 Compare October 22, 2026 07:04
@criticalAY

Copy link
Copy Markdown
Contributor Author

Nice to see GPT seeing through

@criticalAY criticalAY removed the Needs Author Reply Waiting for a reply from the original author label Oct 22, 2026
david-allison

This comment was marked as resolved.

@david-allison david-allison added the Needs Author Reply Waiting for a reply from the original author label Oct 23, 2026
@criticalAY
criticalAY force-pushed the fix/fast-scroller-drag-jitter branch from d47a9b7 to 15955c6 Compare October 23, 2026 22:57
@criticalAY criticalAY removed the Needs Author Reply Waiting for a reply from the original author label Oct 23, 2026
david-allison

This comment was marked as resolved.

@david-allison david-allison added the Needs Author Reply Waiting for a reply from the original author label Oct 24, 2026
@criticalAY criticalAY removed the Needs Author Reply Waiting for a reply from the original author label Oct 27, 2026
@criticalAY
criticalAY force-pushed the fix/fast-scroller-drag-jitter branch from 15955c6 to a11d14d Compare October 27, 2026 17:10
david-allison

This comment was marked as resolved.

@david-allison david-allison added the Needs Author Reply Waiting for a reply from the original author label Oct 27, 2026
@criticalAY
criticalAY force-pushed the fix/fast-scroller-drag-jitter branch from a11d14d to 4709004 Compare October 27, 2026 19:38
@david-allison david-allison removed the Needs Author Reply Waiting for a reply from the original author label Oct 27, 2026
david-allison

This comment was marked as resolved.

@david-allison david-allison added the Needs Author Reply Waiting for a reply from the original author label Oct 27, 2026
@criticalAY
criticalAY force-pushed the fix/fast-scroller-drag-jitter branch from 4709004 to da21217 Compare October 30, 2026 05:17
@criticalAY criticalAY removed the Needs Author Reply Waiting for a reply from the original author label Oct 30, 2026

@mikehardy mikehardy left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assisted-by: grok-4.7

Thanks for sticking with this one — the slow-drag path finally feels right.

Locally I rebased the branch cleanly onto current main (no push) and ran lint, unit tests, and the assigned emulator lane on API35_GAPI; all passed.

The touch path no longer snaps each move to row top plus a deferred fractional correction. Press records a grab offset and a viewport pivot; moves update proportion and scroll at most once per animation frame via pixel scrollBy toward a fractional row, with bounded jump/settle when the target child is not laid out. Robolectric tests cover slow drags, variable heights, track ends, and expanded-row edge cases.

some acceptable findings, no action required, just leaving for posterity: commit title could be under 50 chars; the proportion comment still mentions dividing y by 2 though the code subtracts half handle height and grab offset.

@mikehardy mikehardy added Needs Second Approval Has one approval, one more approval to merge and removed Needs Review labels Nov 1, 2026
david-allison

This comment was marked as resolved.

@david-allison david-allison added the Needs Author Reply Waiting for a reply from the original author label Nov 2, 2026
@criticalAY
criticalAY force-pushed the fix/fast-scroller-drag-jitter branch from da21217 to 0915f43 Compare November 2, 2026 20:07
@criticalAY criticalAY removed the Needs Author Reply Waiting for a reply from the original author label Nov 2, 2026
david-allison

This comment was marked as resolved.

@david-allison david-allison added the Needs Author Reply Waiting for a reply from the original author label Nov 2, 2026
@criticalAY
criticalAY force-pushed the fix/fast-scroller-drag-jitter branch from 0915f43 to 60f53d0 Compare November 2, 2026 21:14
@criticalAY criticalAY removed the Needs Author Reply Waiting for a reply from the original author label Nov 2, 2026
david-allison

This comment was marked as resolved.

@david-allison david-allison added the Needs Author Reply Waiting for a reply from the original author label Nov 2, 2026
Dragging the scrollbar slowly made the list snap back to the top of a row
on every move, then forward again a moment later. The list now only moves
the way the finger does, reaches the top and the bottom at the ends of the
track whatever the row heights, and stays put when the scrollbar is pressed.

Fixes 20994

Assisted-by: Claude Opus 5.5
@criticalAY criticalAY removed the Needs Author Reply Waiting for a reply from the original author label Nov 3, 2026
@criticalAY
criticalAY force-pushed the fix/fast-scroller-drag-jitter branch from 60f53d0 to 92d6497 Compare November 3, 2026 06:03

@david-allison david-allison left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few docs changes/refactorings. I /think/ the code is good, but it's taking a lot of time to reason through it

Comment on lines +366 to +380
private var dragProportion = 0f

// Task that converts handle position into scroll command
private val scrollTask =
private var dragHandleHeight = 0

private var dragGrabOffset: Float? = null

private var dragPivot: DragPivot? = null

private var dragShownProportion = 0f

private var dragSettle: ViewTreeObserver.OnPreDrawListener? = null

private var dragSettlesLeft = 0

private var isDragFramePending = false

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • Please extract to a DragSession class (appropriate variables/methods), this is difficult to understand
  • Please add KDocs
  • Please document the lifecycle: press -> move -> layout -> release


private fun RecyclerView.hasPendingLayout() = isLayoutRequested || hasPendingAdapterUpdates()

private fun RecyclerView.dragPivotAt(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

small KDoc

Comment on lines +417 to 421
when {
snapToEnds && topRow <= 0.0 -> 0f
snapToEnds && bottomRow >= itemCount -> 1f
else -> proportion.coerceIn(MIN_PIVOT_PROPORTION, 1 - MIN_PIVOT_PROPORTION)
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

very nice

return DragPivot(topRow, bottomRow, pivotProportion, itemCount)
}

private fun requestDragFrame() {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Document the association with scrollToDragPosition

recyclerView.postOnAnimation(dragFrame)
}

private fun scrollToDragPosition(isSettling: Boolean = false) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

document isSettling at least

Comment on lines +440 to +463
val layoutManager = recyclerView.layoutManager as? LinearLayoutManager
if (layoutManager == null) {
recyclerView.scrollToPosition((dragProportion * itemCount).toInt().coerceAtMost(itemCount - 1))
return
}
if (dragPivot?.itemCount != itemCount) dragPivot = null
if (dragPivot == null) {
if (recyclerView.hasPendingLayout()) {
settleAfterLayout(recyclerView)
return
}
if (dragProportion > 0f && dragProportion < 1f) {
dragPivot = recyclerView.dragPivotAt(dragShownProportion, snapToEnds = false)
}
}
val target = dragTarget(itemCount) ?: return
dragShownProportion = dragProportion
val helper = OrientationHelper.createVerticalHelper(layoutManager)
val distance = if (recyclerView.isLayoutRequested) null else layoutManager.distanceTo(target, helper, itemCount)
if (distance != null) {
if (distance != 0) recyclerView.scrollBy(0, distance)
return
}
if (dragSettlesLeft-- <= 0) return

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Document/extract the code inside the try a little more; this takes a bit more time to work through than I'm comfortable with


companion object {
private val DEFAULT_AUTO_HIDE_DELAY = 1500.milliseconds
private const val MAX_DRAG_SETTLES = 3

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

explain how we came to this

Comment on lines +550 to +567
private class DragPivot(
val topRow: Double,
val bottomRow: Double,
val proportion: Float,
val itemCount: Int,
)

private class DragTarget(
val row: Double,
val isBottomRow: Boolean,
) {
fun rowAndFraction(itemCount: Int): Pair<Int, Double> {
if (row >= itemCount) return Pair(itemCount - 1, 1.0)
val (wholeRows, fraction) = row.coerceAtLeast(0.0).wholeAndFraction()
if (isBottomRow && fraction == 0.0 && wholeRows > 0) return Pair(wholeRows.toInt() - 1, 1.0)
return Pair(wholeRows.toInt(), fraction)
}
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Document these a little more; especially unit types

@david-allison david-allison added the Needs Author Reply Waiting for a reply from the original author label Nov 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Needs Author Reply Waiting for a reply from the original author Needs Second Approval Has one approval, one more approval to merge

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Scrolling the card browser is jittery

4 participants