Repository navigation
fix(scrollbar): stop the list jumping back on slow drags - #22625
criticalAY wants to merge 1 commit into
Conversation
|
The new recording seems to have some lag, but otherwise it's much better. |
c57607c to
1bd75a9
Compare
1bd75a9 to
fa68024
Compare
|
thankyou for the patch |
fa68024 to
d47a9b7
Compare
|
Nice to see GPT seeing through |
d47a9b7 to
15955c6
Compare
15955c6 to
a11d14d
Compare
a11d14d to
4709004
Compare
4709004 to
da21217
Compare
mikehardy
left a comment
There was a problem hiding this comment.
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.
da21217 to
0915f43
Compare
0915f43 to
60f53d0
Compare
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
60f53d0 to
92d6497
Compare
david-allison
left a comment
There was a problem hiding this comment.
A few docs changes/refactorings. I /think/ the code is good, but it's taking a lot of time to reason through it
| 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 |
There was a problem hiding this comment.
- Please extract to a
DragSessionclass (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( |
| when { | ||
| snapToEnds && topRow <= 0.0 -> 0f | ||
| snapToEnds && bottomRow >= itemCount -> 1f | ||
| else -> proportion.coerceIn(MIN_PIVOT_PROPORTION, 1 - MIN_PIVOT_PROPORTION) | ||
| } |
| return DragPivot(topRow, bottomRow, pivotProportion, itemCount) | ||
| } | ||
|
|
||
| private fun requestDragFrame() { |
There was a problem hiding this comment.
Document the association with scrollToDragPosition
| recyclerView.postOnAnimation(dragFrame) | ||
| } | ||
|
|
||
| private fun scrollToDragPosition(isSettling: Boolean = false) { |
There was a problem hiding this comment.
document isSettling at least
| 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 |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
explain how we came to this
| 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) | ||
| } | ||
| } |
There was a problem hiding this comment.
Document these a little more; especially unit types
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.