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 'page_storage.dart';
6/// @docImport 'scroll_controller.dart';
7/// @docImport 'scroll_view.dart';
8/// @docImport 'scrollable.dart';
9/// @docImport 'viewport.dart';
10library;
11
12import 'dart:math' as math;
13
14import 'package:flutter/gestures.dart';
15import 'package:flutter/physics.dart';
16import 'package:flutter/rendering.dart';
17
18import 'basic.dart';
19import 'framework.dart';
20import 'scroll_activity.dart';
21import 'scroll_context.dart';
22import 'scroll_notification.dart';
23import 'scroll_physics.dart';
24import 'scroll_position.dart';
25
26/// A scroll position that manages scroll activities for a single
27/// [ScrollContext].
28///
29/// This class is a concrete subclass of [ScrollPosition] logic that handles a
30/// single [ScrollContext], such as a [Scrollable]. An instance of this class
31/// manages [ScrollActivity] instances, which change what content is visible in
32/// the [Scrollable]'s [Viewport].
33///
34/// {@macro flutter.widgets.scrollPosition.listening}
35///
36/// See also:
37///
38/// * [ScrollPosition], which defines the underlying model for a position
39/// within a [Scrollable] but is agnostic as to how that position is
40/// changed.
41/// * [ScrollView] and its subclasses such as [ListView], which use
42/// [ScrollPositionWithSingleContext] to manage their scroll position.
43/// * [ScrollController], which can manipulate one or more [ScrollPosition]s,
44/// and which uses [ScrollPositionWithSingleContext] as its default class for
45/// scroll positions.
46class ScrollPositionWithSingleContext extends ScrollPosition implements ScrollActivityDelegate {
47 /// Create a [ScrollPosition] object that manages its behavior using
48 /// [ScrollActivity] objects.
49 ///
50 /// The `initialPixels` argument can be null, but in that case it is
51 /// imperative that the value be set, using [correctPixels], as soon as
52 /// [applyNewDimensions] is invoked, before calling the inherited
53 /// implementation of that method.
54 ///
55 /// If [keepScrollOffset] is true (the default), the current scroll offset is
56 /// saved with [PageStorage] and restored it if this scroll position's scrollable
57 /// is recreated.
58 ScrollPositionWithSingleContext({
59 required super.physics,
60 required super.context,
61 double? initialPixels = 0.0,
62 super.keepScrollOffset,
63 super.oldPosition,
64 super.debugLabel,
65 }) {
66 // If oldPosition is not null, the superclass will first call absorb(),
67 // which may set _pixels and _activity.
68 if (!hasPixels && initialPixels != null) {
69 correctPixels(initialPixels);
70 }
71 if (activity == null) {
72 goIdle();
73 }
74 assert(activity != null);
75 }
76
77 /// Velocity from a previous activity temporarily held by [hold] to potentially
78 /// transfer to a next activity.
79 double _heldPreviousVelocity = 0.0;
80
81 @override
82 AxisDirection get axisDirection => context.axisDirection;
83
84 @override
85 double setPixels(double newPixels) {
86 assert(activity!.isScrolling);
87 return super.setPixels(newPixels);
88 }
89
90 @override
91 void absorb(ScrollPosition other) {
92 super.absorb(other);
93 if (other is! ScrollPositionWithSingleContext) {
94 goIdle();
95 return;
96 }
97 activity!.updateDelegate(this);
98 _userScrollDirection = other._userScrollDirection;
99 assert(_currentDrag == null);
100 if (other._currentDrag != null) {
101 _currentDrag = other._currentDrag;
102 _currentDrag!.updateDelegate(this);
103 other._currentDrag = null;
104 }
105 }
106
107 @override
108 void applyNewDimensions() {
109 super.applyNewDimensions();
110 context.setCanDrag(physics.shouldAcceptUserOffset(this));
111 }
112
113 @override
114 void beginActivity(ScrollActivity? newActivity) {
115 _heldPreviousVelocity = 0.0;
116 if (newActivity == null) {
117 return;
118 }
119 assert(newActivity.delegate == this);
120 super.beginActivity(newActivity);
121 _currentDrag?.dispose();
122 _currentDrag = null;
123 if (!activity!.isScrolling) {
124 updateUserScrollDirection(ScrollDirection.idle);
125 }
126 }
127
128 @override
129 void applyUserOffset(double delta) {
130 updateUserScrollDirection(delta > 0.0 ? ScrollDirection.forward : ScrollDirection.reverse);
131 setPixels(pixels - physics.applyPhysicsToUserOffset(this, delta));
132 }
133
134 @override
135 void goIdle() {
136 beginActivity(IdleScrollActivity(this));
137 }
138
139 /// Start a physics-driven simulation that settles the [pixels] position,
140 /// starting at a particular velocity.
141 ///
142 /// This method defers to [ScrollPhysics.createBallisticSimulation], which
143 /// typically provides a bounce simulation when the current position is out of
144 /// bounds and a friction simulation when the position is in bounds but has a
145 /// non-zero velocity.
146 ///
147 /// The velocity should be in logical pixels per second.
148 @override
149 void goBallistic(double velocity) {
150 assert(hasPixels);
151 final Simulation? simulation = physics.createBallisticSimulation(this, velocity);
152 if (simulation != null) {
153 beginActivity(BallisticScrollActivity(this, simulation, context.vsync, shouldIgnorePointer));
154 } else {
155 goIdle();
156 }
157 }
158
159 @override
160 ScrollDirection get userScrollDirection => _userScrollDirection;
161 ScrollDirection _userScrollDirection = ScrollDirection.idle;
162
163 /// Set [userScrollDirection] to the given value.
164 ///
165 /// If this changes the value, then a [UserScrollNotification] is dispatched.
166 @protected
167 @visibleForTesting
168 void updateUserScrollDirection(ScrollDirection value) {
169 if (userScrollDirection == value) {
170 return;
171 }
172 _userScrollDirection = value;
173 didUpdateScrollDirection(value);
174 }
175
176 @override
177 Future<void> animateTo(double to, {required Duration duration, required Curve curve}) {
178 if (nearEqual(to, pixels, physics.toleranceFor(this).distance)) {
179 // Skip the animation, go straight to the position as we are already close.
180 jumpTo(to);
181 return Future<void>.value();
182 }
183
184 final DrivenScrollActivity activity = DrivenScrollActivity(
185 this,
186 from: pixels,
187 to: to,
188 duration: duration,
189 curve: curve,
190 vsync: context.vsync,
191 );
192 beginActivity(activity);
193 return activity.done;
194 }
195
196 @override
197 void jumpTo(double value) {
198 goIdle();
199 if (pixels != value) {
200 final double oldPixels = pixels;
201 forcePixels(value);
202 didStartScroll();
203 didUpdateScrollPositionBy(pixels - oldPixels);
204 didEndScroll();
205 }
206 goBallistic(0.0);
207 }
208
209 @override
210 void pointerScroll(double delta) {
211 // If an update is made to pointer scrolling here, consider if the same
212 // (or similar) change should be made in
213 // _NestedScrollCoordinator.pointerScroll.
214 if (delta == 0.0) {
215 goBallistic(0.0);
216 return;
217 }
218
219 final double targetPixels = math.min(
220 math.max(pixels + delta, minScrollExtent),
221 maxScrollExtent,
222 );
223 if (targetPixels != pixels) {
224 goIdle();
225 updateUserScrollDirection(-delta > 0.0 ? ScrollDirection.forward : ScrollDirection.reverse);
226 final double oldPixels = pixels;
227 // Set the notifier before calling force pixels.
228 // This is set to false again after going ballistic below.
229 isScrollingNotifier.value = true;
230 forcePixels(targetPixels);
231 didStartScroll();
232 didUpdateScrollPositionBy(pixels - oldPixels);
233 didEndScroll();
234 goBallistic(0.0);
235 }
236 }
237
238 // flutter_ignore: deprecation_syntax, https://github.com/flutter/flutter/issues/44609
239 @Deprecated('This will lead to bugs.')
240 @override
241 void jumpToWithoutSettling(double value) {
242 goIdle();
243 if (pixels != value) {
244 final double oldPixels = pixels;
245 forcePixels(value);
246 didStartScroll();
247 didUpdateScrollPositionBy(pixels - oldPixels);
248 didEndScroll();
249 }
250 }
251
252 @override
253 ScrollHoldController hold(VoidCallback holdCancelCallback) {
254 final double previousVelocity = activity!.velocity;
255 final HoldScrollActivity holdActivity = HoldScrollActivity(
256 delegate: this,
257 onHoldCanceled: holdCancelCallback,
258 );
259 beginActivity(holdActivity);
260 _heldPreviousVelocity = previousVelocity;
261 return holdActivity;
262 }
263
264 ScrollDragController? _currentDrag;
265
266 @override
267 Drag drag(DragStartDetails details, VoidCallback dragCancelCallback) {
268 final ScrollDragController drag = ScrollDragController(
269 delegate: this,
270 details: details,
271 onDragCanceled: dragCancelCallback,
272 carriedVelocity: physics.carriedMomentum(_heldPreviousVelocity),
273 motionStartDistanceThreshold: physics.dragStartDistanceMotionThreshold,
274 );
275 beginActivity(DragScrollActivity(this, drag));
276 assert(_currentDrag == null);
277 _currentDrag = drag;
278 return drag;
279 }
280
281 @override
282 void dispose() {
283 _currentDrag?.dispose();
284 _currentDrag = null;
285 super.dispose();
286 }
287
288 @override
289 void debugFillDescription(List<String> description) {
290 super.debugFillDescription(description);
291 description.add('${context.runtimeType}');
292 description.add('$physics');
293 description.add('$activity');
294 description.add('$userScrollDirection');
295 }
296}
297

Provided by KDAB

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