Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make ClampingScrollSimulation ballistic and more like Android #120420

Merged
merged 6 commits into from Feb 28, 2023

Conversation

gnprice
Copy link
Member

@gnprice gnprice commented Feb 10, 2023

Fixes #120338.
Fixes #119875.
Fixes #113424.

Fixes most of #16371, as the combined symptom of those three. (There's still #120345, and possibly other contributing factors, remaining.)

This replaces the implementation of ClampingScrollSimulation with a new version that better matches the Android scroll physics it's intended to match.

The new implementation is a version of the Android curve which has been adjusted so that it behaves in a physically reasonable way, in order to satisfy an invariant which the Flutter scroll protocol relies on.

Because the old ClampingScrollSimulation didn't satisfy that invariant either, it suffered from a bug (#120338) where restarting the simulation with new metrics would have the side effect of adding friction, making a fling go about 15% less far. So this fixes #120338.

The new implementation also goes the same total distance as the Android scroll physics, whereas the existing version would go about 7% less far (stacking with the 15% reduction when the other issue was triggered.) That fixes #119875.

Finally, the new implementation comes to a smooth stop, like Android does, whereas the existing version would speed slightly up before abruptly stopping. That fixes #113424.

The new implementation's curve is the unique curve that both goes the same total distance as Android's for every possible starting velocity, and meets the Flutter scroll protocol's ballistic invariant. So in that sense this is the canonical way of fixing up the Android behavior to something physically reasonable, or something that works correctly in Flutter.

Cc: @Piinks @grouma @goderbauer @nt4f04uNd @MxSoul who've previously been interested in this problem.

Pre-launch Checklist

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • I read the Tree Hygiene wiki page, which explains my responsibilities.
  • I read and followed the Flutter Style Guide, including Features we expect every widget to implement.
  • I signed the CLA.
  • I listed at least one issue that this PR fixes in the description above.
  • I updated/added relevant documentation (doc comments with ///).
  • I added new tests to check the change I am making, or this PR is test-exempt.
  • All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel on Discord.

Fixes flutter#120338.
Fixes flutter#119875.
Fixes flutter#113424.

Fixes most of flutter#16371, as the combined symptom of those three.
(There's still flutter#120345, and possibly other contributing factors,
remaining.)

This replaces the implementation of ClampingScrollSimulation with
a new version that better matches the Android scroll physics it's
intended to match.

The new implementation is a version of the Android curve which has
been adjusted so that it behaves in a physically reasonable way,
in order to satisfy an invariant which the Flutter scroll protocol
relies on.

Because the old ClampingScrollSimulation didn't satisfy that
invariant either, it suffered from a bug (flutter#120338) where
restarting the simulation with new metrics would have the side
effect of adding friction, making a fling go about 15% less far.
So this fixes flutter#120338.

The new implementation also goes the same total distance as the
Android scroll physics, whereas the existing version would go about
7% less far (stacking with the 15% reduction when the other issue
was triggered.)  That fixes flutter#119875.

Finally, the new implementation comes to a smooth stop, like Android
does, whereas the existing version would speed slightly up before
abruptly stopping.  That fixes flutter#113424.

The new implementation's curve is the unique curve that both goes the
same total distance as Android's for every possible starting velocity,
and meets the Flutter scroll protocol's ballistic invariant.  So in
that sense this is the canonical way of fixing up the Android behavior
to something physically reasonable, or something that works correctly
in Flutter.
A few tests encoded precise expectations for how far a fling-scroll
would go, and need small updates.
@flutter-dashboard flutter-dashboard bot added a: accessibility Accessibility, e.g. VoiceOver or TalkBack. (aka a11y) f: scrolling Viewports, list views, slivers, etc. framework flutter/packages/flutter repository. See also f: labels. labels Feb 10, 2023
@gnprice
Copy link
Member Author

gnprice commented Feb 10, 2023

Here are before/after screen recordings showing the effect of this PR. The recipe is very similar to the repro recipes for #119875 and #120338:

  1. Use the platform_tests/scroll_overlay app, on an Android device.

  2. Scroll with a fling gesture, swiping rapidly straight upward. In these recordings, after the first fling completes, I do a series of four rapid flings to test the momentum behavior too.

  3. Observe how far the scroll goes, as shown by the numbered items. In these recordings, you can also see the measured velocity at each frame displayed graphically on the right.


The first pair of recordings are with itemCount: 1000 still included in the ListView constructor call. This causes the framework to restart the simulation frequently, nearly every frame, so that it relies on the scroll physics being ballistic.

The "before" version therefore shows the effects of #120338 as well as #119875, so it suffers much too much drag; but the restarts mask the effect of #113424.

The "after" version matches Android much more closely, and ends at almost exactly the same place.

Before, with restarts After, with restarts
before-withrestarts.mp4
after-withrestarts.mp4

The second pair of recordings are with itemCount: 1000 removed, which suppresses those restarts as described in the repro for #120338.

The "before" version therefore shows the effect of #113424, speeding up at the end. And it still displays #119875, suffering too much drag but not as much as when #120338 is also present.

The "after" version again matches Android much more closely; the behavior is the same as in the version with restarts, to within the precision of my finger. There's no speeding up at the end.

Before, no restarts After, no restarts
before-norestarts.mp4
after-norestarts.mp4

@gnprice
Copy link
Member Author

gnprice commented Feb 10, 2023

This PR does not use Android's exact scrolling curve, as the previous PRs #77497 and #107735 did. As described in #120338, the Android curve unfortunately does not satisfy the Flutter scroll protocol's invariant of physical reasonableness.

Using the Android curve without changes to the scroll protocol causes a loss of friction, #83632 — the effect is in the opposite direction from the existing curve's #119875, and is worse.

Revising the protocol to remove that invariant would be possible, but complicated, and probably requires a breaking change:
#120338 (comment)

This PR fixes the major user-visible issues with the current implementation and makes it much more similar to Android's. When I try it out, either in scroll_overlay or in a more typical app, the scrolling now feels natural and responsive. (As long as you swipe vertically, pending #120345.) The remaining discrepancy is in subtle details where Android's behavior loses any grounding in physical metaphor.

So I think it would be reasonable to declare that remaining discrepancy a polish issue in Android, not in Flutter, and stick with this PR's "like Android, but fixed" physics indefinitely.

Alternatively, even if we do intend to pursue changing the scroll protocol in order to use the Android curve, I think it would make sense to merge this implementation first, for two reasons:

  • This fixes the existing physics's major user-visible issues in a much lower-risk way that can be shipped sooner.
  • Because this makes the physics closer to Android's and the code no more complex, it doesn't put any new obstacles in the way of the other approach.

@@ -23,4 +25,98 @@ void main() {
checkInitialConditions(75.0, 614.2093);
checkInitialConditions(5469.0, 182.114534);
});

test('ClampingScrollSimulation only decelerates, never speeds up', () {
Copy link
Member

Choose a reason for hiding this comment

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

Can we add a test that the velocity eventually goes to zero?

Copy link
Member Author

Choose a reason for hiding this comment

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

Sure, will do.

I think that follows as a consequence of being ballistic/restartable: if the simulation still has significant velocity in the last moments before the end (i.e., if the limit as time goes to the ending time isn't zero), then restarting it at that point would mean it goes on for longer than the few moments that were otherwise remaining.

But it would still be sensible to have a test for it directly.

Copy link
Member Author

Choose a reason for hiding this comment

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

Added.

@grouma
Copy link
Member

grouma commented Feb 10, 2023

What a wonderful set of write ups. I'm truly impressed and thank you!

I don't own any of this infrastructure so I can't provide a proper review but I did leave a suggestion for another test.

@Piinks
Copy link
Contributor

Piinks commented Feb 10, 2023

Thank you so much for all of this work! I have this and the other PR in scroll_overlay on my review queue, just wanted to acknowledge since it may take me a moment to review all of this.
As you know, we have tried to fix this a couple of times, and every time experienced regressions, so I'd also like to run this through all of the internal tests we have access to as well.
I really appreciate everything you have done here. Should have some feedback by the end of today.

Copy link
Contributor

@Piinks Piinks left a comment

Choose a reason for hiding this comment

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

I think this looks great, but will need to wait for Monday to test it extensively. Following up on the scroll_overlay PR now. 👍

@whiskeyPeak
Copy link
Contributor

Are these curves specific to flutter apps running on Android? I find that the scrolling curve on Linux also jumps a little towards the end.

@gnprice
Copy link
Member Author

gnprice commented Feb 12, 2023

Are these curves specific to flutter apps running on Android? I find that the scrolling curve on Linux also jumps a little towards the end.

This scroll physics is the one Flutter uses by default on all platforms other than iOS and macOS. So you're seeing #113424, which is one of the issues this PR fixes.

@Piinks
Copy link
Contributor

Piinks commented Feb 13, 2023

I've fired off some additional internal testing, will report back.

@Piinks
Copy link
Contributor

Piinks commented Feb 14, 2023

So I've tested this manually against some of the regressions we saw in the past from trying to resolve this issue, so far all is well. 👍

@gnprice
Copy link
Member Author

gnprice commented Feb 14, 2023

Great! Thanks for all the testing. Hoping those internal tests all come back green.

@Piinks
Copy link
Contributor

Piinks commented Feb 16, 2023

Still following up on tests, there is an infra issue with triggering and returning the results here

@Piinks
Copy link
Contributor

Piinks commented Feb 16, 2023

So there are some test failures, but they look to be expected. For example, if a customer has a screenshot test that scrolled before taking the picture, it would look different now. I will need a day or two to check all of the tests and communicate with customers.

@gnprice
Copy link
Member Author

gnprice commented Feb 16, 2023

Makes sense. Thanks for your help!

@Piinks
Copy link
Contributor

Piinks commented Feb 22, 2023

Update: Still working with customers to ensure all of the changes we can see are expected for this PR. Almost there. 👍

@Piinks
Copy link
Contributor

Piinks commented Feb 27, 2023

Only 1 customer approval remains. Thanks for your patience.

@Piinks
Copy link
Contributor

Piinks commented Feb 28, 2023

Customers are set. 👍

///
/// The default value causes the particle to travel the same total distance
/// as in the Android scroll physics.
// See mFlingFriction.
Copy link
Contributor

Choose a reason for hiding this comment

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

This should take the form of a See also and a breadcrumb [] like in other docs.

Copy link
Contributor

Choose a reason for hiding this comment

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

Oh I see this is private, // v ///. You can disregard, unless this would be helpful as a public comment.

Copy link
Member Author

Choose a reason for hiding this comment

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

Cool. Yeah, this is a reference to the corresponding Android source (like some other private comments in this class), so I think it's useful only for someone actually reading the implementation.

engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Mar 1, 2023
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Mar 1, 2023
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Mar 1, 2023
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Mar 1, 2023
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Mar 1, 2023
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Mar 1, 2023
@gnprice gnprice deleted the pr-fix-clampingscroll branch March 1, 2023 18:42
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Mar 1, 2023
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Mar 2, 2023
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Mar 2, 2023
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Mar 2, 2023
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Mar 2, 2023
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Mar 2, 2023
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Mar 2, 2023
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Mar 2, 2023
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Mar 3, 2023
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Mar 3, 2023
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request May 10, 2023
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request May 10, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
a: accessibility Accessibility, e.g. VoiceOver or TalkBack. (aka a11y) autosubmit Merge PR when tree becomes green via auto submit App f: scrolling Viewports, list views, slivers, etc. framework flutter/packages/flutter repository. See also f: labels.
Projects
None yet
5 participants