scroll_metrics.dart 6.56 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
2 3 4 5 6 7 8 9
// 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/rendering.dart';

10
/// A description of a [Scrollable]'s contents, useful for modeling the state
11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
/// of its viewport.
///
/// This class defines a current position, [pixels], and a range of values
/// considered "in bounds" for that position. The range has a minimum value at
/// [minScrollExtent] and a maximum value at [maxScrollExtent] (inclusive). The
/// viewport scrolls in the direction and axis described by [axisDirection]
/// and [axis].
///
/// The [outOfRange] getter will return true if [pixels] is outside this defined
/// range. The [atEdge] getter will return true if the [pixels] position equals
/// either the [minScrollExtent] or the [maxScrollExtent].
///
/// The dimensions of the viewport in the given [axis] are described by
/// [viewportDimension].
///
/// The above values are also exposed in terms of [extentBefore],
/// [extentInside], and [extentAfter], which may be more useful for use cases
/// such as scroll bars; for example, see [Scrollbar].
29 30 31 32 33
///
/// See also:
///
///  * [FixedScrollMetrics], which is an immutable object that implements this
///    interface.
34
mixin ScrollMetrics {
35 36 37 38
  /// Creates a [ScrollMetrics] that has the same properties as this object.
  ///
  /// This is useful if this object is mutable, but you want to get a snapshot
  /// of the current state.
39 40 41 42 43
  ///
  /// The named arguments allow the values to be adjusted in the process. This
  /// is useful to examine hypothetical situations, for example "would applying
  /// this delta unmodified take the position [outOfRange]?".
  ScrollMetrics copyWith({
44 45 46 47 48
    double? minScrollExtent,
    double? maxScrollExtent,
    double? pixels,
    double? viewportDimension,
    AxisDirection? axisDirection,
49
  }) {
50
    return FixedScrollMetrics(
51 52 53 54
      minScrollExtent: minScrollExtent ?? (hasContentDimensions ? this.minScrollExtent : null),
      maxScrollExtent: maxScrollExtent ?? (hasContentDimensions ? this.maxScrollExtent : null),
      pixels: pixels ?? (hasPixels ? this.pixels : null),
      viewportDimension: viewportDimension ?? (hasViewportDimension ? this.viewportDimension : null),
55 56 57
      axisDirection: axisDirection ?? this.axisDirection,
    );
  }
58

59 60 61
  /// The minimum in-range value for [pixels].
  ///
  /// The actual [pixels] value might be [outOfRange].
62
  ///
63 64
  /// This value should typically be non-null and less than or equal to
  /// [maxScrollExtent]. It can be negative infinity, if the scroll is unbounded.
65
  double get minScrollExtent;
66 67 68 69

  /// The maximum in-range value for [pixels].
  ///
  /// The actual [pixels] value might be [outOfRange].
70
  ///
71 72
  /// This value should typically be non-null and greater than or equal to
  /// [minScrollExtent]. It can be infinity, if the scroll is unbounded.
73
  double get maxScrollExtent;
74

75 76 77
  /// Whether the [minScrollExtent] and the [maxScrollExtent] properties are available.
  bool get hasContentDimensions;

78
  /// The current scroll position, in logical pixels along the [axisDirection].
79
  double get pixels;
80

81 82 83
  /// Whether the [pixels] property is available.
  bool get hasPixels;

84
  /// The extent of the viewport along the [axisDirection].
85
  double get viewportDimension;
86

87 88 89
  /// Whether the [viewportDimension] property is available.
  bool get hasViewportDimension;

90
  /// The direction in which the scroll view scrolls.
91 92
  AxisDirection get axisDirection;

93
  /// The axis in which the scroll view scrolls.
94 95
  Axis get axis => axisDirectionToAxis(axisDirection);

96 97
  /// Whether the [pixels] value is outside the [minScrollExtent] and
  /// [maxScrollExtent].
98 99
  bool get outOfRange => pixels < minScrollExtent || pixels > maxScrollExtent;

100 101
  /// Whether the [pixels] value is exactly at the [minScrollExtent] or the
  /// [maxScrollExtent].
102 103
  bool get atEdge => pixels == minScrollExtent || pixels == maxScrollExtent;

104 105
  /// The quantity of content conceptually "above" the viewport in the scrollable.
  /// This is the content above the content described by [extentInside].
106 107
  double get extentBefore => math.max(pixels - minScrollExtent, 0.0);

108
  /// The quantity of content conceptually "inside" the viewport in the scrollable.
109
  ///
110 111 112 113 114
  /// The value is typically the height of the viewport when [outOfRange] is false.
  /// It could be less if there is less content visible than the size of the
  /// viewport, such as when overscrolling.
  ///
  /// The value is always non-negative, and less than or equal to [viewportDimension].
115
  double get extentInside {
116
    assert(minScrollExtent <= maxScrollExtent);
117 118 119 120 121
    return viewportDimension
      // "above" overscroll value
      - (minScrollExtent - pixels).clamp(0, viewportDimension)
      // "below" overscroll value
      - (pixels - maxScrollExtent).clamp(0, viewportDimension);
122 123
  }

124 125
  /// The quantity of content conceptually "below" the viewport in the scrollable.
  /// This is the content below the content described by [extentInside].
126 127 128
  double get extentAfter => math.max(maxScrollExtent - pixels, 0.0);
}

129 130 131
/// An immutable snapshot of values associated with a [Scrollable] viewport.
///
/// For details, see [ScrollMetrics], which defines this object's interfaces.
132
class FixedScrollMetrics with ScrollMetrics {
133
  /// Creates an immutable snapshot of values associated with a [Scrollable] viewport.
134
  FixedScrollMetrics({
135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154
    required double? minScrollExtent,
    required double? maxScrollExtent,
    required double? pixels,
    required double? viewportDimension,
    required this.axisDirection,
  }) : _minScrollExtent = minScrollExtent,
       _maxScrollExtent = maxScrollExtent,
       _pixels = pixels,
       _viewportDimension = viewportDimension;

  @override
  double get minScrollExtent => _minScrollExtent!;
  final double? _minScrollExtent;

  @override
  double get maxScrollExtent => _maxScrollExtent!;
  final double? _maxScrollExtent;

  @override
  bool get hasContentDimensions => _minScrollExtent != null && _maxScrollExtent != null;
155 156

  @override
157 158
  double get pixels => _pixels!;
  final double? _pixels;
159 160

  @override
161
  bool get hasPixels => _pixels != null;
162 163

  @override
164 165
  double get viewportDimension => _viewportDimension!;
  final double? _viewportDimension;
166 167

  @override
168
  bool get hasViewportDimension => _viewportDimension != null;
169 170 171

  @override
  final AxisDirection axisDirection;
172 173 174

  @override
  String toString() {
175
    return '${objectRuntimeType(this, 'FixedScrollMetrics')}(${extentBefore.toStringAsFixed(1)}..[${extentInside.toStringAsFixed(1)}]..${extentAfter.toStringAsFixed(1)})';
176
  }
177
}