1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/rendering.dart';
import 'framework.dart';
import 'overscroll_indicator.dart';
import 'scroll_physics.dart';
import 'scrollable.dart';
import 'scrollbar.dart';
const Color _kDefaultGlowColor = Color(0xFFFFFFFF);
/// Device types that scrollables should accept drag gestures from by default.
const Set<PointerDeviceKind> _kTouchLikeDeviceTypes = <PointerDeviceKind>{
PointerDeviceKind.touch,
PointerDeviceKind.stylus,
PointerDeviceKind.invertedStylus,
};
/// Describes how [Scrollable] widgets should behave.
///
/// {@template flutter.widgets.scrollBehavior}
/// Used by [ScrollConfiguration] to configure the [Scrollable] widgets in a
/// subtree.
///
/// This class can be extended to further customize a [ScrollBehavior] for a
/// subtree. For example, overriding [ScrollBehavior.getScrollPhysics] sets the
/// default [ScrollPhysics] for [Scrollable]s that inherit this [ScrollConfiguration].
/// Overriding [ScrollBehavior.buildOverscrollIndicator] can be used to add or change
/// the default [GlowingOverscrollIndicator] decoration, while
/// [ScrollBehavior.buildScrollbar] can be changed to modify the default [Scrollbar].
///
/// When looking to easily toggle the default decorations, you can use
/// [ScrollBehavior.copyWith] instead of creating your own [ScrollBehavior] class.
/// The `scrollbar` and `overscrollIndicator` flags can turn these decorations off.
/// {@endtemplate}
///
/// See also:
///
/// * [ScrollConfiguration], the inherited widget that controls how
/// [Scrollable] widgets behave in a subtree.
@immutable
class ScrollBehavior {
/// Creates a description of how [Scrollable] widgets should behave.
const ScrollBehavior();
/// Creates a copy of this ScrollBehavior, making it possible to
/// easily toggle `scrollbar` and `overscrollIndicator` effects.
///
/// This is used by widgets like [PageView] and [ListWheelScrollView] to
/// override the current [ScrollBehavior] and manage how they are decorated.
/// Widgets such as these have the option to provide a [ScrollBehavior] on
/// the widget level, like [PageView.scrollBehavior], in order to change the
/// default.
ScrollBehavior copyWith({
bool scrollbars = true,
bool overscroll = true,
Set<PointerDeviceKind>? dragDevices,
ScrollPhysics? physics,
TargetPlatform? platform,
}) {
return _WrappedScrollBehavior(
delegate: this,
scrollbar: scrollbars,
overscrollIndicator: overscroll,
physics: physics,
platform: platform,
dragDevices: dragDevices,
);
}
/// The platform whose scroll physics should be implemented.
///
/// Defaults to the current platform.
TargetPlatform getPlatform(BuildContext context) => defaultTargetPlatform;
/// The device kinds that the scrollable will accept drag gestures from.
///
/// By default only [PointerDeviceKind.touch], [PointerDeviceKind.stylus], and
/// [PointerDeviceKind.invertedStylus] are configured to create drag gestures.
/// Enabling this for [PointerDeviceKind.mouse] will make it difficult or
/// impossible to select text in scrollable containers and is not recommended.
Set<PointerDeviceKind> get dragDevices => _kTouchLikeDeviceTypes;
/// Wraps the given widget, which scrolls in the given [AxisDirection].
///
/// For example, on Android, this method wraps the given widget with a
/// [GlowingOverscrollIndicator] to provide visual feedback when the user
/// overscrolls.
///
/// This method is deprecated. Use [ScrollBehavior.buildOverscrollIndicator]
/// instead.
@Deprecated(
'Migrate to buildOverscrollIndicator. '
'This feature was deprecated after v2.1.0-11.0.pre.',
)
Widget buildViewportChrome(BuildContext context, Widget child, AxisDirection axisDirection) {
switch (getPlatform(context)) {
case TargetPlatform.iOS:
case TargetPlatform.linux:
case TargetPlatform.macOS:
case TargetPlatform.windows:
return child;
case TargetPlatform.android:
case TargetPlatform.fuchsia:
return GlowingOverscrollIndicator(
axisDirection: axisDirection,
color: _kDefaultGlowColor,
child: child,
);
}
}
/// Applies a [RawScrollbar] to the child widget on desktop platforms.
Widget buildScrollbar(BuildContext context, Widget child, ScrollableDetails details) {
// When modifying this function, consider modifying the implementation in
// the Material and Cupertino subclasses as well.
switch (getPlatform(context)) {
case TargetPlatform.linux:
case TargetPlatform.macOS:
case TargetPlatform.windows:
return RawScrollbar(
controller: details.controller,
child: child,
);
case TargetPlatform.android:
case TargetPlatform.fuchsia:
case TargetPlatform.iOS:
return child;
}
}
/// Applies a [GlowingOverscrollIndicator] to the child widget on
/// [TargetPlatform.android] and [TargetPlatform.fuchsia].
Widget buildOverscrollIndicator(BuildContext context, Widget child, ScrollableDetails details) {
// TODO(Piinks): Move implementation from buildViewportChrome here after
// deprecation period
// When modifying this function, consider modifying the implementation in
// the Material and Cupertino subclasses as well.
return buildViewportChrome(context, child, details.direction);
}
/// Specifies the type of velocity tracker to use in the descendant
/// [Scrollable]s' drag gesture recognizers, for estimating the velocity of a
/// drag gesture.
///
/// This can be used to, for example, apply different fling velocity
/// estimation methods on different platforms, in order to match the
/// platform's native behavior.
///
/// Typically, the provided [GestureVelocityTrackerBuilder] should return a
/// fresh velocity tracker. If null is returned, [Scrollable] creates a new
/// [VelocityTracker] to track the newly added pointer that may develop into
/// a drag gesture.
///
/// The default implementation provides a new
/// [IOSScrollViewFlingVelocityTracker] on iOS and macOS for each new pointer,
/// and a new [VelocityTracker] on other platforms for each new pointer.
GestureVelocityTrackerBuilder velocityTrackerBuilder(BuildContext context) {
switch (getPlatform(context)) {
case TargetPlatform.iOS:
case TargetPlatform.macOS:
return (PointerEvent event) => IOSScrollViewFlingVelocityTracker(event.kind);
case TargetPlatform.android:
case TargetPlatform.fuchsia:
case TargetPlatform.linux:
case TargetPlatform.windows:
return (PointerEvent event) => VelocityTracker.withKind(event.kind);
}
}
static const ScrollPhysics _bouncingPhysics = BouncingScrollPhysics(parent: RangeMaintainingScrollPhysics());
static const ScrollPhysics _clampingPhysics = ClampingScrollPhysics(parent: RangeMaintainingScrollPhysics());
/// The scroll physics to use for the platform given by [getPlatform].
///
/// Defaults to [RangeMaintainingScrollPhysics] mixed with
/// [BouncingScrollPhysics] on iOS and [ClampingScrollPhysics] on
/// Android.
ScrollPhysics getScrollPhysics(BuildContext context) {
switch (getPlatform(context)) {
case TargetPlatform.iOS:
case TargetPlatform.macOS:
return _bouncingPhysics;
case TargetPlatform.android:
case TargetPlatform.fuchsia:
case TargetPlatform.linux:
case TargetPlatform.windows:
return _clampingPhysics;
}
}
/// Called whenever a [ScrollConfiguration] is rebuilt with a new
/// [ScrollBehavior] of the same [runtimeType].
///
/// If the new instance represents different information than the old
/// instance, then the method should return true, otherwise it should return
/// false.
///
/// If this method returns true, all the widgets that inherit from the
/// [ScrollConfiguration] will rebuild using the new [ScrollBehavior]. If this
/// method returns false, the rebuilds might be optimized away.
bool shouldNotify(covariant ScrollBehavior oldDelegate) => false;
@override
String toString() => objectRuntimeType(this, 'ScrollBehavior');
}
class _WrappedScrollBehavior implements ScrollBehavior {
const _WrappedScrollBehavior({
required this.delegate,
this.scrollbar = true,
this.overscrollIndicator = true,
this.physics,
this.platform,
Set<PointerDeviceKind>? dragDevices,
}) : _dragDevices = dragDevices;
final ScrollBehavior delegate;
final bool scrollbar;
final bool overscrollIndicator;
final ScrollPhysics? physics;
final TargetPlatform? platform;
final Set<PointerDeviceKind>? _dragDevices;
@override
Set<PointerDeviceKind> get dragDevices => _dragDevices ?? delegate.dragDevices;
@override
Widget buildOverscrollIndicator(BuildContext context, Widget child, ScrollableDetails details) {
if (overscrollIndicator)
return delegate.buildOverscrollIndicator(context, child, details);
return child;
}
@override
Widget buildScrollbar(BuildContext context, Widget child, ScrollableDetails details) {
if (scrollbar)
return delegate.buildScrollbar(context, child, details);
return child;
}
@override
Widget buildViewportChrome(BuildContext context, Widget child, AxisDirection axisDirection) {
return delegate.buildViewportChrome(context, child, axisDirection);
}
@override
ScrollBehavior copyWith({
bool scrollbars = true,
bool overscroll = true,
ScrollPhysics? physics,
TargetPlatform? platform,
Set<PointerDeviceKind>? dragDevices,
}) {
return delegate.copyWith(
scrollbars: scrollbars,
overscroll: overscroll,
physics: physics,
platform: platform,
dragDevices: dragDevices,
);
}
@override
TargetPlatform getPlatform(BuildContext context) {
return platform ?? delegate.getPlatform(context);
}
@override
ScrollPhysics getScrollPhysics(BuildContext context) {
return physics ?? delegate.getScrollPhysics(context);
}
@override
bool shouldNotify(_WrappedScrollBehavior oldDelegate) {
return oldDelegate.delegate.runtimeType != delegate.runtimeType
|| oldDelegate.scrollbar != scrollbar
|| oldDelegate.overscrollIndicator != overscrollIndicator
|| oldDelegate.physics != physics
|| oldDelegate.platform != platform
|| setEquals<PointerDeviceKind>(oldDelegate.dragDevices, dragDevices)
|| delegate.shouldNotify(oldDelegate.delegate);
}
@override
GestureVelocityTrackerBuilder velocityTrackerBuilder(BuildContext context) {
return delegate.velocityTrackerBuilder(context);
}
@override
String toString() => objectRuntimeType(this, '_WrappedScrollBehavior');
}
/// Controls how [Scrollable] widgets behave in a subtree.
///
/// The scroll configuration determines the [ScrollPhysics] and viewport
/// decorations used by descendants of [child].
class ScrollConfiguration extends InheritedWidget {
/// Creates a widget that controls how [Scrollable] widgets behave in a subtree.
///
/// The [behavior] and [child] arguments must not be null.
const ScrollConfiguration({
Key? key,
required this.behavior,
required Widget child,
}) : super(key: key, child: child);
/// How [Scrollable] widgets that are descendants of [child] should behave.
final ScrollBehavior behavior;
/// The [ScrollBehavior] for [Scrollable] widgets in the given [BuildContext].
///
/// If no [ScrollConfiguration] widget is in scope of the given `context`,
/// a default [ScrollBehavior] instance is returned.
static ScrollBehavior of(BuildContext context) {
final ScrollConfiguration? configuration = context.dependOnInheritedWidgetOfExactType<ScrollConfiguration>();
return configuration?.behavior ?? const ScrollBehavior();
}
@override
bool updateShouldNotify(ScrollConfiguration oldWidget) {
assert(behavior != null);
return behavior.runtimeType != oldWidget.behavior.runtimeType
|| (behavior != oldWidget.behavior && behavior.shouldNotify(oldWidget.behavior));
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.add(DiagnosticsProperty<ScrollBehavior>('behavior', behavior));
}
}