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: viewportDimension ?? (hasViewportDimension ? this.viewportDimension : null), |
72 | axisDirection: axisDirection ?? this.axisDirection, |
73 | devicePixelRatio: devicePixelRatio ?? this.devicePixelRatio, |
74 | ); |
75 | } |
76 | |
77 | /// The minimum in-range value for [pixels]. |
78 | /// |
79 | /// The actual [pixels] value might be [outOfRange]. |
80 | /// |
81 | /// This value is typically less than or equal to |
82 | /// [maxScrollExtent]. It can be negative infinity, if the scroll is unbounded. |
83 | double get minScrollExtent; |
84 | |
85 | /// The maximum in-range value for [pixels]. |
86 | /// |
87 | /// The actual [pixels] value might be [outOfRange]. |
88 | /// |
89 | /// This value is typically greater than or equal to |
90 | /// [minScrollExtent]. It can be infinity, if the scroll is unbounded. |
91 | double get maxScrollExtent; |
92 | |
93 | /// Whether the [minScrollExtent] and the [maxScrollExtent] properties are available. |
94 | bool get hasContentDimensions; |
95 | |
96 | /// The current scroll position, in logical pixels along the [axisDirection]. |
97 | double get pixels; |
98 | |
99 | /// Whether the [pixels] property is available. |
100 | bool get hasPixels; |
101 | |
102 | /// The extent of the viewport along the [axisDirection]. |
103 | double get viewportDimension; |
104 | |
105 | /// Whether the [viewportDimension] property is available. |
106 | bool get hasViewportDimension; |
107 | |
108 | /// The direction in which the scroll view scrolls. |
109 | AxisDirection get axisDirection; |
110 | |
111 | /// The axis in which the scroll view scrolls. |
112 | Axis get axis => axisDirectionToAxis(axisDirection); |
113 | |
114 | /// Whether the [pixels] value is outside the [minScrollExtent] and |
115 | /// [maxScrollExtent]. |
116 | bool get outOfRange => pixels < minScrollExtent || pixels > maxScrollExtent; |
117 | |
118 | /// Whether the [pixels] value is exactly at the [minScrollExtent] or the |
119 | /// [maxScrollExtent]. |
120 | bool get atEdge => pixels == minScrollExtent || pixels == maxScrollExtent; |
121 | |
122 | /// The quantity of content conceptually "above" the viewport in the scrollable. |
123 | /// This is the content above the content described by [extentInside]. |
124 | double get extentBefore => math.max(pixels - minScrollExtent, 0.0); |
125 | |
126 | /// The quantity of content conceptually "inside" the viewport in the |
127 | /// scrollable (including empty space if the total amount of content is less |
128 | /// than the [viewportDimension]). |
129 | /// |
130 | /// The value is typically the extent of the viewport ([viewportDimension]) |
131 | /// when [outOfRange] is false. It can be less when overscrolling. |
132 | /// |
133 | /// The value is always non-negative, and less than or equal to [viewportDimension]. |
134 | double get extentInside { |
135 | assert(minScrollExtent <= maxScrollExtent); |
136 | return viewportDimension |
137 | // "above" overscroll value |
138 | - clampDouble(minScrollExtent - pixels, 0, viewportDimension) |
139 | // "below" overscroll value |
140 | - clampDouble(pixels - maxScrollExtent, 0, viewportDimension); |
141 | } |
142 | |
143 | /// The quantity of content conceptually "below" the viewport in the scrollable. |
144 | /// This is the content below the content described by [extentInside]. |
145 | double get extentAfter => math.max(maxScrollExtent - pixels, 0.0); |
146 | |
147 | /// The total quantity of content available. |
148 | /// |
149 | /// This is the sum of [extentBefore], [extentInside], and [extentAfter], modulo |
150 | /// any rounding errors. |
151 | double get extentTotal => maxScrollExtent - minScrollExtent + viewportDimension; |
152 | |
153 | /// The [FlutterView.devicePixelRatio] of the view that the [Scrollable] |
154 | /// associated with this metrics object is drawn into. |
155 | double get devicePixelRatio; |
156 | } |
157 | |
158 | /// An immutable snapshot of values associated with a [Scrollable] viewport. |
159 | /// |
160 | /// For details, see [ScrollMetrics], which defines this object's interfaces. |
161 | /// |
162 | /// {@tool dartpad} |
163 | /// This sample shows how a [ScrollMetricsNotification] is dispatched when |
164 | /// the [ScrollMetrics] changed as a result of resizing the [Viewport]. |
165 | /// Press the floating action button to increase the scrollable window's size. |
166 | /// |
167 | /// ** See code in examples/api/lib/widgets/scroll_position/scroll_metrics_notification.0.dart ** |
168 | /// {@end-tool} |
169 | class FixedScrollMetrics with ScrollMetrics { |
170 | /// Creates an immutable snapshot of values associated with a [Scrollable] viewport. |
171 | FixedScrollMetrics({ |
172 | required double? minScrollExtent, |
173 | required double? maxScrollExtent, |
174 | required double? pixels, |
175 | required double? viewportDimension, |
176 | required this.axisDirection, |
177 | required this.devicePixelRatio, |
178 | }) : _minScrollExtent = minScrollExtent, |
179 | _maxScrollExtent = maxScrollExtent, |
180 | _pixels = pixels, |
181 | _viewportDimension = viewportDimension; |
182 | |
183 | @override |
184 | double get minScrollExtent => _minScrollExtent!; |
185 | final double? _minScrollExtent; |
186 | |
187 | @override |
188 | double get maxScrollExtent => _maxScrollExtent!; |
189 | final double? _maxScrollExtent; |
190 | |
191 | @override |
192 | bool get hasContentDimensions => _minScrollExtent != null && _maxScrollExtent != null; |
193 | |
194 | @override |
195 | double get pixels => _pixels!; |
196 | final double? _pixels; |
197 | |
198 | @override |
199 | bool get hasPixels => _pixels != null; |
200 | |
201 | @override |
202 | double get viewportDimension => _viewportDimension!; |
203 | final double? _viewportDimension; |
204 | |
205 | @override |
206 | bool get hasViewportDimension => _viewportDimension != null; |
207 | |
208 | @override |
209 | final AxisDirection axisDirection; |
210 | |
211 | @override |
212 | final double devicePixelRatio; |
213 | |
214 | @override |
215 | String toString() { |
216 | return ' ${objectRuntimeType(this, 'FixedScrollMetrics' )}( ${extentBefore.toStringAsFixed(1)}..[ ${extentInside.toStringAsFixed(1)}].. ${extentAfter.toStringAsFixed(1)})' ; |
217 | } |
218 | } |
219 | |