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 'package:flutter/material.dart'; |
6 | /// |
7 | /// @docImport 'sliver.dart'; |
8 | /// @docImport 'sliver_persistent_header.dart'; |
9 | /// @docImport 'viewport.dart'; |
10 | library; |
11 | |
12 | import 'package:flutter/animation.dart'; |
13 | import 'package:flutter/foundation.dart'; |
14 | |
15 | /// The direction of a scroll, relative to the positive scroll offset axis given |
16 | /// by an [AxisDirection] and a [GrowthDirection]. |
17 | /// |
18 | /// This is similar to [GrowthDirection], but contrasts in that it has a third |
19 | /// value, [idle], for the case where no scroll is occurring. |
20 | /// |
21 | /// This is used by [RenderSliverFloatingPersistentHeader] to only expand when |
22 | /// the user is scrolling in the same direction as the detected scroll offset |
23 | /// change. |
24 | /// |
25 | /// {@template flutter.rendering.ScrollDirection.sample} |
26 | /// {@tool dartpad} |
27 | /// This sample shows a [CustomScrollView], with [Radio] buttons in the |
28 | /// [AppBar.bottom] that change the [AxisDirection] to illustrate different |
29 | /// configurations. With a [NotificationListener] to listen to |
30 | /// [UserScrollNotification]s, which occur when the [ScrollDirection] changes |
31 | /// or stops. |
32 | /// |
33 | /// ** See code in examples/api/lib/rendering/scroll_direction/scroll_direction.0.dart ** |
34 | /// {@end-tool} |
35 | /// {@endtemplate} |
36 | /// |
37 | /// See also: |
38 | /// |
39 | /// * [AxisDirection], which is a directional version of this enum (with values |
40 | /// like left and right, rather than just horizontal). |
41 | /// * [GrowthDirection], the direction in which slivers and their content are |
42 | /// ordered, relative to the scroll offset axis as specified by |
43 | /// [AxisDirection]. |
44 | /// * [UserScrollNotification], which will notify listeners when the |
45 | /// [ScrollDirection] changes. |
46 | enum ScrollDirection { |
47 | /// No scrolling is underway. |
48 | idle, |
49 | |
50 | /// Scrolling is happening in the negative scroll offset direction. |
51 | /// |
52 | /// For example, for the [GrowthDirection.forward] part of a vertical |
53 | /// [AxisDirection.down] list, which is the default directional configuration |
54 | /// of all scroll views, this means the content is going down, exposing |
55 | /// earlier content as it approaches the zero position. |
56 | /// |
57 | /// An anecdote for this most common case is 'forward is toward' the zero |
58 | /// position. |
59 | forward, |
60 | |
61 | /// Scrolling is happening in the positive scroll offset direction. |
62 | /// |
63 | /// For example, for the [GrowthDirection.forward] part of a vertical |
64 | /// [AxisDirection.down] list, which is the default directional configuration |
65 | /// of all scroll views, this means the content is moving up, exposing |
66 | /// lower content. |
67 | /// |
68 | /// An anecdote for this most common case is reversing, or backing away, from |
69 | /// the zero position. |
70 | reverse, |
71 | } |
72 | |
73 | /// Returns the opposite of the given [ScrollDirection]. |
74 | /// |
75 | /// Specifically, returns [ScrollDirection.reverse] for [ScrollDirection.forward] |
76 | /// (and vice versa) and returns [ScrollDirection.idle] for |
77 | /// [ScrollDirection.idle]. |
78 | ScrollDirection flipScrollDirection(ScrollDirection direction) { |
79 | return switch (direction) { |
80 | ScrollDirection.idle => ScrollDirection.idle, |
81 | ScrollDirection.forward => ScrollDirection.reverse, |
82 | ScrollDirection.reverse => ScrollDirection.forward, |
83 | }; |
84 | } |
85 | |
86 | /// Which part of the content inside the viewport should be visible. |
87 | /// |
88 | /// The [pixels] value determines the scroll offset that the viewport uses to |
89 | /// select which part of its content to display. As the user scrolls the |
90 | /// viewport, this value changes, which changes the content that is displayed. |
91 | /// |
92 | /// This object is a [Listenable] that notifies its listeners when [pixels] |
93 | /// changes. |
94 | /// |
95 | /// See also: |
96 | /// |
97 | /// * [ScrollPosition], which is a commonly used concrete subclass. |
98 | /// * [RenderViewportBase], which is a render object that uses viewport |
99 | /// offsets. |
100 | abstract class ViewportOffset extends ChangeNotifier { |
101 | /// Default constructor. |
102 | /// |
103 | /// Allows subclasses to construct this object directly. |
104 | ViewportOffset() { |
105 | if (kFlutterMemoryAllocationsEnabled) { |
106 | ChangeNotifier.maybeDispatchObjectCreation(this); |
107 | } |
108 | } |
109 | |
110 | /// Creates a viewport offset with the given [pixels] value. |
111 | /// |
112 | /// The [pixels] value does not change unless the viewport issues a |
113 | /// correction. |
114 | factory ViewportOffset.fixed(double value) = _FixedViewportOffset; |
115 | |
116 | /// Creates a viewport offset with a [pixels] value of 0.0. |
117 | /// |
118 | /// The [pixels] value does not change unless the viewport issues a |
119 | /// correction. |
120 | factory ViewportOffset.zero() = _FixedViewportOffset.zero; |
121 | |
122 | /// The number of pixels to offset the children in the opposite of the axis direction. |
123 | /// |
124 | /// For example, if the axis direction is down, then the pixel value |
125 | /// represents the number of logical pixels to move the children _up_ the |
126 | /// screen. Similarly, if the axis direction is left, then the pixels value |
127 | /// represents the number of logical pixels to move the children to _right_. |
128 | /// |
129 | /// This object notifies its listeners when this value changes (except when |
130 | /// the value changes due to [correctBy]). |
131 | double get pixels; |
132 | |
133 | /// Whether the [pixels] property is available. |
134 | bool get hasPixels; |
135 | |
136 | /// Called when the viewport's extents are established. |
137 | /// |
138 | /// The argument is the dimension of the [RenderViewport] in the main axis |
139 | /// (e.g. the height, for a vertical viewport). |
140 | /// |
141 | /// This may be called redundantly, with the same value, each frame. This is |
142 | /// called during layout for the [RenderViewport]. If the viewport is |
143 | /// configured to shrink-wrap its contents, it may be called several times, |
144 | /// since the layout is repeated each time the scroll offset is corrected. |
145 | /// |
146 | /// If this is called, it is called before [applyContentDimensions]. If this |
147 | /// is called, [applyContentDimensions] will be called soon afterwards in the |
148 | /// same layout phase. If the viewport is not configured to shrink-wrap its |
149 | /// contents, then this will only be called when the viewport recomputes its |
150 | /// size (i.e. when its parent lays out), and not during normal scrolling. |
151 | /// |
152 | /// If applying the viewport dimensions changes the scroll offset, return |
153 | /// false. Otherwise, return true. If you return false, the [RenderViewport] |
154 | /// will be laid out again with the new scroll offset. This is expensive. (The |
155 | /// return value is answering the question "did you accept these viewport |
156 | /// dimensions unconditionally?"; if the new dimensions change the |
157 | /// [ViewportOffset]'s actual [pixels] value, then the viewport will need to |
158 | /// be laid out again.) |
159 | bool applyViewportDimension(double viewportDimension); |
160 | |
161 | /// Called when the viewport's content extents are established. |
162 | /// |
163 | /// The arguments are the minimum and maximum scroll extents respectively. The |
164 | /// minimum will be equal to or less than the maximum. In the case of slivers, |
165 | /// the minimum will be equal to or less than zero, the maximum will be equal |
166 | /// to or greater than zero. |
167 | /// |
168 | /// The maximum scroll extent has the viewport dimension subtracted from it. |
169 | /// For instance, if there is 100.0 pixels of scrollable content, and the |
170 | /// viewport is 80.0 pixels high, then the minimum scroll extent will |
171 | /// typically be 0.0 and the maximum scroll extent will typically be 20.0, |
172 | /// because there's only 20.0 pixels of actual scroll slack. |
173 | /// |
174 | /// If applying the content dimensions changes the scroll offset, return |
175 | /// false. Otherwise, return true. If you return false, the [RenderViewport] |
176 | /// will be laid out again with the new scroll offset. This is expensive. (The |
177 | /// return value is answering the question "did you accept these content |
178 | /// dimensions unconditionally?"; if the new dimensions change the |
179 | /// [ViewportOffset]'s actual [pixels] value, then the viewport will need to |
180 | /// be laid out again.) |
181 | /// |
182 | /// This is called at least once each time the [RenderViewport] is laid out, |
183 | /// even if the values have not changed. It may be called many times if the |
184 | /// scroll offset is corrected (if this returns false). This is always called |
185 | /// after [applyViewportDimension], if that method is called. |
186 | bool applyContentDimensions(double minScrollExtent, double maxScrollExtent); |
187 | |
188 | /// Apply a layout-time correction to the scroll offset. |
189 | /// |
190 | /// This method should change the [pixels] value by `correction`, but without |
191 | /// calling [notifyListeners]. It is called during layout by the |
192 | /// [RenderViewport], before [applyContentDimensions]. After this method is |
193 | /// called, the layout will be recomputed and that may result in this method |
194 | /// being called again, though this should be very rare. |
195 | /// |
196 | /// See also: |
197 | /// |
198 | /// * [jumpTo], for also changing the scroll position when not in layout. |
199 | /// [jumpTo] applies the change immediately and notifies its listeners. |
200 | void correctBy(double correction); |
201 | |
202 | /// Jumps [pixels] from its current value to the given value, |
203 | /// without animation, and without checking if the new value is in range. |
204 | /// |
205 | /// See also: |
206 | /// |
207 | /// * [correctBy], for changing the current offset in the middle of layout |
208 | /// and that defers the notification of its listeners until after layout. |
209 | void jumpTo(double pixels); |
210 | |
211 | /// Animates [pixels] from its current value to the given value. |
212 | /// |
213 | /// The returned [Future] will complete when the animation ends, whether it |
214 | /// completed successfully or whether it was interrupted prematurely. |
215 | /// |
216 | /// The duration must not be zero. To jump to a particular value without an |
217 | /// animation, use [jumpTo]. |
218 | Future<void> animateTo(double to, {required Duration duration, required Curve curve}); |
219 | |
220 | /// Calls [jumpTo] if duration is null or [Duration.zero], otherwise |
221 | /// [animateTo] is called. |
222 | /// |
223 | /// If [animateTo] is called then [curve] defaults to [Curves.ease]. The |
224 | /// [clamp] parameter is ignored by this stub implementation but subclasses |
225 | /// like [ScrollPosition] handle it by adjusting [to] to prevent over or |
226 | /// underscroll. |
227 | Future<void> moveTo(double to, {Duration? duration, Curve? curve, bool? clamp}) { |
228 | if (duration == null || duration == Duration.zero) { |
229 | jumpTo(to); |
230 | return Future<void>.value(); |
231 | } else { |
232 | return animateTo(to, duration: duration, curve: curve ?? Curves.ease); |
233 | } |
234 | } |
235 | |
236 | /// The direction in which the user is trying to change [pixels], relative to |
237 | /// the viewport's [RenderViewportBase.axisDirection]. |
238 | /// |
239 | /// If the _user_ is not scrolling, this will return [ScrollDirection.idle] |
240 | /// even if there is (for example) a [ScrollActivity] currently animating the |
241 | /// position. |
242 | /// |
243 | /// This is exposed in [SliverConstraints.userScrollDirection], which is used |
244 | /// by some slivers to determine how to react to a change in scroll offset. |
245 | /// For example, [RenderSliverFloatingPersistentHeader] will only expand a |
246 | /// floating app bar when the [userScrollDirection] is in the positive scroll |
247 | /// offset direction. |
248 | /// |
249 | /// {@macro flutter.rendering.ScrollDirection.sample} |
250 | ScrollDirection get userScrollDirection; |
251 | |
252 | /// Whether a viewport is allowed to change [pixels] implicitly to respond to |
253 | /// a call to [RenderObject.showOnScreen]. |
254 | /// |
255 | /// [RenderObject.showOnScreen] is, for example, used to bring a text field |
256 | /// fully on screen after it has received focus. This property controls |
257 | /// whether the viewport associated with this offset is allowed to change the |
258 | /// offset's [pixels] value to fulfill such a request. |
259 | bool get allowImplicitScrolling; |
260 | |
261 | @override |
262 | String toString() { |
263 | final List<String> description = <String>[]; |
264 | debugFillDescription(description); |
265 | return '${describeIdentity(this)} (${description.join( ", ")} )'; |
266 | } |
267 | |
268 | /// Add additional information to the given description for use by [toString]. |
269 | /// |
270 | /// This method makes it easier for subclasses to coordinate to provide a |
271 | /// high-quality [toString] implementation. The [toString] implementation on |
272 | /// the [State] base class calls [debugFillDescription] to collect useful |
273 | /// information from subclasses to incorporate into its return value. |
274 | /// |
275 | /// Implementations of this method should start with a call to the inherited |
276 | /// method, as in `super.debugFillDescription(description)`. |
277 | @mustCallSuper |
278 | void debugFillDescription(List<String> description) { |
279 | if (hasPixels) { |
280 | description.add('offset:${pixels.toStringAsFixed(1)} '); |
281 | } |
282 | } |
283 | } |
284 | |
285 | class _FixedViewportOffset extends ViewportOffset { |
286 | _FixedViewportOffset(this._pixels); |
287 | _FixedViewportOffset.zero() : _pixels = 0.0; |
288 | |
289 | double _pixels; |
290 | |
291 | @override |
292 | double get pixels => _pixels; |
293 | |
294 | @override |
295 | bool get hasPixels => true; |
296 | |
297 | @override |
298 | bool applyViewportDimension(double viewportDimension) => true; |
299 | |
300 | @override |
301 | bool applyContentDimensions(double minScrollExtent, double maxScrollExtent) => true; |
302 | |
303 | @override |
304 | void correctBy(double correction) { |
305 | _pixels += correction; |
306 | } |
307 | |
308 | @override |
309 | void jumpTo(double pixels) { |
310 | // Do nothing, viewport is fixed. |
311 | } |
312 | |
313 | @override |
314 | Future<void> animateTo(double to, {required Duration duration, required Curve curve}) async {} |
315 | |
316 | @override |
317 | ScrollDirection get userScrollDirection => ScrollDirection.idle; |
318 | |
319 | @override |
320 | bool get allowImplicitScrolling => false; |
321 | } |
322 |
Definitions
- ScrollDirection
- flipScrollDirection
- ViewportOffset
- ViewportOffset
- fixed
- zero
- pixels
- hasPixels
- applyViewportDimension
- applyContentDimensions
- correctBy
- jumpTo
- animateTo
- moveTo
- userScrollDirection
- allowImplicitScrolling
- toString
- debugFillDescription
- _FixedViewportOffset
- _FixedViewportOffset
- zero
- pixels
- hasPixels
- applyViewportDimension
- applyContentDimensions
- correctBy
- jumpTo
- animateTo
- userScrollDirection
Learn more about Flutter for embedded and desktop on industrialflutter.com