• Chip Weinberger's avatar
    [Velocity Tracker] Fix: Issue 97761: Flutter Scrolling does not match iOS;... · fffbbf27
    Chip Weinberger authored
    [Velocity Tracker] Fix: Issue 97761: Flutter Scrolling does not match iOS; inadvertent scrolling when user lifts up finger (#132291)
    
    ## Issue
    
    **Issue:** https://github.com/flutter/flutter/issues/97761
    
    https://github.com/flutter/flutter/assets/1863934/53c5e0df-b85a-483c-a17d-bddd18db3aa9
    
    ## The Cause:
    
    The bug is very simple to understand - `velocity_tracker.dart` **only adds new samples while your finger is moving**.
    
    **Therefore**, if you move your finger quickly & (important) stop suddenly with no extra movement, the last 3 samples will all be > 0 dy. Regardless of how long you wait, you will get movement when you lift up your finger.
    
    **Logs from velocity_tracker.dart:**
    Notice: all 3 `_previousVelocityAt` are `dy > 0` despite a 2 second delay since the last scroll
    ```
    // start moving finger
    flutter: addPosition dy:-464.0
    flutter: addPosition dy:-465.0
    flutter: addPosition dy:-466.0
    flutter: addPosition dy:-467.0
    flutter: addPosition dy:-468.0
    flutter: addPosition dy:-469.0
    flutter: addPosition dy:-470.0
    // stop moving finger here, keep it still for 2 seconds & lift it up
    flutter: _previousVelocityAt(-2) samples(-467.0, -468.0)) dy:-176.772140710624
    flutter: _previousVelocityAt(-1) samples(-468.0, -469.0)) dy:-375.0937734433609
    flutter: _previousVelocityAt(0) samples(-469.0, -470.0)) dy:-175.71604287471447
    flutter: primaryVelocity DragEndDetails(Velocity(0.0, -305.5)).primaryVelocity
    flutter: createBallisticSimulation pixels 464.16666666666663 velocity 305.4699824197211
    ```
    
    ## The Fix
    
    **There are 3 options to fix it:**
    A. sample uniformly *per unit time* (a larger more risky change, hurts battery life)
    B. consider elapsed time since the last sample. If greater than X, assume no more velocity. (easy & just as valid)
    C. similar to B, but instead add "ghost samples" of velocity zero, and run calculations as normal (a bit tricker, of dubious benefit imo)
    
    **For Option B I considered two approaches:**
    1. _get the current timestamp and compare to event timestamp._  This is tricky because events are documented to use an arbitrary timescale & I wasn't able to find the code that generates the timestamps. This approach could be considered more.
    2. _get a new timestamp using Stopwatch and compare now vs when the last sample was added._ This is the solution implemented here.  There is a limitation in that we don't know when addSamples is called relative to the event. But, this estimation is already on a very low latency path & still it gives us a *minimum* time bound which is sufficient for comparison. 
    
    **This PR chooses the simplest of the all solutions. Please try it our yourself, it completely solves the problem 😀** Option _B.1_ would be a nice alternative as well, if we can define and access the same timesource as the pointer tracker in a maintainable simple way.
    
    ## After Fix
    
    https://github.com/flutter/flutter/assets/1863934/be50d8e7-d5da-495a-a4af-c71bc541cbe3
    fffbbf27
Name
Last commit
Last update
..
arena_test.dart Loading commit data...
debug_test.dart Loading commit data...
double_tap_test.dart Loading commit data...
drag_test.dart Loading commit data...
events_test.dart Loading commit data...
force_press_test.dart Loading commit data...
gesture_binding_resample_event_on_widget_test.dart Loading commit data...
gesture_binding_resample_event_test.dart Loading commit data...
gesture_binding_test.dart Loading commit data...
gesture_config_regression_test.dart Loading commit data...
gesture_tester.dart Loading commit data...
hit_test_test.dart Loading commit data...
locking_test.dart Loading commit data...
long_press_test.dart Loading commit data...
lsq_solver_test.dart Loading commit data...
monodrag_test.dart Loading commit data...
multidrag_test.dart Loading commit data...
multitap_test.dart Loading commit data...
pointer_router_test.dart Loading commit data...
pointer_signal_resolver_test.dart Loading commit data...
recognizer_test.dart Loading commit data...
resampler_test.dart Loading commit data...
scale_test.dart Loading commit data...
serial_tap_test.dart Loading commit data...
tap_and_drag_test.dart Loading commit data...
tap_test.dart Loading commit data...
team_test.dart Loading commit data...
transformed_double_tap_test.dart Loading commit data...
transformed_long_press_test.dart Loading commit data...
transformed_monodrag_test.dart Loading commit data...
transformed_scale_test.dart Loading commit data...
transformed_tap_test.dart Loading commit data...
velocity_tracker_data.dart Loading commit data...
velocity_tracker_test.dart Loading commit data...