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: 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}
169class 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