| // Copyright 2014 The Flutter Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| import 'dart:math' as math; |
| |
| import 'package:flutter/widgets.dart'; |
| import 'package:flutter_test/flutter_test.dart'; |
| |
| void main() { |
| test('ClampingScrollSimulation has a stable initial conditions', () { |
| void checkInitialConditions(double position, double velocity) { |
| final ClampingScrollSimulation simulation = ClampingScrollSimulation(position: position, velocity: velocity); |
| expect(simulation.x(0.0), moreOrLessEquals(position)); |
| expect(simulation.dx(0.0), moreOrLessEquals(velocity)); |
| } |
| |
| 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); |
| }); |
| |
| 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); |
| }); |
| } |