Commit 9eef8535 authored by Hans Muller's avatar Hans Muller Committed by GitHub

Update Android scrolling physicsAndroid physics (#6391)

parent 2cfc0405
......@@ -2,15 +2,99 @@
// 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/foundation.dart';
import 'package:flutter/physics.dart';
final SpringDescription _kScrollSpring = new SpringDescription.withDampingRatio(mass: 0.5, springConstant: 100.0, ratio: 1.1);
final double _kDrag = 0.025;
// This class is based on Scroller.java from
// https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/widget
// The "See" comments refer to Scroller methods and values. Some simplifications
// have been made.
class _MountainViewSimulation extends Simulation {
_MountainViewSimulation({
this.position,
this.velocity,
this.friction: 0.015,
}) {
_scaledFriction = friction * _decelerationForFriction(0.84); // See mPhysicalCoeff
_duration = _flingDuration(velocity);
_distance = _flingDistance(velocity);
}
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);
// See computeDeceleration().
double _decelerationForFriction(double friction) {
return friction * 61774.04968;
}
class _MountainViewSimulation extends FrictionSimulation {
static const double drag = 0.025;
_MountainViewSimulation({ double position, double velocity })
: super(drag, position, velocity);
// 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 getSplineFlingDistance()
double _flingDistance(double velocity) {
final double rate = _decelerationRate / (_decelerationRate - 1.0) * _flingDeceleration(velocity);
return _scaledFriction * math.exp(rate);
}
// Based on a cubic curve fit to the computeScrollOffset() values produced
// for an initial velocity of 4000. The value of scroller.getDuration()
// and scroller.getFinalY() were 686ms and 961 pixels respectively.
// Algebra courtesy of Wolfram Alpha.
//
// f(x) = scrollOffset, x is time in millseconds
// f(x) = 3.60882×10^-6 x^3 - 0.00668009 x^2 + 4.29427 x - 3.15307
// f(x) = 3.60882×10^-6 x^3 - 0.00668009 x^2 + 4.29427 x, so f(0) is 0
// f(686ms) = 961 pixels
// Scale to f(0 <= t <= 1.0), x = t * 686
// f(t) = 1165.03 t^3 - 3143.62 t^2 + 2945.87 t
// 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);
}
// The deriviate of the _flingPenetration() function.
double _flingVelocityPenetration(double t) {
return (3.63693 * t * t) - (6.5424 * t) + 3.06542;
}
@override
double x(double time) {
final double t = (time / _duration).clamp(0.0, 1.0);
return position + _distance * _flingDistancePenetration(t) * velocity.sign;
}
@override
double dx(double time) {
final double t = (time / _duration).clamp(0.0, 1.0);
return velocity * _flingVelocityPenetration(t);
}
@override
bool isDone(double time) {
return time >= _duration;
}
}
class _CupertinoSimulation extends FrictionSimulation {
......@@ -49,7 +133,7 @@ class ScrollSimulation extends SimulationGroup {
}) : _leadingExtent = leadingExtent,
_trailingExtent = trailingExtent,
_spring = spring ?? _kScrollSpring,
_drag = drag,
_drag = drag ?? _kDrag,
_platform = platform {
assert(_leadingExtent != null);
assert(_trailingExtent != null);
......@@ -103,7 +187,7 @@ class ScrollSimulation extends SimulationGroup {
case TargetPlatform.fuchsia:
_currentSimulation = new _MountainViewSimulation(
position: position,
velocity: velocity
velocity: velocity,
);
break;
case TargetPlatform.iOS:
......
......@@ -101,6 +101,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 48']));
expect(log, equals(<String>['tap 18', 'tap 43']));
});
}
......@@ -23,17 +23,35 @@ double snapOffsetCallback(double offset, Size size) {
return (offset / itemExtent).floor() * itemExtent;
}
class TestScrollConfigurationDelegate extends ScrollConfigurationDelegate {
const TestScrollConfigurationDelegate();
// Not testing platform-specific fling scrolling, use the default fling
// decleration simulation.
@override
TargetPlatform get platform => null;
@override
ExtentScrollBehavior createScrollBehavior() => new OverscrollWhenScrollableBehavior(platform: platform);
@override
bool updateShouldNotify(ScrollConfigurationDelegate old) => false;
}
Widget buildFrame() {
scrollableListKey = new GlobalKey();
return new Center(
child: new Container(
height: itemExtent * 2.0,
child: new ScrollableList(
scrollableKey: scrollableListKey,
snapOffsetCallback: snapOffsetCallback,
scrollDirection: scrollDirection,
itemExtent: itemExtent,
children: <int>[0, 1, 2, 3, 4, 5, 7, 8, 9].map(buildItem)
return new ScrollConfiguration(
delegate: const TestScrollConfigurationDelegate(),
child: new Center(
child: new Container(
height: itemExtent * 2.0,
child: new ScrollableList(
scrollableKey: scrollableListKey,
snapOffsetCallback: snapOffsetCallback,
scrollDirection: scrollDirection,
itemExtent: itemExtent,
children: <int>[0, 1, 2, 3, 4, 5, 7, 8, 9].map(buildItem)
)
)
)
);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment