scroll_physics.dart 6.43 KB
Newer Older
1 2 3 4 5 6 7 8 9 10
// Copyright 2015 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 'dart:math' as math;

import 'package:flutter/physics.dart';

import 'overscroll_indicator.dart';
import 'scroll_position.dart';
11
import 'scroll_simulation.dart';
12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29

// The ScrollPhysics base class is defined in scroll_position.dart because it
// has as circular dependency with ScrollPosition.
export 'scroll_position.dart' show ScrollPhysics;

/// Scroll physics for environments that allow the scroll offset to go beyond
/// the bounds of the content, but then bounce the content back to the edge of
/// those bounds.
///
/// This is the behavior typically seen on iOS.
///
/// See also:
///
/// * [ViewportScrollBehavior], which uses this to provide the iOS component of
///   its scroll behavior.
/// * [ClampingScrollPhysics], which is the analogous physics for Android's
///   clamping behavior.
class BouncingScrollPhysics extends ScrollPhysics {
30
  /// Creates scroll physics that bounce back from the edge.
31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
  const BouncingScrollPhysics({ ScrollPhysics parent }) : super(parent);

  @override
  BouncingScrollPhysics applyTo(ScrollPhysics parent) => new BouncingScrollPhysics(parent: parent);

  /// The multiple applied to overscroll to make it appear that scrolling past
  /// the edge of the scrollable contents is harder than scrolling the list.
  ///
  /// By default this is 0.5, meaning that overscroll is twice as hard as normal
  /// scroll.
  double get frictionFactor => 0.5;

  @override
  double applyPhysicsToUserOffset(ScrollPosition position, double offset) {
    assert(offset != 0.0);
    assert(position.minScrollExtent <= position.maxScrollExtent);
    if (offset > 0.0)
      return _applyFriction(position.pixels, position.minScrollExtent, position.maxScrollExtent, offset, frictionFactor);
    return -_applyFriction(-position.pixels, -position.maxScrollExtent, -position.minScrollExtent, -offset, frictionFactor);
  }

  static double _applyFriction(double start, double lowLimit, double highLimit, double delta, double gamma) {
    assert(lowLimit <= highLimit);
    assert(delta > 0.0);
    double total = 0.0;
    if (start < lowLimit) {
57 58
      final double distanceToLimit = lowLimit - start;
      final double deltaToLimit = distanceToLimit / gamma;
59 60 61 62 63 64 65 66
      if (delta < deltaToLimit)
        return total + delta * gamma;
      total += distanceToLimit;
      delta -= deltaToLimit;
    }
    return total + delta;
  }

67 68 69
  @override
  double applyBoundaryConditions(ScrollPosition position, double value) => 0.0;

70 71 72 73 74 75 76
  @override
  Simulation createBallisticSimulation(ScrollPosition position, double velocity) {
    final Tolerance tolerance = this.tolerance;
    if (velocity.abs() >= tolerance.velocity || position.outOfRange) {
      return new BouncingScrollSimulation(
        spring: spring,
        position: position.pixels,
77
        velocity: velocity * 0.91, // TODO(abarth): We should move this constant closer to the drag end.
78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100
        leadingExtent: position.minScrollExtent,
        trailingExtent: position.maxScrollExtent,
      )..tolerance = tolerance;
    }
    return null;
  }
}

/// Scroll physics for environments that prevent the scroll offset from reaching
/// beyond the bounds of the content.
///
/// This is the behavior typically seen on Android.
///
/// See also:
///
/// * [ViewportScrollBehavior], which uses this to provide the Android component
///   of its scroll behavior.
/// * [BouncingScrollPhysics], which is the analogous physics for iOS' bouncing
///   behavior.
/// * [GlowingOverscrollIndicator], which is used by [ViewportScrollBehavior] to
///   provide the glowing effect that is usually found with this clamping effect
///   on Android.
class ClampingScrollPhysics extends ScrollPhysics {
101 102
  /// Creates scroll physics that prevent the scroll offset from exceeding the
  /// bounds of the content..
103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150
  const ClampingScrollPhysics({ ScrollPhysics parent }) : super(parent);

  @override
  ClampingScrollPhysics applyTo(ScrollPhysics parent) => new ClampingScrollPhysics(parent: parent);

  @override
  double applyBoundaryConditions(ScrollPosition position, double value) {
    assert(value != position.pixels);
    if (value < position.pixels && position.pixels <= position.minScrollExtent) // underscroll
      return value - position.pixels;
    if (position.maxScrollExtent <= position.pixels && position.pixels < value) // overscroll
      return value - position.pixels;
    if (value < position.minScrollExtent && position.minScrollExtent < position.pixels) // hit top edge
      return value - position.minScrollExtent;
    if (position.pixels < position.maxScrollExtent && position.maxScrollExtent < value) // hit bottom edge
      return value - position.maxScrollExtent;
    return 0.0;
  }

  @override
  Simulation createBallisticSimulation(ScrollPosition position, double velocity) {
    final Tolerance tolerance = this.tolerance;
    if (position.outOfRange) {
      double end;
      if (position.pixels > position.maxScrollExtent)
        end = position.maxScrollExtent;
      if (position.pixels < position.minScrollExtent)
        end = position.minScrollExtent;
      assert(end != null);
      return new ScrollSpringSimulation(
        spring,
        position.pixels,
        position.maxScrollExtent,
        math.min(0.0, velocity),
        tolerance: tolerance
      );
    }
    if (!position.atEdge && velocity.abs() >= tolerance.velocity) {
      return new ClampingScrollSimulation(
        position: position.pixels,
        velocity: velocity,
        tolerance: tolerance,
      );
    }
    return null;
  }
}

151 152 153 154 155 156 157 158 159 160 161 162 163
/// Scroll physics that always lets the user scroll.
///
/// On Android, overscrolls will be clamped by default and result in an
/// overscroll glow. On iOS, overscrolls will load a spring that will return
/// the scroll view to its normal range when released.
///
/// See also:
///
/// * [BouncingScrollPhysics], which provides the bouncing overscroll behavior
///   found on iOS.
/// * [ClampingScrollPhysics], which provides the clamping overscroll behavior
///   found on Android.
class AlwaysScrollableScrollPhysics extends ScrollPhysics {
164
  /// Creates scroll physics that always lets the user scroll.
165 166 167 168 169 170 171 172
  const AlwaysScrollableScrollPhysics({ ScrollPhysics parent }) : super(parent);

  @override
  AlwaysScrollableScrollPhysics applyTo(ScrollPhysics parent) => new AlwaysScrollableScrollPhysics(parent: parent);

  @override
  bool shouldAcceptUserOffset(ScrollPosition position) => true;
}