scroll_simulation_test.dart 6.65 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
2 3 4
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

5 6
import 'dart:math' as math;

7
import 'package:flutter/widgets.dart';
8
import 'package:flutter_test/flutter_test.dart';
9 10 11 12

void main() {
  test('ClampingScrollSimulation has a stable initial conditions', () {
    void checkInitialConditions(double position, double velocity) {
13
      final ClampingScrollSimulation simulation = ClampingScrollSimulation(position: position, velocity: velocity);
14 15
      expect(simulation.x(0.0), moreOrLessEquals(position));
      expect(simulation.dx(0.0), moreOrLessEquals(velocity));
16 17 18 19 20 21 22 23 24 25 26 27
    }

    checkInitialConditions(51.0, 2866.91537);
    checkInitialConditions(584.0, 2617.294734);
    checkInitialConditions(345.0, 1982.785934);
    checkInitialConditions(0.0, 1831.366634);
    checkInitialConditions(-156.2, 1541.57665);
    checkInitialConditions(4.0, 1139.250439);
    checkInitialConditions(4534.0, 1073.553798);
    checkInitialConditions(75.0, 614.2093);
    checkInitialConditions(5469.0, 182.114534);
  });
28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157

  test('ClampingScrollSimulation only decelerates, never speeds up', () {
    // Regression test for https://github.com/flutter/flutter/issues/113424
    final ClampingScrollSimulation simulation =
        ClampingScrollSimulation(position: 0, velocity: 8000.0);
    double time = 0.0;
    double velocity = simulation.dx(time);
    while (!simulation.isDone(time)) {
      expect(time, lessThan(3.0));
      time += 1 / 60;
      final double nextVelocity = simulation.dx(time);
      expect(nextVelocity, lessThanOrEqualTo(velocity));
      velocity = nextVelocity;
    }
  });

  test('ClampingScrollSimulation reaches a smooth stop: velocity is continuous and goes to zero', () {
    // Regression test for https://github.com/flutter/flutter/issues/113424
    const double initialVelocity = 8000.0;
    const double maxDeceleration = 5130.0; // -acceleration(initialVelocity), from formula below
    final ClampingScrollSimulation simulation =
        ClampingScrollSimulation(position: 0, velocity: initialVelocity);

    double time = 0.0;
    double velocity = simulation.dx(time);
    const double delta = 1 / 60;
    do {
      expect(time, lessThan(3.0));
      time += delta;
      final double nextVelocity = simulation.dx(time);
      expect((nextVelocity - velocity).abs(), lessThan(delta * maxDeceleration));
      velocity = nextVelocity;
    } while (!simulation.isDone(time));
    expect(velocity, moreOrLessEquals(0.0));
  });

  test('ClampingScrollSimulation is ballistic', () {
    // Regression test for https://github.com/flutter/flutter/issues/120338
    const double delta = 1 / 90;
    final ClampingScrollSimulation undisturbed =
        ClampingScrollSimulation(position: 0, velocity: 8000.0);

    double time = 0.0;
    ClampingScrollSimulation restarted = undisturbed;
    final List<double> xsRestarted = <double>[];
    final List<double> xsUndisturbed = <double>[];
    final List<double> dxsRestarted = <double>[];
    final List<double> dxsUndisturbed = <double>[];
    do {
      expect(time, lessThan(4.0));
      time += delta;
      restarted = ClampingScrollSimulation(
          position: restarted.x(delta), velocity: restarted.dx(delta));
      xsRestarted.add(restarted.x(0));
      xsUndisturbed.add(undisturbed.x(time));
      dxsRestarted.add(restarted.dx(0));
      dxsUndisturbed.add(undisturbed.dx(time));
    } while (!restarted.isDone(0) || !undisturbed.isDone(time));

    // Compare the headline number first: the total distances traveled.
    // This way, if the test fails, it shows the big final difference
    // instead of the tiny difference that's in the very first frame.
    expect(xsRestarted.last, moreOrLessEquals(xsUndisturbed.last));

    // The whole trajectories along the way should match too.
    for (int i = 0; i < xsRestarted.length; i++) {
      expect(xsRestarted[i],  moreOrLessEquals(xsUndisturbed[i]));
      expect(dxsRestarted[i], moreOrLessEquals(dxsUndisturbed[i]));
    }
  });

  test('ClampingScrollSimulation satisfies a physical acceleration formula', () {
    // Different regression test for https://github.com/flutter/flutter/issues/120338
    //
    // This one provides a formula for the particle's acceleration as a function
    // of its velocity, and checks that it behaves according to that formula.
    // The point isn't that it's this specific formula, but just that there's
    // some formula which depends only on velocity, not time, so that the
    // physical metaphor makes sense.

    // Copied from the implementation.
    final double kDecelerationRate = math.log(0.78) / math.log(0.9);

    // Same as the referenceVelocity in _flingDuration.
    const double referenceVelocity = .015 * 9.80665 * 39.37 * 160.0 * 0.84 / 0.35;

    // The value of _duration when velocity == referenceVelocity.
    final double referenceDuration = kDecelerationRate * 0.35;

    // The rate of deceleration when dx(time) == referenceVelocity.
    final double referenceDeceleration = (kDecelerationRate - 1) * referenceVelocity / referenceDuration;

    double acceleration(double velocity) {
      return - velocity.sign
                 * referenceDeceleration *
                 math.pow(velocity.abs() / referenceVelocity,
                          (kDecelerationRate - 2) / (kDecelerationRate - 1));
    }

    double jerk(double velocity) {
      return referenceVelocity / referenceDuration / referenceDuration
               * (kDecelerationRate - 1) * (kDecelerationRate - 2)
               * math.pow(velocity.abs() / referenceVelocity,
                          (kDecelerationRate - 3) / (kDecelerationRate - 1));
    }

    void checkAcceleration(double position, double velocity) {
      final ClampingScrollSimulation simulation =
          ClampingScrollSimulation(position: position, velocity: velocity);
      double time = 0.0;
      const double delta = 1/60;
      for (; time < 2.0; time += delta) {
        final double difference = simulation.dx(time + delta) - simulation.dx(time);
        final double predictedDifference = delta * acceleration(simulation.dx(time + delta/2));
        final double maxThirdDerivative = jerk(simulation.dx(time + delta));
        expect((difference - predictedDifference).abs(),
            lessThan(maxThirdDerivative * math.pow(delta, 2)/2));
      }
    }

    checkAcceleration(51.0, 2866.91537);
    checkAcceleration(584.0, 2617.294734);
    checkAcceleration(345.0, 1982.785934);
    checkAcceleration(0.0, 1831.366634);
    checkAcceleration(-156.2, 1541.57665);
    checkAcceleration(4.0, 1139.250439);
    checkAcceleration(4534.0, 1073.553798);
    checkAcceleration(75.0, 614.2093);
    checkAcceleration(5469.0, 182.114534);
  });
158
}