| 1 | // Copyright 2014 The Flutter 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 | /// @docImport 'dart:ui'; |
| 6 | /// @docImport 'package:flutter/material.dart'; |
| 7 | /// |
| 8 | /// @docImport 'scroll_position.dart'; |
| 9 | /// @docImport 'scrollable.dart'; |
| 10 | /// @docImport 'viewport.dart'; |
| 11 | library; |
| 12 | |
| 13 | import 'dart:math' as math; |
| 14 | |
| 15 | import 'package:flutter/foundation.dart'; |
| 16 | import 'package:flutter/rendering.dart'; |
| 17 | |
| 18 | /// A description of a [Scrollable]'s contents, useful for modeling the state |
| 19 | /// of its viewport. |
| 20 | /// |
| 21 | /// This class defines a current position, [pixels], and a range of values |
| 22 | /// considered "in bounds" for that position. The range has a minimum value at |
| 23 | /// [minScrollExtent] and a maximum value at [maxScrollExtent] (inclusive). The |
| 24 | /// viewport scrolls in the direction and axis described by [axisDirection] |
| 25 | /// and [axis]. |
| 26 | /// |
| 27 | /// The [outOfRange] getter will return true if [pixels] is outside this defined |
| 28 | /// range. The [atEdge] getter will return true if the [pixels] position equals |
| 29 | /// either the [minScrollExtent] or the [maxScrollExtent]. |
| 30 | /// |
| 31 | /// The dimensions of the viewport in the given [axis] are described by |
| 32 | /// [viewportDimension]. |
| 33 | /// |
| 34 | /// The above values are also exposed in terms of [extentBefore], |
| 35 | /// [extentInside], and [extentAfter], which may be more useful for use cases |
| 36 | /// such as scroll bars; for example, see [Scrollbar]. |
| 37 | /// |
| 38 | /// {@tool dartpad} |
| 39 | /// This sample shows how a [ScrollMetricsNotification] is dispatched when |
| 40 | /// the [ScrollMetrics] changed as a result of resizing the [Viewport]. |
| 41 | /// Press the floating action button to increase the scrollable window's size. |
| 42 | /// |
| 43 | /// ** See code in examples/api/lib/widgets/scroll_position/scroll_metrics_notification.0.dart ** |
| 44 | /// {@end-tool} |
| 45 | /// |
| 46 | /// See also: |
| 47 | /// |
| 48 | /// * [FixedScrollMetrics], which is an immutable object that implements this |
| 49 | /// interface. |
| 50 | mixin ScrollMetrics { |
| 51 | /// Creates a [ScrollMetrics] that has the same properties as this object. |
| 52 | /// |
| 53 | /// This is useful if this object is mutable, but you want to get a snapshot |
| 54 | /// of the current state. |
| 55 | /// |
| 56 | /// The named arguments allow the values to be adjusted in the process. This |
| 57 | /// is useful to examine hypothetical situations, for example "would applying |
| 58 | /// this delta unmodified take the position [outOfRange]?". |
| 59 | ScrollMetrics copyWith({ |
| 60 | double? minScrollExtent, |
| 61 | double? maxScrollExtent, |
| 62 | double? pixels, |
| 63 | double? viewportDimension, |
| 64 | AxisDirection? axisDirection, |
| 65 | double? devicePixelRatio, |
| 66 | }) { |
| 67 | return FixedScrollMetrics( |
| 68 | minScrollExtent: minScrollExtent ?? (hasContentDimensions ? this.minScrollExtent : null), |
| 69 | maxScrollExtent: maxScrollExtent ?? (hasContentDimensions ? this.maxScrollExtent : null), |
| 70 | pixels: pixels ?? (hasPixels ? this.pixels : null), |
| 71 | viewportDimension: |
| 72 | viewportDimension ?? (hasViewportDimension ? this.viewportDimension : null), |
| 73 | axisDirection: axisDirection ?? this.axisDirection, |
| 74 | devicePixelRatio: devicePixelRatio ?? this.devicePixelRatio, |
| 75 | ); |
| 76 | } |
| 77 | |
| 78 | /// The minimum in-range value for [pixels]. |
| 79 | /// |
| 80 | /// The actual [pixels] value might be [outOfRange]. |
| 81 | /// |
| 82 | /// This value is typically less than or equal to |
| 83 | /// [maxScrollExtent]. It can be negative infinity, if the scroll is unbounded. |
| 84 | double get minScrollExtent; |
| 85 | |
| 86 | /// The maximum in-range value for [pixels]. |
| 87 | /// |
| 88 | /// The actual [pixels] value might be [outOfRange]. |
| 89 | /// |
| 90 | /// This value is typically greater than or equal to |
| 91 | /// [minScrollExtent]. It can be infinity, if the scroll is unbounded. |
| 92 | double get maxScrollExtent; |
| 93 | |
| 94 | /// Whether the [minScrollExtent] and the [maxScrollExtent] properties are available. |
| 95 | bool get hasContentDimensions; |
| 96 | |
| 97 | /// The current scroll position, in logical pixels along the [axisDirection]. |
| 98 | double get pixels; |
| 99 | |
| 100 | /// Whether the [pixels] property is available. |
| 101 | bool get hasPixels; |
| 102 | |
| 103 | /// The extent of the viewport along the [axisDirection]. |
| 104 | double get viewportDimension; |
| 105 | |
| 106 | /// Whether the [viewportDimension] property is available. |
| 107 | bool get hasViewportDimension; |
| 108 | |
| 109 | /// The direction in which the scroll view scrolls. |
| 110 | AxisDirection get axisDirection; |
| 111 | |
| 112 | /// The axis in which the scroll view scrolls. |
| 113 | Axis get axis => axisDirectionToAxis(axisDirection); |
| 114 | |
| 115 | /// Whether the [pixels] value is outside the [minScrollExtent] and |
| 116 | /// [maxScrollExtent]. |
| 117 | bool get outOfRange => pixels < minScrollExtent || pixels > maxScrollExtent; |
| 118 | |
| 119 | /// Whether the [pixels] value is exactly at the [minScrollExtent] or the |
| 120 | /// [maxScrollExtent]. |
| 121 | bool get atEdge => pixels == minScrollExtent || pixels == maxScrollExtent; |
| 122 | |
| 123 | /// The quantity of content conceptually "above" the viewport in the scrollable. |
| 124 | /// This is the content above the content described by [extentInside]. |
| 125 | double get extentBefore => math.max(pixels - minScrollExtent, 0.0); |
| 126 | |
| 127 | /// The quantity of content conceptually "inside" the viewport in the |
| 128 | /// scrollable (including empty space if the total amount of content is less |
| 129 | /// than the [viewportDimension]). |
| 130 | /// |
| 131 | /// The value is typically the extent of the viewport ([viewportDimension]) |
| 132 | /// when [outOfRange] is false. It can be less when overscrolling. |
| 133 | /// |
| 134 | /// The value is always non-negative, and less than or equal to [viewportDimension]. |
| 135 | double get extentInside { |
| 136 | assert(minScrollExtent <= maxScrollExtent); |
| 137 | return viewportDimension |
| 138 | // "above" overscroll value |
| 139 | - |
| 140 | clampDouble(minScrollExtent - pixels, 0, viewportDimension) |
| 141 | // "below" overscroll value |
| 142 | - |
| 143 | clampDouble(pixels - maxScrollExtent, 0, viewportDimension); |
| 144 | } |
| 145 | |
| 146 | /// The quantity of content conceptually "below" the viewport in the scrollable. |
| 147 | /// This is the content below the content described by [extentInside]. |
| 148 | double get extentAfter => math.max(maxScrollExtent - pixels, 0.0); |
| 149 | |
| 150 | /// The total quantity of content available. |
| 151 | /// |
| 152 | /// This is the sum of [extentBefore], [extentInside], and [extentAfter], modulo |
| 153 | /// any rounding errors. |
| 154 | double get extentTotal => maxScrollExtent - minScrollExtent + viewportDimension; |
| 155 | |
| 156 | /// The [FlutterView.devicePixelRatio] of the view that the [Scrollable] |
| 157 | /// associated with this metrics object is drawn into. |
| 158 | double get devicePixelRatio; |
| 159 | } |
| 160 | |
| 161 | /// An immutable snapshot of values associated with a [Scrollable] viewport. |
| 162 | /// |
| 163 | /// For details, see [ScrollMetrics], which defines this object's interfaces. |
| 164 | /// |
| 165 | /// {@tool dartpad} |
| 166 | /// This sample shows how a [ScrollMetricsNotification] is dispatched when |
| 167 | /// the [ScrollMetrics] changed as a result of resizing the [Viewport]. |
| 168 | /// Press the floating action button to increase the scrollable window's size. |
| 169 | /// |
| 170 | /// ** See code in examples/api/lib/widgets/scroll_position/scroll_metrics_notification.0.dart ** |
| 171 | /// {@end-tool} |
| 172 | class FixedScrollMetrics with ScrollMetrics { |
| 173 | /// Creates an immutable snapshot of values associated with a [Scrollable] viewport. |
| 174 | FixedScrollMetrics({ |
| 175 | required double? minScrollExtent, |
| 176 | required double? maxScrollExtent, |
| 177 | required double? pixels, |
| 178 | required double? viewportDimension, |
| 179 | required this.axisDirection, |
| 180 | required this.devicePixelRatio, |
| 181 | }) : _minScrollExtent = minScrollExtent, |
| 182 | _maxScrollExtent = maxScrollExtent, |
| 183 | _pixels = pixels, |
| 184 | _viewportDimension = viewportDimension; |
| 185 | |
| 186 | @override |
| 187 | double get minScrollExtent => _minScrollExtent!; |
| 188 | final double? _minScrollExtent; |
| 189 | |
| 190 | @override |
| 191 | double get maxScrollExtent => _maxScrollExtent!; |
| 192 | final double? _maxScrollExtent; |
| 193 | |
| 194 | @override |
| 195 | bool get hasContentDimensions => _minScrollExtent != null && _maxScrollExtent != null; |
| 196 | |
| 197 | @override |
| 198 | double get pixels => _pixels!; |
| 199 | final double? _pixels; |
| 200 | |
| 201 | @override |
| 202 | bool get hasPixels => _pixels != null; |
| 203 | |
| 204 | @override |
| 205 | double get viewportDimension => _viewportDimension!; |
| 206 | final double? _viewportDimension; |
| 207 | |
| 208 | @override |
| 209 | bool get hasViewportDimension => _viewportDimension != null; |
| 210 | |
| 211 | @override |
| 212 | final AxisDirection axisDirection; |
| 213 | |
| 214 | @override |
| 215 | final double devicePixelRatio; |
| 216 | |
| 217 | @override |
| 218 | String toString() { |
| 219 | return ' ${objectRuntimeType(this, 'FixedScrollMetrics' )}( ${extentBefore.toStringAsFixed(1)}..[ ${extentInside.toStringAsFixed(1)}].. ${extentAfter.toStringAsFixed(1)})' ; |
| 220 | } |
| 221 | } |
| 222 | |