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 'list_wheel_scroll_view.dart';
8/// @docImport 'page_view.dart';
9/// @docImport 'scroll_position.dart';
10/// @docImport 'scroll_view.dart';
11library;
12
13import 'package:flutter/foundation.dart';
14import 'package:flutter/gestures.dart';
15import 'package:flutter/rendering.dart';
16import 'package:flutter/services.dart' show LogicalKeyboardKey;
17
18import 'framework.dart';
19import 'overscroll_indicator.dart';
20import 'scroll_physics.dart';
21import 'scroll_view.dart';
22import 'scrollable.dart';
23import 'scrollable_helpers.dart';
24import 'scrollbar.dart';
25
26const Color _kDefaultGlowColor = Color(0xFFFFFFFF);
27
28/// Device types that scrollables should accept drag gestures from by default.
29const Set<PointerDeviceKind> _kTouchLikeDeviceTypes = <PointerDeviceKind>{
30 PointerDeviceKind.touch,
31 PointerDeviceKind.stylus,
32 PointerDeviceKind.invertedStylus,
33 PointerDeviceKind.trackpad,
34 // The VoiceAccess sends pointer events with unknown type when scrolling
35 // scrollables.
36 PointerDeviceKind.unknown,
37};
38
39/// Types of overscroll indicators supported by [TargetPlatform.android].
40enum AndroidOverscrollIndicator {
41 /// Utilizes a [StretchingOverscrollIndicator], which transforms the contents
42 /// of a [ScrollView] when overscrolled.
43 stretch,
44
45 /// Utilizes a [GlowingOverscrollIndicator], painting a glowing semi circle on
46 /// top of the [ScrollView] in response to overscrolling.
47 glow,
48}
49
50/// Describes how [Scrollable] widgets should behave.
51///
52/// {@template flutter.widgets.scrollBehavior}
53/// Used by [ScrollConfiguration] to configure the [Scrollable] widgets in a
54/// subtree.
55///
56/// This class can be extended to further customize a [ScrollBehavior] for a
57/// subtree. For example, overriding [ScrollBehavior.getScrollPhysics] sets the
58/// default [ScrollPhysics] for [Scrollable]s that inherit this [ScrollConfiguration].
59/// Overriding [ScrollBehavior.buildOverscrollIndicator] can be used to add or change
60/// the default [GlowingOverscrollIndicator] decoration, while
61/// [ScrollBehavior.buildScrollbar] can be changed to modify the default [Scrollbar].
62///
63/// When looking to easily toggle the default decorations, you can use
64/// [ScrollBehavior.copyWith] instead of creating your own [ScrollBehavior] class.
65/// The `scrollbar` and `overscrollIndicator` flags can turn these decorations off.
66/// {@endtemplate}
67///
68/// See also:
69///
70/// * [ScrollConfiguration], the inherited widget that controls how
71/// [Scrollable] widgets behave in a subtree.
72@immutable
73class ScrollBehavior {
74 /// Creates a description of how [Scrollable] widgets should behave.
75 const ScrollBehavior();
76
77 /// Creates a copy of this ScrollBehavior, making it possible to
78 /// easily toggle `scrollbar` and `overscrollIndicator` effects.
79 ///
80 /// This is used by widgets like [PageView] and [ListWheelScrollView] to
81 /// override the current [ScrollBehavior] and manage how they are decorated.
82 /// Widgets such as these have the option to provide a [ScrollBehavior] on
83 /// the widget level, like [PageView.scrollBehavior], in order to change the
84 /// default.
85 ScrollBehavior copyWith({
86 bool? scrollbars,
87 bool? overscroll,
88 Set<PointerDeviceKind>? dragDevices,
89 MultitouchDragStrategy? multitouchDragStrategy,
90 Set<LogicalKeyboardKey>? pointerAxisModifiers,
91 ScrollPhysics? physics,
92 TargetPlatform? platform,
93 ScrollViewKeyboardDismissBehavior? keyboardDismissBehavior,
94 }) {
95 return _WrappedScrollBehavior(
96 delegate: this,
97 scrollbars: scrollbars ?? true,
98 overscroll: overscroll ?? true,
99 dragDevices: dragDevices,
100 multitouchDragStrategy: multitouchDragStrategy,
101 pointerAxisModifiers: pointerAxisModifiers,
102 physics: physics,
103 platform: platform,
104 keyboardDismissBehavior: keyboardDismissBehavior,
105 );
106 }
107
108 /// The platform whose scroll physics should be implemented.
109 ///
110 /// Defaults to the current platform.
111 TargetPlatform getPlatform(BuildContext context) => defaultTargetPlatform;
112
113 /// The device kinds that the scrollable will accept drag gestures from.
114 ///
115 /// By default only [PointerDeviceKind.touch], [PointerDeviceKind.stylus],
116 /// [PointerDeviceKind.invertedStylus], and [PointerDeviceKind.trackpad]
117 /// are configured to create drag gestures. Enabling this for
118 /// [PointerDeviceKind.mouse] will make it difficult or impossible to select
119 /// text in scrollable containers and is not recommended.
120 Set<PointerDeviceKind> get dragDevices => _kTouchLikeDeviceTypes;
121
122 /// {@macro flutter.gestures.monodrag.DragGestureRecognizer.multitouchDragStrategy}
123 ///
124 /// By default, [MultitouchDragStrategy.latestPointer] is configured to
125 /// create drag gestures for non-Apple platforms, and
126 /// [MultitouchDragStrategy.averageBoundaryPointers] for Apple platforms.
127 MultitouchDragStrategy getMultitouchDragStrategy(BuildContext context) {
128 switch (getPlatform(context)) {
129 case TargetPlatform.macOS:
130 case TargetPlatform.iOS:
131 return MultitouchDragStrategy.averageBoundaryPointers;
132 case TargetPlatform.linux:
133 case TargetPlatform.windows:
134 case TargetPlatform.android:
135 case TargetPlatform.fuchsia:
136 return MultitouchDragStrategy.latestPointer;
137 }
138 }
139
140 /// A set of [LogicalKeyboardKey]s that, when any or all are pressed in
141 /// combination with a [PointerDeviceKind.mouse] pointer scroll event, will
142 /// flip the axes of the scroll input.
143 ///
144 /// This will for example, result in the input of a vertical mouse wheel, to
145 /// move the [ScrollPosition] of a [ScrollView] with an [Axis.horizontal]
146 /// scroll direction.
147 ///
148 /// If other keys exclusive of this set are pressed during a scroll event, in
149 /// conjunction with keys from this set, the scroll input will still be
150 /// flipped.
151 ///
152 /// Defaults to [LogicalKeyboardKey.shiftLeft],
153 /// [LogicalKeyboardKey.shiftRight].
154 Set<LogicalKeyboardKey> get pointerAxisModifiers => <LogicalKeyboardKey>{
155 LogicalKeyboardKey.shiftLeft,
156 LogicalKeyboardKey.shiftRight,
157 };
158
159 /// Applies a [RawScrollbar] to the child widget on desktop platforms.
160 Widget buildScrollbar(BuildContext context, Widget child, ScrollableDetails details) {
161 // When modifying this function, consider modifying the implementation in
162 // the Material and Cupertino subclasses as well.
163 switch (getPlatform(context)) {
164 case TargetPlatform.linux:
165 case TargetPlatform.macOS:
166 case TargetPlatform.windows:
167 assert(details.controller != null);
168 return RawScrollbar(controller: details.controller, child: child);
169 case TargetPlatform.android:
170 case TargetPlatform.fuchsia:
171 case TargetPlatform.iOS:
172 return child;
173 }
174 }
175
176 /// Applies a [GlowingOverscrollIndicator] to the child widget on
177 /// [TargetPlatform.android] and [TargetPlatform.fuchsia].
178 Widget buildOverscrollIndicator(BuildContext context, Widget child, ScrollableDetails details) {
179 // When modifying this function, consider modifying the implementation in
180 // the Material and Cupertino subclasses as well.
181 switch (getPlatform(context)) {
182 case TargetPlatform.iOS:
183 case TargetPlatform.linux:
184 case TargetPlatform.macOS:
185 case TargetPlatform.windows:
186 return child;
187 case TargetPlatform.android:
188 case TargetPlatform.fuchsia:
189 return GlowingOverscrollIndicator(
190 axisDirection: details.direction,
191 color: _kDefaultGlowColor,
192 child: child,
193 );
194 }
195 }
196
197 /// Specifies the type of velocity tracker to use in the descendant
198 /// [Scrollable]s' drag gesture recognizers, for estimating the velocity of a
199 /// drag gesture.
200 ///
201 /// This can be used to, for example, apply different fling velocity
202 /// estimation methods on different platforms, in order to match the
203 /// platform's native behavior.
204 ///
205 /// Typically, the provided [GestureVelocityTrackerBuilder] should return a
206 /// fresh velocity tracker. If null is returned, [Scrollable] creates a new
207 /// [VelocityTracker] to track the newly added pointer that may develop into
208 /// a drag gesture.
209 ///
210 /// The default implementation provides a new
211 /// [IOSScrollViewFlingVelocityTracker] on iOS and macOS for each new pointer,
212 /// and a new [VelocityTracker] on other platforms for each new pointer.
213 GestureVelocityTrackerBuilder velocityTrackerBuilder(BuildContext context) {
214 switch (getPlatform(context)) {
215 case TargetPlatform.iOS:
216 return (PointerEvent event) => IOSScrollViewFlingVelocityTracker(event.kind);
217 case TargetPlatform.macOS:
218 return (PointerEvent event) => MacOSScrollViewFlingVelocityTracker(event.kind);
219 case TargetPlatform.android:
220 case TargetPlatform.fuchsia:
221 case TargetPlatform.linux:
222 case TargetPlatform.windows:
223 return (PointerEvent event) => VelocityTracker.withKind(event.kind);
224 }
225 }
226
227 static const ScrollPhysics _bouncingPhysics = BouncingScrollPhysics(
228 parent: RangeMaintainingScrollPhysics(),
229 );
230 static const ScrollPhysics _bouncingDesktopPhysics = BouncingScrollPhysics(
231 decelerationRate: ScrollDecelerationRate.fast,
232 parent: RangeMaintainingScrollPhysics(),
233 );
234 static const ScrollPhysics _clampingPhysics = ClampingScrollPhysics(
235 parent: RangeMaintainingScrollPhysics(),
236 );
237
238 /// The scroll physics to use for the platform given by [getPlatform].
239 ///
240 /// Defaults to [RangeMaintainingScrollPhysics] mixed with
241 /// [BouncingScrollPhysics] on iOS and [ClampingScrollPhysics] on
242 /// Android.
243 ScrollPhysics getScrollPhysics(BuildContext context) {
244 // When modifying this function, consider modifying the implementation in
245 // the Material and Cupertino subclasses as well.
246 switch (getPlatform(context)) {
247 case TargetPlatform.iOS:
248 return _bouncingPhysics;
249 case TargetPlatform.macOS:
250 return _bouncingDesktopPhysics;
251 case TargetPlatform.android:
252 case TargetPlatform.fuchsia:
253 case TargetPlatform.linux:
254 case TargetPlatform.windows:
255 return _clampingPhysics;
256 }
257 }
258
259 /// Called whenever a [ScrollConfiguration] is rebuilt with a new
260 /// [ScrollBehavior] of the same [runtimeType].
261 ///
262 /// If the new instance represents different information than the old
263 /// instance, then the method should return true, otherwise it should return
264 /// false.
265 ///
266 /// If this method returns true, all the widgets that inherit from the
267 /// [ScrollConfiguration] will rebuild using the new [ScrollBehavior]. If this
268 /// method returns false, the rebuilds might be optimized away.
269 bool shouldNotify(covariant ScrollBehavior oldDelegate) => false;
270
271 /// The default keyboard dismissal behavior for [ScrollView] widgets.
272 ///
273 /// Defaults to [ScrollViewKeyboardDismissBehavior.manual].
274 ScrollViewKeyboardDismissBehavior getKeyboardDismissBehavior(BuildContext context) =>
275 ScrollViewKeyboardDismissBehavior.manual;
276
277 @override
278 String toString() => objectRuntimeType(this, 'ScrollBehavior');
279}
280
281class _WrappedScrollBehavior implements ScrollBehavior {
282 const _WrappedScrollBehavior({
283 required this.delegate,
284 this.scrollbars = true,
285 this.overscroll = true,
286 Set<PointerDeviceKind>? dragDevices,
287 this.multitouchDragStrategy,
288 Set<LogicalKeyboardKey>? pointerAxisModifiers,
289 this.physics,
290 this.platform,
291 this.keyboardDismissBehavior,
292 }) : _dragDevices = dragDevices,
293 _pointerAxisModifiers = pointerAxisModifiers;
294
295 final ScrollBehavior delegate;
296 final bool scrollbars;
297 final bool overscroll;
298 final ScrollPhysics? physics;
299 final TargetPlatform? platform;
300 final ScrollViewKeyboardDismissBehavior? keyboardDismissBehavior;
301 final Set<PointerDeviceKind>? _dragDevices;
302 final MultitouchDragStrategy? multitouchDragStrategy;
303 final Set<LogicalKeyboardKey>? _pointerAxisModifiers;
304
305 @override
306 Set<PointerDeviceKind> get dragDevices => _dragDevices ?? delegate.dragDevices;
307
308 @override
309 Set<LogicalKeyboardKey> get pointerAxisModifiers =>
310 _pointerAxisModifiers ?? delegate.pointerAxisModifiers;
311
312 @override
313 MultitouchDragStrategy getMultitouchDragStrategy(BuildContext context) {
314 return multitouchDragStrategy ?? delegate.getMultitouchDragStrategy(context);
315 }
316
317 @override
318 Widget buildOverscrollIndicator(BuildContext context, Widget child, ScrollableDetails details) {
319 if (overscroll) {
320 return delegate.buildOverscrollIndicator(context, child, details);
321 }
322 return child;
323 }
324
325 @override
326 Widget buildScrollbar(BuildContext context, Widget child, ScrollableDetails details) {
327 if (scrollbars) {
328 return delegate.buildScrollbar(context, child, details);
329 }
330 return child;
331 }
332
333 @override
334 ScrollBehavior copyWith({
335 bool? scrollbars,
336 bool? overscroll,
337 Set<PointerDeviceKind>? dragDevices,
338 MultitouchDragStrategy? multitouchDragStrategy,
339 Set<LogicalKeyboardKey>? pointerAxisModifiers,
340 ScrollPhysics? physics,
341 TargetPlatform? platform,
342 ScrollViewKeyboardDismissBehavior? keyboardDismissBehavior,
343 }) {
344 return delegate.copyWith(
345 scrollbars: scrollbars ?? this.scrollbars,
346 overscroll: overscroll ?? this.overscroll,
347 dragDevices: dragDevices ?? this.dragDevices,
348 multitouchDragStrategy: multitouchDragStrategy ?? this.multitouchDragStrategy,
349 pointerAxisModifiers: pointerAxisModifiers ?? this.pointerAxisModifiers,
350 physics: physics ?? this.physics,
351 platform: platform ?? this.platform,
352 keyboardDismissBehavior: keyboardDismissBehavior ?? this.keyboardDismissBehavior,
353 );
354 }
355
356 @override
357 TargetPlatform getPlatform(BuildContext context) {
358 return platform ?? delegate.getPlatform(context);
359 }
360
361 @override
362 ScrollPhysics getScrollPhysics(BuildContext context) {
363 return physics ?? delegate.getScrollPhysics(context);
364 }
365
366 @override
367 ScrollViewKeyboardDismissBehavior getKeyboardDismissBehavior(BuildContext context) {
368 return keyboardDismissBehavior ?? delegate.getKeyboardDismissBehavior(context);
369 }
370
371 @override
372 bool shouldNotify(_WrappedScrollBehavior oldDelegate) {
373 return oldDelegate.delegate.runtimeType != delegate.runtimeType ||
374 oldDelegate.scrollbars != scrollbars ||
375 oldDelegate.overscroll != overscroll ||
376 !setEquals<PointerDeviceKind>(oldDelegate.dragDevices, dragDevices) ||
377 oldDelegate.multitouchDragStrategy != multitouchDragStrategy ||
378 !setEquals<LogicalKeyboardKey>(oldDelegate.pointerAxisModifiers, pointerAxisModifiers) ||
379 oldDelegate.physics != physics ||
380 oldDelegate.platform != platform ||
381 delegate.shouldNotify(oldDelegate.delegate);
382 }
383
384 @override
385 GestureVelocityTrackerBuilder velocityTrackerBuilder(BuildContext context) {
386 return delegate.velocityTrackerBuilder(context);
387 }
388
389 @override
390 String toString() => objectRuntimeType(this, '_WrappedScrollBehavior');
391}
392
393/// Controls how [Scrollable] widgets behave in a subtree.
394///
395/// The scroll configuration determines the [ScrollPhysics] and viewport
396/// decorations used by descendants of [child].
397class ScrollConfiguration extends InheritedWidget {
398 /// Creates a widget that controls how [Scrollable] widgets behave in a subtree.
399 const ScrollConfiguration({super.key, required this.behavior, required super.child});
400
401 /// How [Scrollable] widgets that are descendants of [child] should behave.
402 final ScrollBehavior behavior;
403
404 /// The [ScrollBehavior] for [Scrollable] widgets in the given [BuildContext].
405 ///
406 /// If no [ScrollConfiguration] widget is in scope of the given `context`,
407 /// a default [ScrollBehavior] instance is returned.
408 static ScrollBehavior of(BuildContext context) {
409 final ScrollConfiguration? configuration =
410 context.dependOnInheritedWidgetOfExactType<ScrollConfiguration>();
411 return configuration?.behavior ?? const ScrollBehavior();
412 }
413
414 @override
415 bool updateShouldNotify(ScrollConfiguration oldWidget) {
416 return behavior.runtimeType != oldWidget.behavior.runtimeType ||
417 (behavior != oldWidget.behavior && behavior.shouldNotify(oldWidget.behavior));
418 }
419
420 @override
421 void debugFillProperties(DiagnosticPropertiesBuilder properties) {
422 super.debugFillProperties(properties);
423 properties.add(DiagnosticsProperty<ScrollBehavior>('behavior', behavior));
424 }
425}
426

Provided by KDAB

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