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';
10library;
11
12import 'package:flutter/animation.dart';
13import '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.
46enum 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].
78ScrollDirection 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.
100abstract 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
285class _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

Provided by KDAB

Privacy Policy
Learn more about Flutter for embedded and desktop on industrialflutter.com