Repository navigation
ClampingScrollSimulation decays sooner and goes less far than native Android scrolling #119875
Description
Activity
I've done some investigation of this issue already, and have a diagnosis.
Android uses (in
OverScroller.java, which is used by RecyclerView) a certain spline curve: time and position are scaled as$t, x \in [0,1]$ , and both$t$ and$x$ are cubic functions of an additional parameter$p \in [0,1]$ : namely$t = 0.425 p^3 + 0.525 p$ while$x = -1/2 p^3 + 3/2 p$ . This curve has$x = 0$ at$t = 0$ , and$x = 1$ at$t = 1$ .With the spline curve, to find the scroll position
$x$ for a frame at time$t$ , one needs to solve the first cubic for$p$ and then put that into the second cubic to find$x$ . To avoid having to solve a cubic on each frame, Android'sOverScrollerprecomputes a static table of$x$ for times$t = 0, 0.01, 0.02, \ldots, 0.99, 1$ , and does linear interpolation from that.In
ClampingScrollSimulation, we simplify that to a single cubic:$x = (1/.35/3.065) * (1.2 t^3 - 3.27 t^2 + 3.065 t)$ . At$t = 0$ this has the correct value$x = 0$ . At$t = 1$ , however, it has$x \approx 0.92752$ . This causes the scroll to end up$1 - x(1) \approx 7.248\%$ short of where the Android scroll physics would take it.Here's a set of graphs comparing the Android scroll physics and what's in
ClampingScrollSimulation, showing both position and velocity over time:So what's needed is to use a curve that better approximates the Android curve. In particular, at
$t = 1$ it should have$x = 1$ .
There's a bonus issue with the
ClampingScrollSimulationcurve, which one can see in the above graphs: the scroll does not come to a smooth stop.- Edit: This bonus issue is previously filed as:
ClampingScrollSimulationhas large nonzero velocity when near the end, and velocity even increases slightly there #113424
Instead the velocity actually increases over the last 200ms or so, before abruptly stopping when the simulation ends. In the Android scroll physics, the velocity decreases continuously to reach zero at the end.
I originally noticed this bonus issue thanks to these graphs; when interacting as a user I'd never put my finger on it. But now that I know about it, it's very conspicuous to me when using
scroll_overlay; and I find it noticeable even in a normal app with a long list to scroll through. I suspect that for lots of users the lack of a smooth stop contributes to a feeling that the scrolling isn't quite right, even without them ever identifying specifically why.To fix this bonus issue and get a smooth stop, we want the curve to have velocity 0 at
$t = 1$ , i.e.$\frac{\mathrm d x}{\mathrm d t}(1) = 0$ , like the Android curve does.- Edit: This bonus issue is previously filed as:
- addedin triagePresently being triaged by the triage teamPresently being triaged by the triage team
on Feb 3, 2023 Reproducible using the code sample and steps outlined above.
Labeling based on the information provided above.
recording
telegram-cloud-document-4-5974508209786850387.mp4
flutter doctor -v
[✓] Flutter (Channel stable, 3.7.1, on macOS 13.1 22C65 darwin-arm64, locale en-GB) • Flutter version 3.7.1 on channel stable at /Users/nexus/dev/sdks/flutter • Upstream repository https://github.com/flutter/flutter.git • Framework revision 7048ed95a5 (2 days ago), 2023-02-01 09:07:31 -0800 • Engine revision 800594f1f4 • Dart version 2.19.1 • DevTools version 2.20.1 [✓] Android toolchain - develop for Android devices (Android SDK version 33.0.0) • Android SDK at /Users/nexus/Library/Android/sdk • Platform android-33, build-tools 33.0.0 • Java binary at: /Applications/Android Studio.app/Contents/jbr/Contents/Home/bin/java • Java version OpenJDK Runtime Environment (build 11.0.15+0-b2043.56-8887301) • All Android licenses accepted. [✓] Xcode - develop for iOS and macOS (Xcode 14.2) • Xcode at /Applications/Xcode.app/Contents/Developer • Build 14C18 • CocoaPods version 1.11.3 [✓] Chrome - develop for the web • Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome [!] Android Studio (version 2022.1) • Android Studio at /Applications/Android Studio.app/Contents • Flutter plugin can be installed from: 🔨 https://plugins.jetbrains.com/plugin/9212-flutter • Dart plugin can be installed from: 🔨 https://plugins.jetbrains.com/plugin/6351-dart ✗ Unable to find bundled Java version. • Try updating or re-installing Android Studio. [!] Android Studio (version 2022.1) • Android Studio at /Users/nexus/Library/Application Support/JetBrains/Toolbox/apps/AndroidStudio/ch-0/221.6008.13.2211.9477386/Android Studio.app/Contents • Flutter plugin can be installed from: 🔨 https://plugins.jetbrains.com/plugin/9212-flutter • Dart plugin can be installed from: 🔨 https://plugins.jetbrains.com/plugin/6351-dart ✗ Unable to find bundled Java version. • Try updating or re-installing Android Studio. [✓] IntelliJ IDEA Community Edition (version 2022.3.2) • IntelliJ at /Applications/IntelliJ IDEA CE.app • Flutter plugin can be installed from: 🔨 https://plugins.jetbrains.com/plugin/9212-flutter • Dart plugin can be installed from: 🔨 https://plugins.jetbrains.com/plugin/6351-dart [✓] VS Code (version 1.75.0) • VS Code at /Applications/Visual Studio Code.app/Contents • Flutter extension version 3.58.0 [✓] Connected device (4 available) • Pixel 7 (mobile) • adb-28291FDH2001SA-5Lv71w._adb-tls-connect._tcp. • android-arm64 • Android 13 (API 33) • M2007J20CG (mobile) • adb-5dd3be00-17AYzd._adb-tls-connect._tcp. • android-arm64 • Android 12 (API 31) • macOS (desktop) • macos • darwin-arm64 • macOS 13.1 22C65 darwin-arm64 • Chrome (web) • chrome • web-javascript • Google Chrome 109.0.5414.119 [✓] HTTP Host Availability • All required HTTP hosts are available ! Doctor found issues in 2 categories.[!] Flutter (Channel master, 3.8.0-3.0.pre.21, on macOS 13.1 22C65 darwin-arm64, locale en-GB) • Flutter version 3.8.0-3.0.pre.21 on channel master at /Users/nexus/dev/sdks/flutters ! Warning: `flutter` on your path resolves to /Users/nexus/dev/sdks/flutter/bin/flutter, which is not inside your current Flutter SDK checkout at /Users/nexus/dev/sdks/flutters. Consider adding /Users/nexus/dev/sdks/flutters/bin to the front of your path. ! Warning: `dart` on your path resolves to /Users/nexus/dev/sdks/flutter/bin/dart, which is not inside your current Flutter SDK checkout at /Users/nexus/dev/sdks/flutters. Consider adding /Users/nexus/dev/sdks/flutters/bin to the front of your path. • Upstream repository https://github.com/flutter/flutter.git • Framework revision f3effce630 (5 hours ago), 2023-02-03 00:07:10 -0500 • Engine revision e3fe6dade9 • Dart version 3.0.0 (build 3.0.0-198.0.dev) • DevTools version 2.21.1 • If those were intentional, you can disregard the above warnings; however it is recommended to use "git" directly to perform update checks and upgrades. [✓] Android toolchain - develop for Android devices (Android SDK version 33.0.0) • Android SDK at /Users/nexus/Library/Android/sdk • Platform android-33, build-tools 33.0.0 • Java binary at: /Applications/Android Studio.app/Contents/jbr/Contents/Home/bin/java • Java version OpenJDK Runtime Environment (build 11.0.15+0-b2043.56-8887301) • All Android licenses accepted. [✓] Xcode - develop for iOS and macOS (Xcode 14.2) • Xcode at /Applications/Xcode.app/Contents/Developer • Build 14C18 • CocoaPods version 1.11.3 [✓] Chrome - develop for the web • Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome [!] Android Studio (version 2022.1) • Android Studio at /Applications/Android Studio.app/Contents • Flutter plugin can be installed from: 🔨 https://plugins.jetbrains.com/plugin/9212-flutter • Dart plugin can be installed from: 🔨 https://plugins.jetbrains.com/plugin/6351-dart ✗ Unable to find bundled Java version. • Try updating or re-installing Android Studio. [!] Android Studio (version 2022.1) • Android Studio at /Users/nexus/Library/Application Support/JetBrains/Toolbox/apps/AndroidStudio/ch-0/221.6008.13.2211.9477386/Android Studio.app/Contents • Flutter plugin can be installed from: 🔨 https://plugins.jetbrains.com/plugin/9212-flutter • Dart plugin can be installed from: 🔨 https://plugins.jetbrains.com/plugin/6351-dart ✗ Unable to find bundled Java version. • Try updating or re-installing Android Studio. [✓] IntelliJ IDEA Community Edition (version 2022.3.2) • IntelliJ at /Applications/IntelliJ IDEA CE.app • Flutter plugin can be installed from: 🔨 https://plugins.jetbrains.com/plugin/9212-flutter • Dart plugin can be installed from: 🔨 https://plugins.jetbrains.com/plugin/6351-dart [✓] VS Code (version 1.75.0) • VS Code at /Applications/Visual Studio Code.app/Contents • Flutter extension version 3.58.0 [✓] Connected device (4 available) • Pixel 7 (mobile) • adb-28291FDH2001SA-5Lv71w._adb-tls-connect._tcp. • android-arm64 • Android 13 (API 33) • M2007J20CG (mobile) • adb-5dd3be00-17AYzd._adb-tls-connect._tcp. • android-arm64 • Android 12 (API 31) • macOS (desktop) • macos • darwin-arm64 • macOS 13.1 22C65 darwin-arm64 • Chrome (web) • chrome • web-javascript • Google Chrome 109.0.5414.119 [✓] HTTP Host Availability • All required HTTP hosts are available ! Doctor found issues in 3 categories.- addedplatform-androidAndroid applications specificallyAndroid applications specificallyframeworkflutter/packages/flutter repository. See also f: labels.flutter/packages/flutter repository. See also f: labels.a: fidelityMatching the OEM platforms betterMatching the OEM platforms betterf: scrollingViewports, list views, slivers, etc.Viewports, list views, slivers, etc.has reproducible stepsThe issue has been confirmed reproducible and is ready to work onThe issue has been confirmed reproducible and is ready to work onfound in release: 3.7Found to occur in 3.7Found to occur in 3.7found in release: 3.8Found to occur in 3.8Found to occur in 3.8and removedin triagePresently being triaged by the triage teamPresently being triaged by the triage team
on Feb 3, 2023 Thanks @danagbemava-nc for that recording! Yes, that exactly.
I plan to send a PR to fix this issue. Before that, I'll send a platform_tests PR or two to update the
scroll_overlaytest app and to make some changes I've found helpful in investigating.Before I send a PR, it would be helpful to get some feedback on the possible solutions. I see several options:
-
We could continue using a cubic, and just choose a higher-fidelity cubic.
Specifically, we can make it go the same total distance as Android, and also come to a smooth stop. Together with the basic requirements of starting at zero and at the user's initial velocity, that exhausts the flexibility there is in a cubic.
I haven't yet experimented enough to have a strong view on whether this would be good enough to feel right as a user. I suspect it would not. For example, like the current
ClampingScrollSimulationcubic, this curve would have a negative second derivative right from the start — meaning that the scroll would be decelerating at a rapid rate from the beginning. In the Android spline, the deceleration starts at zero. I suspect that difference affects how the scrolling feels as a user. -
We could use a higher-degree polynomial that more closely approximates the Android scrolling curve.
For example, a quartic (a degree-4 polynomial) would let us set the initial deceleration to zero, in addition to the other constraints. At higher and higher degree we can approximate the Android curve more and more closely.
-
We could use linear interpolation from a table, like Android does, in order to match its curve exactly.
Android's
OverScroller.javacomputes the table in a static initializer. Dart doesn't have those, and we wouldn't want to compute it at runtime anyway. The way to do this would be to have aconstlist of numbers, computed once (and with the script to compute them checked in underdev/so that they aren't completely magic numbers.)
Here's a set of graphs showing Android's curve; the current cubic from
main; the cubic from option 1; and a degree-10 polynomial as an example of option 2:

You can see that the degree-10 polynomial (in red) hugs the Android spline (in blue) much more closely than either cubic, but still imperfectly.
-
Aha, and just now I've done the code archaeology that I probably should have done sooner, and found the previous work on this issue:
- Previous issue thread: Android scroll speed deceleration rate doesn't match native #16371
- PR to fix it, which was merged: Update ClampingScrollSimulation to better match Android #77497
- Issue that PR introduced, causing it to be reverted: Scroll view regains velocity as it scrolls #83632
- Revised PR to fix it, also merged: Update Ballistic animation & ClampingScrollSimulation #107735
- Issue that one introduced, causing it to also be reverted: ListView Sliding abnormal flashing #110340
- Pointers to additional issues that caused the latter revert: Revert Ballistic & Clamping simulation updates #111201 (comment)
Cc'ing @grouma and @Piinks as the authors of those previous PRs.
(I did try searching the tracker, and missed those; the closest I found was #103762 which is about iOS. But
git log widgets/scroll_simulation.dartpulled the relevant history right up.)
I see that both the above PRs went for option 3 from my previous comment: using linear interpolation from a table, in order to go for perfect fidelity. That's the option I was thinking was probably best. I guess those PRs confirm that others agree.
I'll spend some time studying those previous PRs (especially the later version) and the issues that caused the reverts.
I'll spend some time studying those previous PRs (especially the later version) and the issues that caused the reverts.
@gnprice thank you very much for your detailed analysis! This has been a challenging issue to resolve. I look forward to hearing what your thoughts are after some research.
Reacted by Greg Price- addedP2Important issues not at the top of the work listImportant issues not at the top of the work list
on Feb 7, 2023 I'll spend some time studying those previous PRs (especially the later version) and the issues that caused the reverts.
OK, I've now done this. I'm going to file a few more issues reflecting what I've found, then a PR or two with a possible way forward.
- added a commit that references this issue
on Feb 10, 2023 Incredible write up! Thank you.
Before I landed #77497, I did attempt another approach. I simply updated the cubic approximation to match the boundary conditions and thus have the velocity eventually decelerate to zero. As you noted above, this updated approximation is not truly accurate which I noticed. I then decided to copy the logic which has issues as you also noted.
Reacted by Greg PriceIncredible write up! Thank you.
Thanks!
Before I landed #77497, I did attempt another approach. I simply updated the cubic approximation to match the boundary conditions and thus have the velocity eventually decelerate to zero. As you noted above, this updated approximation is not truly accurate which I noticed. I then decided to copy the logic which has issues as you also noted.
Makes sense. That would have been the same cubic shown in green in my graphs above (#119875 (comment)). It would have fixed this issue and #113424, but #120338 would have still been present, with I think a slightly smaller effect because the initial deceleration would be slightly less.
- addedr: fixedIssue is closed as already fixed in a newer versionIssue is closed as already fixed in a newer version
on Mar 1, 2023 This thread has been automatically locked since there has not been any recent activity after it was closed. If you are still experiencing a similar issue, please open a new bug, including the output of
flutter doctor -vand a minimal reproduction of the issue.- locked as resolved and limited conversation to collaborators
on Mar 15, 2023

On Android, when the user attempts to scroll quickly using a "fling" gesture, the scroll doesn't go as far or as fast as it would have in a native Android scrolling view. In a long list this can make scrolling feel sluggish, or not easy, or not responsive.
One reason for this behavior is that Flutter's
ClampingScrollSimulation, which is meant to match Android's scroll physics (and is used by default on Android and other non-Apple platforms), in fact uses a curve that decays faster than the Android curve, and goes less far in total. When the simulation is complete, the distance traveled is about 7.2% less than the Android scroll physics would travel.(This isn't the only factor contributing to the overall slowness of the scroll; I'll file other issues separately.)
Related issue:
Steps to Reproduce
platform_tests/scroll_overlayapp on an Android device.Expected results:
The Flutter list should scroll the same distance as the Android list, so that corresponding list items are aligned with each other.
Actual results:
The Flutter list goes substantially less far than the Android list.
Specifically, when I try this on the current version of
scroll_overlay(at flutter/platform_tests@1d797b8 ), I see "Android 94" next to "Flutter 81".That version has item heights that vary through the list and don't quite agree between Android and Flutter, making the numbers a bit hard to interpret. In a revised
scroll_overlaywith fixed heights (which I'll send a platform_tests PR for), I see "Android 202" next to "Flutter 189".Logs