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 {
this.shortcuts,
this.actions,
this.restorationScopeId,
this.scrollBehavior,
}) : assert(routes != null),
assert(navigatorObservers != null),
assert(title != null),
......@@ -207,6 +208,7 @@ class CupertinoApp extends StatefulWidget {
this.shortcuts,
this.actions,
this.restorationScopeId,
this.scrollBehavior,
}) : assert(title != null),
assert(showPerformanceOverlay != null),
assert(checkerboardRasterCacheImages != null),
......@@ -393,6 +395,16 @@ class CupertinoApp extends StatefulWidget {
/// {@macro flutter.widgets.widgetsApp.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
_CupertinoAppState createState() => _CupertinoAppState();
......@@ -403,7 +415,18 @@ class CupertinoApp extends StatefulWidget {
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
Widget buildViewportChrome(BuildContext context, Widget child, AxisDirection axisDirection) {
// Never build any overscroll glow indicators.
......@@ -521,7 +544,7 @@ class _CupertinoAppState extends State<CupertinoApp> {
final CupertinoThemeData effectiveThemeData = widget.theme ?? const CupertinoThemeData();
return ScrollConfiguration(
behavior: _AlwaysCupertinoScrollBehavior(),
behavior: widget.scrollBehavior ?? CupertinoScrollBehavior(),
child: CupertinoUserInterfaceLevel(
data: CupertinoUserInterfaceLevelData.base,
child: CupertinoTheme(
......
......@@ -196,6 +196,7 @@ class MaterialApp extends StatefulWidget {
this.shortcuts,
this.actions,
this.restorationScopeId,
this.scrollBehavior,
}) : assert(routes != null),
assert(navigatorObservers != null),
assert(title != null),
......@@ -242,6 +243,7 @@ class MaterialApp extends StatefulWidget {
this.shortcuts,
this.actions,
this.restorationScopeId,
this.scrollBehavior,
}) : assert(routeInformationParser != null),
assert(routerDelegate != null),
assert(title != null),
......@@ -632,6 +634,23 @@ class MaterialApp extends StatefulWidget {
/// {@macro flutter.widgets.widgetsApp.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
/// Material apps.
///
......@@ -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
TargetPlatform getPlatform(BuildContext context) {
return Theme.of(context).platform;
......@@ -850,7 +880,7 @@ class _MaterialAppState extends State<MaterialApp> {
}());
return ScrollConfiguration(
behavior: _MaterialScrollBehavior(),
behavior: widget.scrollBehavior ?? MaterialScrollBehavior(),
child: HeroControllerScope(
controller: _heroController,
child: result,
......
......@@ -14,8 +14,21 @@ const Color _kDefaultGlowColor = Color(0xFFFFFFFF);
/// 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.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
class ScrollBehavior {
/// Creates a description of how [Scrollable] widgets should behave.
......@@ -33,7 +46,7 @@ class ScrollBehavior {
/// overscrolls.
Widget buildViewportChrome(BuildContext context, Widget child, AxisDirection axisDirection) {
// When modifying this function, consider modifying the implementation in
// _MaterialScrollBehavior as well.
// MaterialScrollBehavior as well.
switch (getPlatform(context)) {
case TargetPlatform.iOS:
case TargetPlatform.linux:
......
......@@ -178,6 +178,44 @@ void main() {
await tester.pumpAndSettle();
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);
......
......@@ -1026,6 +1026,44 @@ void main() {
));
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 {
......
......@@ -308,6 +308,25 @@ void main() {
));
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);
......
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