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