Skip to content

Conversation

@grouma
Copy link
Member

@grouma grouma commented Mar 7, 2021

I've noticed that the Android Flutter scroll logic appears to be slightly off. After a scroll slows down to a sufficient amount, items appear to "snap" into place. It's as if the inertial scrolling clamps down and micro movements are not allowed. Note the difference is very subtle but present across Flutter apps, e.g. Stadia, gPay etc. It appears that the underlying implementation is an approximation of the Android logic which may explain the behavior.

Since this has been driving me mad I have decided to translate the 2-dimensional logic from Scroller.java to 1 dimension and reuse in Flutter. The behavior should now be identical assuming equivalent DPI and friction settings.

@flutter-dashboard flutter-dashboard bot added the framework flutter/packages/flutter repository. See also f: labels. label Mar 7, 2021
@flutter-dashboard
Copy link

It looks like this pull request may not have tests. Please make sure to add tests before merging. If you need an exemption to this rule, contact Hixie on the #hackers channel in Chat.

If you are not sure if you need tests, consider this rule of thumb: the purpose of a test is to make sure someone doesn't accidentally revert the fix. Ask yourself, is there anything in your PR that you feel it is important we not accidentally revert back to how it was before your fix?

Reviewers: Read the Tree Hygiene page and make sure this patch meets those guidelines before LGTMing.

@google-cla google-cla bot added the cla: yes label Mar 7, 2021
@grouma grouma changed the title [WIP] Update Android Scroll Physics to better match Android Update Android Scroll Physics to better match Android Mar 7, 2021
@grouma grouma requested review from HansMuller and Hixie March 7, 2021 05:24
@grouma grouma changed the title Update Android Scroll Physics to better match Android Update ClampingScrollSimulation to better match Android Mar 7, 2021
@grouma
Copy link
Member Author

grouma commented Mar 8, 2021

I created a diff between the output (x, dx) with the new logic and existing logic. The data on the left is the new logic. You can see the velocity slows down to 0 as the scroll comes to a completion. The existing logic on the other hand slows down and then starts to ramp back up at the very end, likely due to the cubic function.

return 9.80665 *
39.37 *
friction *
1.0 * // Assume display density of 1.0.
Copy link
Contributor

Choose a reason for hiding this comment

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

how much of a difference does this make? if it matters, we should be able to find a way to get the pixel density here...

Copy link
Member Author

Choose a reason for hiding this comment

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

There is an API in Android but I couldn't find one in Flutter. We have deviceRatio which is slightly different. For what it's worth, this hasn't changed from the previous logic, just the formula is now explicit.

Copy link
Contributor

Choose a reason for hiding this comment

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

although, actually, maybe 1.0 is the right number here? since we operate in logical pixels not physical pixels in the framework. In which case the comment should reflect that -- it's not an assumption, it's a reality.

_splineTime[i] = coef * ((1.0 - y) * p1 + y * p2) + y * y * y;
}
_splinePosition[_nbSamples] = _splineTime[_nbSamples] = 1.0;
_isInitialized = true;
Copy link
Contributor

Choose a reason for hiding this comment

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

can we do this statically and just fill in the data? (rather than doing it at runtime on first use)

Copy link
Member Author

@grouma grouma Mar 8, 2021

Choose a reason for hiding this comment

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

Do you mean statically through code, like in the JAVA implementation? If so, no I don't think Dart offers anything similar hence the somewhat awkward implementation.

If you actually meant writing an external program to create the static data, we could do that. It would obviously not be as easily configured liked the Android implementation.

Copy link
Contributor

Choose a reason for hiding this comment

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

There is no "static initialization" time in Dart. All code that runs should do so as a result of invoking main().

The options here are either to compute it on first use, or to add a hook somewhere in the framework to cause it to be computed early.

Copy link
Contributor

Choose a reason for hiding this comment

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

I meant offline (as in have a program in the dev/ directory that runs this code and generates the constants and you can just paste them into this file). Looks like this code isn't configurable by developers, so that wouldn't reduce the overall configurability for our developers, but would be more efficient for users.

Copy link
Member Author

Choose a reason for hiding this comment

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

Done.

/// Creates a scroll physics simulation that matches Android scrolling.
ClampingScrollSimulation({
required this.position,
required this.velocity,
Copy link
Contributor

Choose a reason for hiding this comment

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

is the "some simplifications have been made" bit still true?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes. The logic in Scroller.java is 2-dimensional and has logic for non "fling" scrolls so there are simplifications still.

@Hixie
Copy link
Contributor

Hixie commented Mar 8, 2021

what's the performance like?

@Hixie
Copy link
Contributor

Hixie commented Mar 8, 2021

I'll defer to @HansMuller or @goderbauer for a detailed review.
(review might be easier once the spurious reformatting is fixed though)

@grouma
Copy link
Member Author

grouma commented Mar 8, 2021

Removed the unnecessary diffs.

what's the performance like?

Performance should be more or less equivalent after the spline data is initialized as there are roughly the same number of operations. I doubt the initialization has a measurable impact on the performance but it'd be good to know how best to verify that assumption.

@grouma grouma requested a review from goderbauer March 8, 2021 23:54
});


test('ClampingScrollSimulation velocity eventually reaches 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.

Note these new tests fail with the old logic. As noted in the my previous comment, velocity ramps back up near the end of the simulation.

Copy link
Member

@goderbauer goderbauer left a comment

Choose a reason for hiding this comment

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

Looks like some existing tests are failing. Did you take a look at those?

import 'dart:math' as math;

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
Copy link
Member

Choose a reason for hiding this comment

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

files in the widgets directory can't import material.

Copy link
Member Author

Choose a reason for hiding this comment

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

I think this was an artifact of getting the deviceRatio in a pervious implementation. I'm surprised there isn't an unused import lint or issue.

Copy link
Contributor

Choose a reason for hiding this comment

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

(material reexports most of widgets so it doesn't get flagged as unused. redundant imports is a lint we haven't deployed yet though I understand work is ongoing.)

@grouma
Copy link
Member Author

grouma commented Mar 11, 2021

This should be ready for review PTAL.

@goderbauer
Copy link
Member

Looks like there are still some test failures.

const double _endTension = 1.0;
const double _inflexion = 0.35;

// Generated spline data used in ClampingScrollSimulation.
Copy link
Member

Choose a reason for hiding this comment

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

Can you add a little more context to this doc? E.g. where's the math in here coming from? What should be done with the result after changing anything here?

Copy link
Member Author

Choose a reason for hiding this comment

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

Done.

}
}

class _NBSample {
Copy link
Member

Choose a reason for hiding this comment

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

what does the NB stand for? Maybe document or just spell it out?

Copy link
Member Author

Choose a reason for hiding this comment

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

To be honest I'm not sure. I think it might just happen to be the variable name of choice for the underlying math equation that the original developer likely referenced. I kept it here as I wanted it to be immediately obvious how this logic is translated from Scroller.java.


static const int _nbSamples = 100;

// Generated from dev/tools/generate_android_spline_data.dart.
Copy link
Member

Choose a reason for hiding this comment

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

Ideally, we would have some kind of a test to verify that the numbers reproduced here match the result of that calculation to ensure that we update things here if the calculation math ever changes.

Copy link
Member Author

Choose a reason for hiding this comment

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

A test may be helpful but I don't think it provides a ton of value. Updating the comment in the file as you suggested should make things much more clear.

@goderbauer
Copy link
Member

goderbauer commented Mar 12, 2021

Overall, this looks good. Thanks for taking the time to update it! I am going to trigger a google3 presubmit to see if it breaks any tests there (like it broke some of our framework tests).

Copy link
Member

@goderbauer goderbauer left a comment

Choose a reason for hiding this comment

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

LGTM

@Piinks
Copy link
Contributor

Piinks commented Sep 9, 2021

This has caused a bad regression in Android ballistic scrolling. Described in #83632 with good repros, flinging hard and fast on the scrollable causes the ballistics activity to run off much further than it should.

There are some mentions of this in #16371 as well.

find.byType(ListWheelScrollView),
const Offset(0.0, -50.0),
800.0,
100.0,
Copy link
Contributor

Choose a reason for hiding this comment

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

@grouma Can you recall why this fling needed to be reduced so much? This is probably the same regression that has been reported for flings going way too far.

Copy link
Member Author

Choose a reason for hiding this comment

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

Unfortunately I do not. I recall that a number of tests had to have their parameters updated to keep the "goldens" valid. The main intent behind this change was to fix the jitter users would experience as the scroll slowed down. I added a test for that invalid behavior, see ClampingScrollSimulation velocity eventually reaches zero.

@Piinks
Copy link
Contributor

Piinks commented Sep 9, 2021

Chatted with @goderbauer and we are going to revert this, unfortunately it has been in for a while so it can't be done automatically. I should be able to do this manually tomorrow and add some extra tests while I am at it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

framework flutter/packages/flutter repository. See also f: labels.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants