Unverified Commit 38fd5af5 authored by Kate Lovett's avatar Kate Lovett Committed by GitHub

Exposing ScrollBehaviors for app-wide settings (#76739)

parent aee756ff
...@@ -168,6 +168,7 @@ class CupertinoApp extends StatefulWidget { ...@@ -168,6 +168,7 @@ class CupertinoApp extends StatefulWidget {
this.shortcuts, this.shortcuts,
this.actions, this.actions,
this.restorationScopeId, this.restorationScopeId,
this.scrollBehavior,
}) : assert(routes != null), }) : assert(routes != null),
assert(navigatorObservers != null), assert(navigatorObservers != null),
assert(title != null), assert(title != null),
...@@ -207,6 +208,7 @@ class CupertinoApp extends StatefulWidget { ...@@ -207,6 +208,7 @@ class CupertinoApp extends StatefulWidget {
this.shortcuts, this.shortcuts,
this.actions, this.actions,
this.restorationScopeId, this.restorationScopeId,
this.scrollBehavior,
}) : assert(title != null), }) : assert(title != null),
assert(showPerformanceOverlay != null), assert(showPerformanceOverlay != null),
assert(checkerboardRasterCacheImages != null), assert(checkerboardRasterCacheImages != null),
...@@ -393,6 +395,16 @@ class CupertinoApp extends StatefulWidget { ...@@ -393,6 +395,16 @@ class CupertinoApp extends StatefulWidget {
/// {@macro flutter.widgets.widgetsApp.restorationScopeId} /// {@macro flutter.widgets.widgetsApp.restorationScopeId}
final String? restorationScopeId; final String? restorationScopeId;
/// {@macro flutter.material.materialApp.scrollBehavior}
///
/// When null, defaults to [CupertinoScrollBehavior].
///
/// See also:
///
/// * [ScrollConfiguration], which controls how [Scrollable] widgets behave
/// in a subtree.
final ScrollBehavior? scrollBehavior;
@override @override
_CupertinoAppState createState() => _CupertinoAppState(); _CupertinoAppState createState() => _CupertinoAppState();
...@@ -403,7 +415,18 @@ class CupertinoApp extends StatefulWidget { ...@@ -403,7 +415,18 @@ class CupertinoApp extends StatefulWidget {
HeroController(); // Linear tweening. HeroController(); // Linear tweening.
} }
class _AlwaysCupertinoScrollBehavior extends ScrollBehavior { /// Describes how [Scrollable] widgets behave for [CupertinoApp]s.
///
/// {@macro flutter.widgets.scrollBehavior}
///
/// Setting a [CupertinoScrollBehavior] will result in descendant [Scrollable] widgets
/// using [BouncingScrollPhysics] by default. No [GlowingOverscrollIndicator] is
/// applied when using a [CupertinoScrollBehavior] either, regardless of platform.
///
/// See also:
///
/// * [ScrollBehavior], the default scrolling behavior extended by this class.
class CupertinoScrollBehavior extends ScrollBehavior {
@override @override
Widget buildViewportChrome(BuildContext context, Widget child, AxisDirection axisDirection) { Widget buildViewportChrome(BuildContext context, Widget child, AxisDirection axisDirection) {
// Never build any overscroll glow indicators. // Never build any overscroll glow indicators.
...@@ -521,7 +544,7 @@ class _CupertinoAppState extends State<CupertinoApp> { ...@@ -521,7 +544,7 @@ class _CupertinoAppState extends State<CupertinoApp> {
final CupertinoThemeData effectiveThemeData = widget.theme ?? const CupertinoThemeData(); final CupertinoThemeData effectiveThemeData = widget.theme ?? const CupertinoThemeData();
return ScrollConfiguration( return ScrollConfiguration(
behavior: _AlwaysCupertinoScrollBehavior(), behavior: widget.scrollBehavior ?? CupertinoScrollBehavior(),
child: CupertinoUserInterfaceLevel( child: CupertinoUserInterfaceLevel(
data: CupertinoUserInterfaceLevelData.base, data: CupertinoUserInterfaceLevelData.base,
child: CupertinoTheme( child: CupertinoTheme(
......
...@@ -196,6 +196,7 @@ class MaterialApp extends StatefulWidget { ...@@ -196,6 +196,7 @@ class MaterialApp extends StatefulWidget {
this.shortcuts, this.shortcuts,
this.actions, this.actions,
this.restorationScopeId, this.restorationScopeId,
this.scrollBehavior,
}) : assert(routes != null), }) : assert(routes != null),
assert(navigatorObservers != null), assert(navigatorObservers != null),
assert(title != null), assert(title != null),
...@@ -242,6 +243,7 @@ class MaterialApp extends StatefulWidget { ...@@ -242,6 +243,7 @@ class MaterialApp extends StatefulWidget {
this.shortcuts, this.shortcuts,
this.actions, this.actions,
this.restorationScopeId, this.restorationScopeId,
this.scrollBehavior,
}) : assert(routeInformationParser != null), }) : assert(routeInformationParser != null),
assert(routerDelegate != null), assert(routerDelegate != null),
assert(title != null), assert(title != null),
...@@ -632,6 +634,23 @@ class MaterialApp extends StatefulWidget { ...@@ -632,6 +634,23 @@ class MaterialApp extends StatefulWidget {
/// {@macro flutter.widgets.widgetsApp.restorationScopeId} /// {@macro flutter.widgets.widgetsApp.restorationScopeId}
final String? restorationScopeId; final String? restorationScopeId;
/// {@template flutter.material.materialApp.scrollBehavior}
/// The default [ScrollBehavior] for the application.
///
/// [ScrollBehavior]s describe how [Scrollable] widgets behave. Providing
/// a [ScrollBehavior] can set the default [ScrollPhysics] across
/// an application, and manage [Scrollable] decorations like [Scrollbar]s and
/// [GlowingOverscrollIndicator]s.
/// {@endtemplate}
///
/// When null, defaults to [MaterialScrollBehavior].
///
/// See also:
///
/// * [ScrollConfiguration], which controls how [Scrollable] widgets behave
/// in a subtree.
final ScrollBehavior? scrollBehavior;
/// Turns on a [GridPaper] overlay that paints a baseline grid /// Turns on a [GridPaper] overlay that paints a baseline grid
/// Material apps. /// Material apps.
/// ///
...@@ -657,7 +676,18 @@ class MaterialApp extends StatefulWidget { ...@@ -657,7 +676,18 @@ class MaterialApp extends StatefulWidget {
} }
} }
class _MaterialScrollBehavior extends ScrollBehavior { /// Describes how [Scrollable] widgets behave for [MaterialApp]s.
///
/// {@macro flutter.widgets.scrollBehavior}
///
/// Setting a [MaterialScrollBehavior] will apply a
/// [GlowingOverscrollIndicator] to [Scrollable] descendants when executing on
/// [TargetPlatform.android] and [TargetPlatform.fuchsia].
///
/// See also:
///
/// * [ScrollBehavior], the default scrolling behavior extended by this class.
class MaterialScrollBehavior extends ScrollBehavior {
@override @override
TargetPlatform getPlatform(BuildContext context) { TargetPlatform getPlatform(BuildContext context) {
return Theme.of(context).platform; return Theme.of(context).platform;
...@@ -850,7 +880,7 @@ class _MaterialAppState extends State<MaterialApp> { ...@@ -850,7 +880,7 @@ class _MaterialAppState extends State<MaterialApp> {
}()); }());
return ScrollConfiguration( return ScrollConfiguration(
behavior: _MaterialScrollBehavior(), behavior: widget.scrollBehavior ?? MaterialScrollBehavior(),
child: HeroControllerScope( child: HeroControllerScope(
controller: _heroController, controller: _heroController,
child: result, child: result,
......
...@@ -14,8 +14,21 @@ const Color _kDefaultGlowColor = Color(0xFFFFFFFF); ...@@ -14,8 +14,21 @@ const Color _kDefaultGlowColor = Color(0xFFFFFFFF);
/// Describes how [Scrollable] widgets should behave. /// Describes how [Scrollable] widgets should behave.
/// ///
/// {@template flutter.widgets.scrollBehavior}
/// Used by [ScrollConfiguration] to configure the [Scrollable] widgets in a /// Used by [ScrollConfiguration] to configure the [Scrollable] widgets in a
/// subtree. /// 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.buildViewportChrome] can be used to add or change
/// default decorations like [GlowingOverscrollIndicator]s.
/// {@endtemplate}
///
/// See also:
///
/// * [ScrollConfiguration], the inherited widget that controls how
/// [Scrollable] widgets behave in a subtree.
@immutable @immutable
class ScrollBehavior { class ScrollBehavior {
/// Creates a description of how [Scrollable] widgets should behave. /// Creates a description of how [Scrollable] widgets should behave.
...@@ -33,7 +46,7 @@ class ScrollBehavior { ...@@ -33,7 +46,7 @@ class ScrollBehavior {
/// overscrolls. /// overscrolls.
Widget buildViewportChrome(BuildContext context, Widget child, AxisDirection axisDirection) { Widget buildViewportChrome(BuildContext context, Widget child, AxisDirection axisDirection) {
// When modifying this function, consider modifying the implementation in // When modifying this function, consider modifying the implementation in
// _MaterialScrollBehavior as well. // MaterialScrollBehavior as well.
switch (getPlatform(context)) { switch (getPlatform(context)) {
case TargetPlatform.iOS: case TargetPlatform.iOS:
case TargetPlatform.linux: case TargetPlatform.linux:
......
...@@ -178,6 +178,44 @@ void main() { ...@@ -178,6 +178,44 @@ void main() {
await tester.pumpAndSettle(); await tester.pumpAndSettle();
expect(find.text('popped'), findsOneWidget); expect(find.text('popped'), findsOneWidget);
}); });
testWidgets('CupertinoApp has correct default ScrollBehavior', (WidgetTester tester) async {
late BuildContext capturedContext;
await tester.pumpWidget(
CupertinoApp(
home: Builder(
builder: (BuildContext context) {
capturedContext = context;
return const Placeholder();
},
),
),
);
expect(ScrollConfiguration.of(capturedContext).runtimeType, CupertinoScrollBehavior);
});
testWidgets('A ScrollBehavior can be set for CupertinoApp', (WidgetTester tester) async {
late BuildContext capturedContext;
await tester.pumpWidget(
CupertinoApp(
scrollBehavior: MockScrollBehavior(),
home: Builder(
builder: (BuildContext context) {
capturedContext = context;
return const Placeholder();
},
),
),
);
final ScrollBehavior scrollBehavior = ScrollConfiguration.of(capturedContext);
expect(scrollBehavior.runtimeType, MockScrollBehavior);
expect(scrollBehavior.getScrollPhysics(capturedContext).runtimeType, NeverScrollableScrollPhysics);
});
}
class MockScrollBehavior extends ScrollBehavior {
@override
ScrollPhysics getScrollPhysics(BuildContext context) => const NeverScrollableScrollPhysics();
} }
typedef SimpleRouterDelegateBuilder = Widget Function(BuildContext, RouteInformation); typedef SimpleRouterDelegateBuilder = Widget Function(BuildContext, RouteInformation);
......
...@@ -1026,6 +1026,44 @@ void main() { ...@@ -1026,6 +1026,44 @@ void main() {
)); ));
expect(builderChild, isNull); expect(builderChild, isNull);
}); });
testWidgets('MaterialApp has correct default ScrollBehavior', (WidgetTester tester) async {
late BuildContext capturedContext;
await tester.pumpWidget(
MaterialApp(
home: Builder(
builder: (BuildContext context) {
capturedContext = context;
return const Placeholder();
},
),
),
);
expect(ScrollConfiguration.of(capturedContext).runtimeType, MaterialScrollBehavior);
});
testWidgets('A ScrollBehavior can be set for MaterialApp', (WidgetTester tester) async {
late BuildContext capturedContext;
await tester.pumpWidget(
MaterialApp(
scrollBehavior: MockScrollBehavior(),
home: Builder(
builder: (BuildContext context) {
capturedContext = context;
return const Placeholder();
},
),
),
);
final ScrollBehavior scrollBehavior = ScrollConfiguration.of(capturedContext);
expect(scrollBehavior.runtimeType, MockScrollBehavior);
expect(scrollBehavior.getScrollPhysics(capturedContext).runtimeType, NeverScrollableScrollPhysics);
});
}
class MockScrollBehavior extends ScrollBehavior {
@override
ScrollPhysics getScrollPhysics(BuildContext context) => const NeverScrollableScrollPhysics();
} }
class MockAccessibilityFeature implements AccessibilityFeatures { class MockAccessibilityFeature implements AccessibilityFeatures {
......
...@@ -308,6 +308,25 @@ void main() { ...@@ -308,6 +308,25 @@ void main() {
)); ));
expect(find.text('/'), findsOneWidget); expect(find.text('/'), findsOneWidget);
}); });
testWidgets('WidgetsApp has correct default ScrollBehavior', (WidgetTester tester) async {
late BuildContext capturedContext;
await tester.pumpWidget(
WidgetsApp(
builder: (BuildContext context, Widget? child) {
capturedContext = context;
return const Placeholder();
},
color: const Color(0xFF123456),
),
);
expect(ScrollConfiguration.of(capturedContext).runtimeType, ScrollBehavior);
});
}
class MockScrollBehavior extends ScrollBehavior {
@override
ScrollPhysics getScrollPhysics(BuildContext context) => const NeverScrollableScrollPhysics();
} }
typedef SimpleRouterDelegateBuilder = Widget Function(BuildContext, RouteInformation); typedef SimpleRouterDelegateBuilder = Widget Function(BuildContext, RouteInformation);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment