scroll_simulation.dart 11 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 8 9
import 'package:flutter/foundation.dart';
import 'package:flutter/physics.dart';

10 11 12 13 14
/// An implementation of scroll physics that matches iOS.
///
/// See also:
///
///  * [ClampingScrollSimulation], which implements Android scroll physics.
15
class BouncingScrollSimulation extends Simulation {
16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
  /// Creates a simulation group for scrolling on iOS, with the given
  /// parameters.
  ///
  /// The position and velocity arguments must use the same units as will be
  /// expected from the [x] and [dx] methods respectively (typically logical
  /// pixels and logical pixels per second respectively).
  ///
  /// The leading and trailing extents must use the unit of length, the same
  /// unit as used for the position argument and as expected from the [x]
  /// method (typically logical pixels).
  ///
  /// The units used with the provided [SpringDescription] must similarly be
  /// consistent with the other arguments. A default set of constants is used
  /// for the `spring` description if it is omitted; these defaults assume
  /// that the unit of length is the logical pixel.
  BouncingScrollSimulation({
32 33 34 35 36
    required double position,
    required double velocity,
    required this.leadingExtent,
    required this.trailingExtent,
    required this.spring,
37
    Tolerance tolerance = Tolerance.defaultTolerance,
38 39 40 41 42 43 44
  }) : assert(position != null),
       assert(velocity != null),
       assert(leadingExtent != null),
       assert(trailingExtent != null),
       assert(leadingExtent <= trailingExtent),
       assert(spring != null),
       super(tolerance: tolerance) {
45 46
    if (position < leadingExtent) {
      _springSimulation = _underscrollSimulation(position, velocity);
47
      _springTime = double.negativeInfinity;
48 49
    } else if (position > trailingExtent) {
      _springSimulation = _overscrollSimulation(position, velocity);
50
      _springTime = double.negativeInfinity;
51
    } else {
52 53
      // Taken from UIScrollView.decelerationRate (.normal = 0.998)
      // 0.998^1000 = ~0.135
54
      _frictionSimulation = FrictionSimulation(0.135, position, velocity);
55 56 57
      final double finalX = _frictionSimulation.finalX;
      if (velocity > 0.0 && finalX > trailingExtent) {
        _springTime = _frictionSimulation.timeAtX(trailingExtent);
58 59 60 61
        _springSimulation = _overscrollSimulation(
          trailingExtent,
          math.min(_frictionSimulation.dx(_springTime), maxSpringTransferVelocity),
        );
62 63 64
        assert(_springTime.isFinite);
      } else if (velocity < 0.0 && finalX < leadingExtent) {
        _springTime = _frictionSimulation.timeAtX(leadingExtent);
65 66 67 68
        _springSimulation = _underscrollSimulation(
          leadingExtent,
          math.min(_frictionSimulation.dx(_springTime), maxSpringTransferVelocity),
        );
69 70
        assert(_springTime.isFinite);
      } else {
71
        _springTime = double.infinity;
72 73 74
      }
    }
    assert(_springTime != null);
75
  }
76

77
  /// The maximum velocity that can be transferred from the inertia of a ballistic
78 79 80
  /// scroll into overscroll.
  static const double maxSpringTransferVelocity = 5000.0;

81 82 83 84 85 86 87
  /// When [x] falls below this value the simulation switches from an internal friction
  /// model to a spring model which causes [x] to "spring" back to [leadingExtent].
  final double leadingExtent;

  /// When [x] exceeds this value the simulation switches from an internal friction
  /// model to a spring model which causes [x] to "spring" back to [trailingExtent].
  final double trailingExtent;
88

nt4f04uNd's avatar
nt4f04uNd committed
89
  /// The spring used to return [x] to either [leadingExtent] or [trailingExtent].
90 91
  final SpringDescription spring;

92 93 94
  late FrictionSimulation _frictionSimulation;
  late Simulation _springSimulation;
  late double _springTime;
95 96 97
  double _timeOffset = 0.0;

  Simulation _underscrollSimulation(double x, double dx) {
98
    return ScrollSpringSimulation(spring, x, leadingExtent, dx);
99 100 101
  }

  Simulation _overscrollSimulation(double x, double dx) {
102
    return ScrollSpringSimulation(spring, x, trailingExtent, dx);
103 104 105
  }

  Simulation _simulation(double time) {
106
    final Simulation simulation;
107
    if (time > _springTime) {
108 109 110 111 112 113 114 115
      _timeOffset = _springTime.isFinite ? _springTime : 0.0;
      simulation = _springSimulation;
    } else {
      _timeOffset = 0.0;
      simulation = _frictionSimulation;
    }
    return simulation..tolerance = tolerance;
  }
116 117

  @override
118
  double x(double time) => _simulation(time).x(time - _timeOffset);
119 120

  @override
121
  double dx(double time) => _simulation(time).dx(time - _timeOffset);
122 123

  @override
124
  bool isDone(double time) => _simulation(time).isDone(time - _timeOffset);
125 126 127

  @override
  String toString() {
128
    return '${objectRuntimeType(this, 'BouncingScrollSimulation')}(leadingExtent: $leadingExtent, trailingExtent: $trailingExtent)';
129 130 131
  }
}

132 133
const double _inflexion = 0.35;

134 135 136 137 138 139 140 141 142 143 144 145 146 147
/// An implementation of scroll physics that matches Android.
///
/// See also:
///
///  * [BouncingScrollSimulation], which implements iOS scroll physics.
//
// This class is based on Scroller.java from Android:
//   https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/widget
//
// The "See..." comments below refer to Scroller methods and values. Some
// simplifications have been made.
class ClampingScrollSimulation extends Simulation {
  /// Creates a scroll physics simulation that matches Android scrolling.
  ClampingScrollSimulation({
148 149
    required this.position,
    required this.velocity,
150 151
    this.friction = 0.015,
    Tolerance tolerance = Tolerance.defaultTolerance,
152 153 154
  }) : super(tolerance: tolerance) {
    _duration = _splineFlingDuration(velocity);
    _distance = _splineFlingDistance(velocity);
155 156
  }

157
  /// The position of the particle at the beginning of the simulation.
158
  final double position;
159 160 161

  /// The velocity at which the particle is traveling at the beginning of the
  /// simulation.
162
  final double velocity;
163 164 165 166

  /// The amount of friction the particle experiences as it travels.
  ///
  /// The more friction the particle experiences, the sooner it stops.
167 168
  final double friction;

169
  late int _duration;
170
  late double _distance;
171

172
  // See DECELERATION_RATE.
173
  static final double _kDecelerationRate = math.log(0.78) / math.log(0.9);
174 175

  // See computeDeceleration().
176
  static double _decelerationForFriction(double friction) {
177 178 179 180 181
    return 9.80665 *
        39.37 *
        friction *
        1.0 * // Flutter operates on logical pixels so the DPI should be 1.0.
        160.0;
182
  }
183

184 185
  // See getSplineDeceleration().
  double _splineDeceleration(double velocity) {
186
    return math.log(_inflexion * velocity.abs() / (friction * _decelerationForFriction(0.84)));
187 188
  }

189 190 191 192
  // See getSplineFlingDuration().
  int _splineFlingDuration(double velocity) {
    final double deceleration = _splineDeceleration(velocity);
    return (1000 * math.exp(deceleration / (_kDecelerationRate - 1.0))).round();
193 194
  }

195 196 197 198 199 200 201
  // See getSplineFlingDistance().
  double _splineFlingDistance(double velocity) {
    final double l = _splineDeceleration(velocity);
    final double decelMinusOne = _kDecelerationRate - 1.0;
    return friction *
        _decelerationForFriction(0.84) *
        math.exp(_kDecelerationRate / decelMinusOne * l);
202 203 204 205
  }

  @override
  double x(double time) {
206 207 208 209 210
    if (time == 0) {
      return position;
    }
    final _NBSample sample = _NBSample(time, _duration);
    return position + (sample.distanceCoef * _distance) * velocity.sign;
211 212 213 214
  }

  @override
  double dx(double time) {
215 216 217 218 219
    if (time == 0) {
      return velocity;
    }
    final _NBSample sample = _NBSample(time, _duration);
    return sample.velocityCoef * _distance / _duration * velocity.sign * 1000.0;
220 221 222 223
  }

  @override
  bool isDone(double time) {
224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242
    return time * 1000.0 >= _duration;
  }
}

class _NBSample {
  _NBSample(double time, int duration) {
    // See computeScrollOffset().
    final double t = time * 1000.0 / duration;
    final int index = (_nbSamples * t).clamp(0, _nbSamples).round();
    _distanceCoef = 1.0;
    _velocityCoef = 0.0;
    if (index < _nbSamples) {
      final double tInf = index / _nbSamples;
      final double tSup = (index + 1) / _nbSamples;
      final double dInf = _splinePosition[index];
      final double dSup = _splinePosition[index + 1];
      _velocityCoef = (dSup - dInf) / (tSup - tInf);
      _distanceCoef = dInf + (t - tInf) * _velocityCoef;
    }
243
  }
244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354

  late double _velocityCoef;
  double get velocityCoef => _velocityCoef;

  late double _distanceCoef;
  double get distanceCoef => _distanceCoef;

  static const int _nbSamples = 100;

  // Generated from dev/tools/generate_android_spline_data.dart.
  static final List<double> _splinePosition = <double>[
    0.000022888183591973643,
    0.028561000304762274,
    0.05705195792956655,
    0.08538917797618413,
    0.11349556286812107,
    0.14129881694635613,
    0.16877157254923383,
    0.19581093511175632,
    0.22239649722992452,
    0.24843841866631658,
    0.2740024733220569,
    0.298967680744136,
    0.32333234658228116,
    0.34709556909569184,
    0.3702249257894571,
    0.39272483400399893,
    0.41456988647721615,
    0.43582889025419114,
    0.4564192786416,
    0.476410299013587,
    0.4957560715637827,
    0.5145493169954743,
    0.5327205670880077,
    0.5502846891191615,
    0.5673274324802855,
    0.583810881323224,
    0.5997478744397482,
    0.615194045299478,
    0.6301165005270208,
    0.6445484042257972,
    0.6585198219185201,
    0.6720397744233084,
    0.6850997688076114,
    0.6977281404741683,
    0.7099506591298411,
    0.7217749311525871,
    0.7331784038850426,
    0.7442308394229518,
    0.7549087205105974,
    0.7652471277371271,
    0.7752251637549381,
    0.7848768260203478,
    0.7942056937103814,
    0.8032299679689082,
    0.8119428702388629,
    0.8203713516576219,
    0.8285187880808974,
    0.8363794492831295,
    0.8439768562813565,
    0.851322799855549,
    0.8584111051351724,
    0.8652534074722162,
    0.8718525580962131,
    0.8782333271742155,
    0.8843892099362031,
    0.8903155590440985,
    0.8960465359221951,
    0.9015574505919048,
    0.9068736766459904,
    0.9119951682409297,
    0.9169321898723632,
    0.9216747065581234,
    0.9262420604674766,
    0.9306331858366086,
    0.9348476990715433,
    0.9389007110754832,
    0.9427903495057521,
    0.9465220679845756,
    0.9500943036519721,
    0.9535176728088761,
    0.9567898524767604,
    0.959924306623116,
    0.9629127700159108,
    0.9657622101750765,
    0.9684818726275105,
    0.9710676079044347,
    0.9735231939498,
    0.9758514437576309,
    0.9780599066560445,
    0.9801485715370128,
    0.9821149805689633,
    0.9839677526782791,
    0.9857085499421516,
    0.9873347811966005,
    0.9888547171706613,
    0.9902689443512227,
    0.9915771042095881,
    0.9927840651641069,
    0.9938913963715834,
    0.9948987305580712,
    0.9958114963810524,
    0.9966274782266875,
    0.997352148697352,
    0.9979848677523623,
    0.9985285021374979,
    0.9989844084453229,
    0.9993537595844986,
    0.999638729860106,
    0.9998403888004533,
    0.9999602810470701,
355
    1.0,
356
  ];
357
}