Skip to content

Commit 1bb164a

Browse files
authored
Stabilize initial scroll simulation velocity on Android (flutter#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 flutter#8255
1 parent ae1a719 commit 1bb164a

File tree

3 files changed

+43
-27
lines changed

3 files changed

+43
-27
lines changed

packages/flutter/lib/src/widgets/scroll_simulation.dart

Lines changed: 16 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -107,52 +107,41 @@ class BouncingScrollSimulation extends SimulationGroup {
107107
// simplifications have been made.
108108
class ClampingScrollSimulation extends Simulation {
109109
/// Creates a scroll physics simulation that matches Android scrolling.
110-
//
111-
// TODO(ianh): The incoming `velocity` is used to determine the starting speed
112-
// and duration, but does not represent the exact velocity of the simulation
113-
// at t=0 as it should. This causes crazy scrolling irregularities when the
114-
// scroll dimensions change during a fling.
115110
ClampingScrollSimulation({
116111
@required this.position,
117112
@required this.velocity,
118113
this.friction: 0.015,
119114
Tolerance tolerance: Tolerance.defaultTolerance,
120115
}) : super(tolerance: tolerance) {
121-
_scaledFriction = friction * _decelerationForFriction(0.84); // See mPhysicalCoeff
116+
assert(_flingVelocityPenetration(0.0) == _kInitialVelocityPenetration);
122117
_duration = _flingDuration(velocity);
123-
_distance = _flingDistance(velocity);
118+
_distance = (velocity * _duration / _kInitialVelocityPenetration).abs();
124119
}
125120

126121
final double position;
127122
final double velocity;
128123
final double friction;
129124

130-
double _scaledFriction;
131125
double _duration;
132126
double _distance;
133127

134128
// See DECELERATION_RATE.
135-
static final double _decelerationRate = math.log(0.78) / math.log(0.9);
129+
static final double _kDecelerationRate = math.log(0.78) / math.log(0.9);
136130

137131
// See computeDeceleration().
138-
double _decelerationForFriction(double friction) {
132+
static double _decelerationForFriction(double friction) {
139133
return friction * 61774.04968;
140134
}
141135

142-
// See getSplineDeceleration().
143-
double _flingDeceleration(double velocity) {
144-
return math.log(0.35 * velocity.abs() / _scaledFriction);
145-
}
146-
147136
// See getSplineFlingDuration(). Returns a value in seconds.
148137
double _flingDuration(double velocity) {
149-
return math.exp(_flingDeceleration(velocity) / (_decelerationRate - 1.0));
150-
}
138+
// See mPhysicalCoeff
139+
final double scaledFriction = friction * _decelerationForFriction(0.84);
140+
141+
// See getSplineDeceleration().
142+
final double deceleration = math.log(0.35 * velocity.abs() / scaledFriction);
151143

152-
// See getSplineFlingDistance().
153-
double _flingDistance(double velocity) {
154-
final double rate = _decelerationRate / (_decelerationRate - 1.0) * _flingDeceleration(velocity);
155-
return _scaledFriction * math.exp(rate);
144+
return math.exp(deceleration / (_kDecelerationRate - 1.0));
156145
}
157146

158147
// Based on a cubic curve fit to the Scroller.computeScrollOffset() values
@@ -170,13 +159,14 @@ class ClampingScrollSimulation extends Simulation {
170159
// Scale f(t) so that 0.0 <= f(t) <= 1.0
171160
// f(t) = (1165.03 t^3 - 3143.62 t^2 + 2945.87 t) / 961.0
172161
// = 1.2 t^3 - 3.27 t^2 + 3.065 t
173-
double _flingDistancePenetration(double t) {
174-
return (1.2 * t * t * t) - (3.27 * t * t) + (3.065 * t);
162+
static const double _kInitialVelocityPenetration = 3.065;
163+
static double _flingDistancePenetration(double t) {
164+
return (1.2 * t * t * t) - (3.27 * t * t) + (_kInitialVelocityPenetration * t);
175165
}
176166

177167
// The derivative of the _flingDistancePenetration() function.
178-
double _flingVelocityPenetration(double t) {
179-
return (3.63693 * t * t) - (6.5424 * t) + 3.06542;
168+
static double _flingVelocityPenetration(double t) {
169+
return (3.6 * t * t) - (6.54 * t) + _kInitialVelocityPenetration;
180170
}
181171

182172
@override
@@ -188,7 +178,7 @@ class ClampingScrollSimulation extends Simulation {
188178
@override
189179
double dx(double time) {
190180
final double t = (time / _duration).clamp(0.0, 1.0);
191-
return _distance * _flingVelocityPenetration(t) * velocity.sign;
181+
return _distance * _flingVelocityPenetration(t) * velocity.sign / _duration;
192182
}
193183

194184
@override
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// Copyright 2017 The Chromium Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import 'package:flutter_test/flutter_test.dart';
6+
import 'package:flutter/widgets.dart';
7+
8+
void main() {
9+
test('ClampingScrollSimulation has a stable initial conditions', () {
10+
void checkInitialConditions(double position, double velocity) {
11+
ClampingScrollSimulation simulation = new ClampingScrollSimulation(position: position, velocity: velocity);
12+
expect(simulation.x(0.0), closeTo(position, 0.00001));
13+
expect(simulation.dx(0.0), closeTo(velocity, 0.00001));
14+
}
15+
16+
checkInitialConditions(51.0, 2866.91537);
17+
checkInitialConditions(584.0, 2617.294734);
18+
checkInitialConditions(345.0, 1982.785934);
19+
checkInitialConditions(0.0, 1831.366634);
20+
checkInitialConditions(-156.2, 1541.57665);
21+
checkInitialConditions(4.0, 1139.250439);
22+
checkInitialConditions(4534.0, 1073.553798);
23+
checkInitialConditions(75.0, 614.2093);
24+
checkInitialConditions(5469.0, 182.114534);
25+
});
26+
}

packages/flutter/test/widgets/scrollable_fling_test.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,6 @@ void main() {
9191
expect(log, equals(<String>['tap 18']));
9292
await tester.tap(find.byType(Scrollable));
9393
await tester.pump(const Duration(milliseconds: 50));
94-
expect(log, equals(<String>['tap 18', 'tap 43']));
94+
expect(log, equals(<String>['tap 18', 'tap 42']));
9595
}, skip: Platform.isMacOS); // Skip due to https://github.com/flutter/flutter/issues/6961
9696
}

0 commit comments

Comments
 (0)