• 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
..
animation Loading commit data...
cupertino Loading commit data...
dart Loading commit data...
examples Loading commit data...
foundation Loading commit data...
gestures Loading commit data...
harness Loading commit data...
material Loading commit data...
painting Loading commit data...
physics Loading commit data...
rendering Loading commit data...
scheduler Loading commit data...
semantics Loading commit data...
services Loading commit data...
widgets Loading commit data...
_goldens_io.dart Loading commit data...
_goldens_web.dart Loading commit data...
analysis_options.yaml Loading commit data...
flutter_test_config.dart Loading commit data...
image_data.dart Loading commit data...