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';
11library;
12
13import 'dart:math' as math;
14
15import 'package:flutter/foundation.dart';
16import '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.
50mixin 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}
172class 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