Stabilize initial scroll simulation velocity on Android (#8543)

Previously, we would create a simulation whose initial velocity did not match
the requested parameters. We now compute the parameters for the simulation in a
way that ensures all the math works out.

Fixes #8255
This commit is contained in:
Adam Barth 2017-03-02 17:40:36 -08:00 committed by GitHub
parent ae1a719e03
commit 1bb164ab0d
3 changed files with 43 additions and 27 deletions

View file

@ -107,52 +107,41 @@ class BouncingScrollSimulation extends SimulationGroup {
// simplifications have been made.
class ClampingScrollSimulation extends Simulation {
/// Creates a scroll physics simulation that matches Android scrolling.
//
// TODO(ianh): The incoming `velocity` is used to determine the starting speed
// and duration, but does not represent the exact velocity of the simulation
// at t=0 as it should. This causes crazy scrolling irregularities when the
// scroll dimensions change during a fling.
ClampingScrollSimulation({
@required this.position,
@required this.velocity,
this.friction: 0.015,
Tolerance tolerance: Tolerance.defaultTolerance,
}) : super(tolerance: tolerance) {
_scaledFriction = friction * _decelerationForFriction(0.84); // See mPhysicalCoeff
assert(_flingVelocityPenetration(0.0) == _kInitialVelocityPenetration);
_duration = _flingDuration(velocity);
_distance = _flingDistance(velocity);
_distance = (velocity * _duration / _kInitialVelocityPenetration).abs();
}
final double position;
final double velocity;
final double friction;
double _scaledFriction;
double _duration;
double _distance;
// See DECELERATION_RATE.
static final double _decelerationRate = math.log(0.78) / math.log(0.9);
static final double _kDecelerationRate = math.log(0.78) / math.log(0.9);
// See computeDeceleration().
double _decelerationForFriction(double friction) {
static double _decelerationForFriction(double friction) {
return friction * 61774.04968;
}
// See getSplineDeceleration().
double _flingDeceleration(double velocity) {
return math.log(0.35 * velocity.abs() / _scaledFriction);
}
// See getSplineFlingDuration(). Returns a value in seconds.
double _flingDuration(double velocity) {
return math.exp(_flingDeceleration(velocity) / (_decelerationRate - 1.0));
}
// See mPhysicalCoeff
final double scaledFriction = friction * _decelerationForFriction(0.84);
// See getSplineFlingDistance().
double _flingDistance(double velocity) {
final double rate = _decelerationRate / (_decelerationRate - 1.0) * _flingDeceleration(velocity);
return _scaledFriction * math.exp(rate);
// See getSplineDeceleration().
final double deceleration = math.log(0.35 * velocity.abs() / scaledFriction);
return math.exp(deceleration / (_kDecelerationRate - 1.0));
}
// Based on a cubic curve fit to the Scroller.computeScrollOffset() values
@ -170,13 +159,14 @@ class ClampingScrollSimulation extends Simulation {
// Scale f(t) so that 0.0 <= f(t) <= 1.0
// f(t) = (1165.03 t^3 - 3143.62 t^2 + 2945.87 t) / 961.0
// = 1.2 t^3 - 3.27 t^2 + 3.065 t
double _flingDistancePenetration(double t) {
return (1.2 * t * t * t) - (3.27 * t * t) + (3.065 * t);
static const double _kInitialVelocityPenetration = 3.065;
static double _flingDistancePenetration(double t) {
return (1.2 * t * t * t) - (3.27 * t * t) + (_kInitialVelocityPenetration * t);
}
// The derivative of the _flingDistancePenetration() function.
double _flingVelocityPenetration(double t) {
return (3.63693 * t * t) - (6.5424 * t) + 3.06542;
static double _flingVelocityPenetration(double t) {
return (3.6 * t * t) - (6.54 * t) + _kInitialVelocityPenetration;
}
@override
@ -188,7 +178,7 @@ class ClampingScrollSimulation extends Simulation {
@override
double dx(double time) {
final double t = (time / _duration).clamp(0.0, 1.0);
return _distance * _flingVelocityPenetration(t) * velocity.sign;
return _distance * _flingVelocityPenetration(t) * velocity.sign / _duration;
}
@override

View file

@ -0,0 +1,26 @@
// Copyright 2017 The Chromium 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 'package:flutter_test/flutter_test.dart';
import 'package:flutter/widgets.dart';
void main() {
test('ClampingScrollSimulation has a stable initial conditions', () {
void checkInitialConditions(double position, double velocity) {
ClampingScrollSimulation simulation = new ClampingScrollSimulation(position: position, velocity: velocity);
expect(simulation.x(0.0), closeTo(position, 0.00001));
expect(simulation.dx(0.0), closeTo(velocity, 0.00001));
}
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);
});
}

View file

@ -91,6 +91,6 @@ void main() {
expect(log, equals(<String>['tap 18']));
await tester.tap(find.byType(Scrollable));
await tester.pump(const Duration(milliseconds: 50));
expect(log, equals(<String>['tap 18', 'tap 43']));
expect(log, equals(<String>['tap 18', 'tap 42']));
}, skip: Platform.isMacOS); // Skip due to https://github.com/flutter/flutter/issues/6961
}