• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright 2015 The Chromium 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
5import 'dart:ui' as ui;
6
7import 'package:flutter/cupertino.dart';
8import 'package:flutter/rendering.dart';
9import 'package:flutter/widgets.dart';
10
11import 'arc.dart';
12import 'colors.dart';
13import 'floating_action_button.dart';
14import 'icons.dart';
15import 'material_localizations.dart';
16import 'page.dart';
17import 'theme.dart';
18
19/// [MaterialApp] uses this [TextStyle] as its [DefaultTextStyle] to encourage
20/// developers to be intentional about their [DefaultTextStyle].
21///
22/// In Material Design, most [Text] widgets are contained in [Material] widgets,
23/// which sets a specific [DefaultTextStyle]. If you're seeing text that uses
24/// this text style, consider putting your text in a [Material] widget (or
25/// another widget that sets a [DefaultTextStyle]).
26const TextStyle _errorTextStyle = TextStyle(
27  color: Color(0xD0FF0000),
28  fontFamily: 'monospace',
29  fontSize: 48.0,
30  fontWeight: FontWeight.w900,
31  decoration: TextDecoration.underline,
32  decorationColor: Color(0xFFFFFF00),
33  decorationStyle: TextDecorationStyle.double,
34  debugLabel: 'fallback style; consider putting your text in a Material',
35);
36
37/// Describes which theme will be used by [MaterialApp].
38enum ThemeMode {
39  /// Use either the light or dark theme based on what the user has selected in
40  /// the system settings.
41  system,
42
43  /// Always use the light mode regardless of system preference.
44  light,
45
46  /// Always use the dark mode (if available) regardless of system preference.
47  dark,
48}
49
50/// An application that uses material design.
51///
52/// A convenience widget that wraps a number of widgets that are commonly
53/// required for material design applications. It builds upon a [WidgetsApp] by
54/// adding material-design specific functionality, such as [AnimatedTheme] and
55/// [GridPaper].
56///
57/// The [MaterialApp] configures the top-level [Navigator] to search for routes
58/// in the following order:
59///
60///  1. For the `/` route, the [home] property, if non-null, is used.
61///
62///  2. Otherwise, the [routes] table is used, if it has an entry for the route.
63///
64///  3. Otherwise, [onGenerateRoute] is called, if provided. It should return a
65///     non-null value for any _valid_ route not handled by [home] and [routes].
66///
67///  4. Finally if all else fails [onUnknownRoute] is called.
68///
69/// If a [Navigator] is created, at least one of these options must handle the
70/// `/` route, since it is used when an invalid [initialRoute] is specified on
71/// startup (e.g. by another application launching this one with an intent on
72/// Android; see [Window.defaultRouteName]).
73///
74/// This widget also configures the observer of the top-level [Navigator] (if
75/// any) to perform [Hero] animations.
76///
77/// If [home], [routes], [onGenerateRoute], and [onUnknownRoute] are all null,
78/// and [builder] is not null, then no [Navigator] is created.
79///
80/// {@tool sample}
81/// This example shows how to create a [MaterialApp] that disables the "debug"
82/// banner with a [home] route that will be displayed when the app is launched.
83///
84/// ![A screenshot of the MaterialApp class with a home Scaffold](https://flutter.github.io/assets-for-api-docs/assets/material/basic_material_app.png)
85///
86/// ```dart
87/// MaterialApp(
88///   home: Scaffold(
89///     appBar: AppBar(
90///       title: const Text('Home'),
91///     ),
92///   ),
93///   debugShowCheckedModeBanner: false,
94/// )
95/// ```
96/// {@end-tool}
97///
98/// {@tool sample}
99/// This example shows how to create a [MaterialApp] that uses the [routes]
100/// `Map` to define the "home" route and an "about" route.
101///
102/// ```dart
103/// MaterialApp(
104///   routes: <String, WidgetBuilder>{
105///     '/': (BuildContext context) {
106///       return Scaffold(
107///         appBar: AppBar(
108///           title: const Text('Home Route'),
109///         ),
110///       );
111///     },
112///     '/about': (BuildContext context) {
113///       return Scaffold(
114///         appBar: AppBar(
115///           title: const Text('About Route'),
116///         ),
117///       );
118///      }
119///    },
120/// )
121/// ```
122/// {@end-tool}
123///
124/// {@tool sample}
125/// This example shows how to create a [MaterialApp] that defines a [theme] that
126/// will be used for material widgets in the app.
127///
128/// ![A screenshot of the MaterialApp class with a custom theme](https://flutter.github.io/assets-for-api-docs/assets/material/theme_material_app.png)
129///
130/// ```dart
131/// MaterialApp(
132///   theme: ThemeData(
133///     brightness: Brightness.dark,
134///     primaryColor: Colors.blueGrey
135///   ),
136///   home: Scaffold(
137///     appBar: AppBar(
138///       title: const Text('MaterialApp Theme'),
139///     ),
140///   ),
141/// )
142/// ```
143/// {@end-tool}
144///
145/// See also:
146///
147///  * [Scaffold], which provides standard app elements like an [AppBar] and a [Drawer].
148///  * [Navigator], which is used to manage the app's stack of pages.
149///  * [MaterialPageRoute], which defines an app page that transitions in a material-specific way.
150///  * [WidgetsApp], which defines the basic app elements but does not depend on the material library.
151///  * The Flutter Internationalization Tutorial,
152///    <https://flutter.dev/tutorials/internationalization/>.
153class MaterialApp extends StatefulWidget {
154  /// Creates a MaterialApp.
155  ///
156  /// At least one of [home], [routes], [onGenerateRoute], or [builder] must be
157  /// non-null. If only [routes] is given, it must include an entry for the
158  /// [Navigator.defaultRouteName] (`/`), since that is the route used when the
159  /// application is launched with an intent that specifies an otherwise
160  /// unsupported route.
161  ///
162  /// This class creates an instance of [WidgetsApp].
163  ///
164  /// The boolean arguments, [routes], and [navigatorObservers], must not be null.
165  const MaterialApp({
166    Key key,
167    this.navigatorKey,
168    this.home,
169    this.routes = const <String, WidgetBuilder>{},
170    this.initialRoute,
171    this.onGenerateRoute,
172    this.onUnknownRoute,
173    this.navigatorObservers = const <NavigatorObserver>[],
174    this.builder,
175    this.title = '',
176    this.onGenerateTitle,
177    this.color,
178    this.theme,
179    this.darkTheme,
180    this.themeMode = ThemeMode.system,
181    this.locale,
182    this.localizationsDelegates,
183    this.localeListResolutionCallback,
184    this.localeResolutionCallback,
185    this.supportedLocales = const <Locale>[Locale('en', 'US')],
186    this.debugShowMaterialGrid = false,
187    this.showPerformanceOverlay = false,
188    this.checkerboardRasterCacheImages = false,
189    this.checkerboardOffscreenLayers = false,
190    this.showSemanticsDebugger = false,
191    this.debugShowCheckedModeBanner = true,
192  }) : assert(routes != null),
193       assert(navigatorObservers != null),
194       assert(title != null),
195       assert(debugShowMaterialGrid != null),
196       assert(showPerformanceOverlay != null),
197       assert(checkerboardRasterCacheImages != null),
198       assert(checkerboardOffscreenLayers != null),
199       assert(showSemanticsDebugger != null),
200       assert(debugShowCheckedModeBanner != null),
201       super(key: key);
202
203  /// {@macro flutter.widgets.widgetsApp.navigatorKey}
204  final GlobalKey<NavigatorState> navigatorKey;
205
206  /// {@macro flutter.widgets.widgetsApp.home}
207  final Widget home;
208
209  /// The application's top-level routing table.
210  ///
211  /// When a named route is pushed with [Navigator.pushNamed], the route name is
212  /// looked up in this map. If the name is present, the associated
213  /// [WidgetBuilder] is used to construct a [MaterialPageRoute] that performs
214  /// an appropriate transition, including [Hero] animations, to the new route.
215  ///
216  /// {@macro flutter.widgets.widgetsApp.routes}
217  final Map<String, WidgetBuilder> routes;
218
219  /// {@macro flutter.widgets.widgetsApp.initialRoute}
220  final String initialRoute;
221
222  /// {@macro flutter.widgets.widgetsApp.onGenerateRoute}
223  final RouteFactory onGenerateRoute;
224
225  /// {@macro flutter.widgets.widgetsApp.onUnknownRoute}
226  final RouteFactory onUnknownRoute;
227
228  /// {@macro flutter.widgets.widgetsApp.navigatorObservers}
229  final List<NavigatorObserver> navigatorObservers;
230
231  /// {@macro flutter.widgets.widgetsApp.builder}
232  ///
233  /// Material specific features such as [showDialog] and [showMenu], and widgets
234  /// such as [Tooltip], [PopupMenuButton], also require a [Navigator] to properly
235  /// function.
236  final TransitionBuilder builder;
237
238  /// {@macro flutter.widgets.widgetsApp.title}
239  ///
240  /// This value is passed unmodified to [WidgetsApp.title].
241  final String title;
242
243  /// {@macro flutter.widgets.widgetsApp.onGenerateTitle}
244  ///
245  /// This value is passed unmodified to [WidgetsApp.onGenerateTitle].
246  final GenerateAppTitle onGenerateTitle;
247
248  /// Default visual properties, like colors fonts and shapes, for this app's
249  /// material widgets.
250  ///
251  /// A second [darkTheme] [ThemeData] value, which is used to provide a dark
252  /// version of the user interface can also be specified. [themeMode] will
253  /// control which theme will be used if a [darkTheme] is provided.
254  ///
255  /// The default value of this property is the value of [ThemeData.light()].
256  ///
257  /// See also:
258  ///
259  ///  * [themeMode], which controls which theme to use.
260  ///  * [MediaQueryData.platformBrightness], which indicates the platform's
261  ///    desired brightness and is used to automatically toggle between [theme]
262  ///    and [darkTheme] in [MaterialApp].
263  ///  * [ThemeData.brightness], which indicates the [Brightness] of a theme's
264  ///    colors.
265  final ThemeData theme;
266
267  /// The [ThemeData] to use when a 'dark mode' is requested by the system.
268  ///
269  /// Some host platforms allow the users to select a system-wide 'dark mode',
270  /// or the application may want to offer the user the ability to choose a
271  /// dark theme just for this application. This is theme that will be used for
272  /// such cases. [themeMode] will control which theme will be used.
273  ///
274  /// This theme should have a [ThemeData.brightness] set to [Brightness.dark].
275  ///
276  /// Uses [theme] instead when null. Defaults to the value of
277  /// [ThemeData.light()] when both [darkTheme] and [theme] are null.
278  ///
279  /// See also:
280  ///
281  ///  * [themeMode], which controls which theme to use.
282  ///  * [MediaQueryData.platformBrightness], which indicates the platform's
283  ///    desired brightness and is used to automatically toggle between [theme]
284  ///    and [darkTheme] in [MaterialApp].
285  ///  * [ThemeData.brightness], which is typically set to the value of
286  ///    [MediaQueryData.platformBrightness].
287  final ThemeData darkTheme;
288
289  /// Determines which theme will be used by the application if both [theme]
290  /// and [darkTheme] are provided.
291  ///
292  /// If set to [ThemeMode.system], the choice of which theme to use will
293  /// be based on the user's system preferences. If the [MediaQuery.platformBrightnessOf]
294  /// is [Brightness.light], [theme] will be used. If it is [Brightness.dark],
295  /// [darkTheme] will be used (unless it is [null], in which case [theme]
296  /// will be used.
297  ///
298  /// If set to [ThemeMode.light] the [theme] will always be used,
299  /// regardless of the user's system preference.
300  ///
301  /// If set to [ThemeMode.dark] the [darkTheme] will be used
302  /// regardless of the user's system preference. If [darkTheme] is [null]
303  /// then it will fallback to using [theme].
304  ///
305  /// The default value is [ThemeMode.system].
306  ///
307  /// See also:
308  ///
309  ///   * [theme], which is used when a light mode is selected.
310  ///   * [darkTheme], which is used when a dark mode is selected.
311  ///   * [ThemeData.brightness], which indicates to various parts of the
312  ///     system what kind of theme is being used.
313  final ThemeMode themeMode;
314
315  /// {@macro flutter.widgets.widgetsApp.color}
316  final Color color;
317
318  /// {@macro flutter.widgets.widgetsApp.locale}
319  final Locale locale;
320
321  /// {@macro flutter.widgets.widgetsApp.localizationsDelegates}
322  ///
323  /// Internationalized apps that require translations for one of the locales
324  /// listed in [GlobalMaterialLocalizations] should specify this parameter
325  /// and list the [supportedLocales] that the application can handle.
326  ///
327  /// ```dart
328  /// import 'package:flutter_localizations/flutter_localizations.dart';
329  /// MaterialApp(
330  ///   localizationsDelegates: [
331  ///     // ... app-specific localization delegate[s] here
332  ///     GlobalMaterialLocalizations.delegate,
333  ///     GlobalWidgetsLocalizations.delegate,
334  ///   ],
335  ///   supportedLocales: [
336  ///     const Locale('en', 'US'), // English
337  ///     const Locale('he', 'IL'), // Hebrew
338  ///     // ... other locales the app supports
339  ///   ],
340  ///   // ...
341  /// )
342  /// ```
343  ///
344  /// ## Adding localizations for a new locale
345  ///
346  /// The information that follows applies to the unusual case of an app
347  /// adding translations for a language not already supported by
348  /// [GlobalMaterialLocalizations].
349  ///
350  /// Delegates that produce [WidgetsLocalizations] and [MaterialLocalizations]
351  /// are included automatically. Apps can provide their own versions of these
352  /// localizations by creating implementations of
353  /// [LocalizationsDelegate<WidgetsLocalizations>] or
354  /// [LocalizationsDelegate<MaterialLocalizations>] whose load methods return
355  /// custom versions of [WidgetsLocalizations] or [MaterialLocalizations].
356  ///
357  /// For example: to add support to [MaterialLocalizations] for a
358  /// locale it doesn't already support, say `const Locale('foo', 'BR')`,
359  /// one could just extend [DefaultMaterialLocalizations]:
360  ///
361  /// ```dart
362  /// class FooLocalizations extends DefaultMaterialLocalizations {
363  ///   FooLocalizations(Locale locale) : super(locale);
364  ///   @override
365  ///   String get okButtonLabel {
366  ///     if (locale == const Locale('foo', 'BR'))
367  ///       return 'foo';
368  ///     return super.okButtonLabel;
369  ///   }
370  /// }
371  ///
372  /// ```
373  ///
374  /// A `FooLocalizationsDelegate` is essentially just a method that constructs
375  /// a `FooLocalizations` object. We return a [SynchronousFuture] here because
376  /// no asynchronous work takes place upon "loading" the localizations object.
377  ///
378  /// ```dart
379  /// class FooLocalizationsDelegate extends LocalizationsDelegate<MaterialLocalizations> {
380  ///   const FooLocalizationsDelegate();
381  ///   @override
382  ///   Future<FooLocalizations> load(Locale locale) {
383  ///     return SynchronousFuture(FooLocalizations(locale));
384  ///   }
385  ///   @override
386  ///   bool shouldReload(FooLocalizationsDelegate old) => false;
387  /// }
388  /// ```
389  ///
390  /// Constructing a [MaterialApp] with a `FooLocalizationsDelegate` overrides
391  /// the automatically included delegate for [MaterialLocalizations] because
392  /// only the first delegate of each [LocalizationsDelegate.type] is used and
393  /// the automatically included delegates are added to the end of the app's
394  /// [localizationsDelegates] list.
395  ///
396  /// ```dart
397  /// MaterialApp(
398  ///   localizationsDelegates: [
399  ///     const FooLocalizationsDelegate(),
400  ///   ],
401  ///   // ...
402  /// )
403  /// ```
404  /// See also:
405  ///
406  ///  * [supportedLocales], which must be specified along with
407  ///    [localizationsDelegates].
408  ///  * [GlobalMaterialLocalizations], a [localizationsDelegates] value
409  ///    which provides material localizations for many languages.
410  ///  * The Flutter Internationalization Tutorial,
411  ///    <https://flutter.dev/tutorials/internationalization/>.
412  final Iterable<LocalizationsDelegate<dynamic>> localizationsDelegates;
413
414  /// {@macro flutter.widgets.widgetsApp.localeListResolutionCallback}
415  ///
416  /// This callback is passed along to the [WidgetsApp] built by this widget.
417  final LocaleListResolutionCallback localeListResolutionCallback;
418
419  /// {@macro flutter.widgets.widgetsApp.localeResolutionCallback}
420  ///
421  /// This callback is passed along to the [WidgetsApp] built by this widget.
422  final LocaleResolutionCallback localeResolutionCallback;
423
424  /// {@macro flutter.widgets.widgetsApp.supportedLocales}
425  ///
426  /// It is passed along unmodified to the [WidgetsApp] built by this widget.
427  ///
428  /// See also:
429  ///
430  ///  * [localizationsDelegates], which must be specified for localized
431  ///    applications.
432  ///  * [GlobalMaterialLocalizations], a [localizationsDelegates] value
433  ///    which provides material localizations for many languages.
434  ///  * The Flutter Internationalization Tutorial,
435  ///    <https://flutter.dev/tutorials/internationalization/>.
436  final Iterable<Locale> supportedLocales;
437
438  /// Turns on a performance overlay.
439  ///
440  /// See also:
441  ///
442  ///  * <https://flutter.dev/debugging/#performanceoverlay>
443  final bool showPerformanceOverlay;
444
445  /// Turns on checkerboarding of raster cache images.
446  final bool checkerboardRasterCacheImages;
447
448  /// Turns on checkerboarding of layers rendered to offscreen bitmaps.
449  final bool checkerboardOffscreenLayers;
450
451  /// Turns on an overlay that shows the accessibility information
452  /// reported by the framework.
453  final bool showSemanticsDebugger;
454
455  /// {@macro flutter.widgets.widgetsApp.debugShowCheckedModeBanner}
456  final bool debugShowCheckedModeBanner;
457
458  /// Turns on a [GridPaper] overlay that paints a baseline grid
459  /// Material apps.
460  ///
461  /// Only available in checked mode.
462  ///
463  /// See also:
464  ///
465  ///  * <https://material.io/design/layout/spacing-methods.html>
466  final bool debugShowMaterialGrid;
467
468  @override
469  _MaterialAppState createState() => _MaterialAppState();
470}
471
472class _MaterialScrollBehavior extends ScrollBehavior {
473  @override
474  TargetPlatform getPlatform(BuildContext context) {
475    return Theme.of(context).platform;
476  }
477
478  @override
479  Widget buildViewportChrome(BuildContext context, Widget child, AxisDirection axisDirection) {
480    // When modifying this function, consider modifying the implementation in
481    // the base class as well.
482    switch (getPlatform(context)) {
483      case TargetPlatform.iOS:
484        return child;
485      case TargetPlatform.android:
486      case TargetPlatform.fuchsia:
487        return GlowingOverscrollIndicator(
488          child: child,
489          axisDirection: axisDirection,
490          color: Theme.of(context).accentColor,
491        );
492    }
493    return null;
494  }
495}
496
497class _MaterialAppState extends State<MaterialApp> {
498  HeroController _heroController;
499
500  @override
501  void initState() {
502    super.initState();
503    _heroController = HeroController(createRectTween: _createRectTween);
504    _updateNavigator();
505  }
506
507  @override
508  void didUpdateWidget(MaterialApp oldWidget) {
509    super.didUpdateWidget(oldWidget);
510    if (widget.navigatorKey != oldWidget.navigatorKey) {
511      // If the Navigator changes, we have to create a new observer, because the
512      // old Navigator won't be disposed (and thus won't unregister with its
513      // observers) until after the new one has been created (because the
514      // Navigator has a GlobalKey).
515      _heroController = HeroController(createRectTween: _createRectTween);
516    }
517    _updateNavigator();
518  }
519
520  List<NavigatorObserver> _navigatorObservers;
521
522  void _updateNavigator() {
523    if (widget.home != null ||
524        widget.routes.isNotEmpty ||
525        widget.onGenerateRoute != null ||
526        widget.onUnknownRoute != null) {
527      _navigatorObservers = List<NavigatorObserver>.from(widget.navigatorObservers)
528        ..add(_heroController);
529    } else {
530      _navigatorObservers = const <NavigatorObserver>[];
531    }
532  }
533
534  RectTween _createRectTween(Rect begin, Rect end) {
535    return MaterialRectArcTween(begin: begin, end: end);
536  }
537
538  // Combine the Localizations for Material with the ones contributed
539  // by the localizationsDelegates parameter, if any. Only the first delegate
540  // of a particular LocalizationsDelegate.type is loaded so the
541  // localizationsDelegate parameter can be used to override
542  // _MaterialLocalizationsDelegate.
543  Iterable<LocalizationsDelegate<dynamic>> get _localizationsDelegates sync* {
544    if (widget.localizationsDelegates != null)
545      yield* widget.localizationsDelegates;
546    yield DefaultMaterialLocalizations.delegate;
547    yield DefaultCupertinoLocalizations.delegate;
548  }
549
550  @override
551  Widget build(BuildContext context) {
552    Widget result = WidgetsApp(
553      key: GlobalObjectKey(this),
554      navigatorKey: widget.navigatorKey,
555      navigatorObservers: _navigatorObservers,
556        pageRouteBuilder: <T>(RouteSettings settings, WidgetBuilder builder) =>
557            MaterialPageRoute<T>(settings: settings, builder: builder),
558      home: widget.home,
559      routes: widget.routes,
560      initialRoute: widget.initialRoute,
561      onGenerateRoute: widget.onGenerateRoute,
562      onUnknownRoute: widget.onUnknownRoute,
563      builder: (BuildContext context, Widget child) {
564        // Use a light theme, dark theme, or fallback theme.
565        final ThemeMode mode = widget.themeMode ?? ThemeMode.system;
566        ThemeData theme;
567        if (widget.darkTheme != null) {
568          final ui.Brightness platformBrightness = MediaQuery.platformBrightnessOf(context);
569          if (mode == ThemeMode.dark ||
570              (mode == ThemeMode.system && platformBrightness == ui.Brightness.dark)) {
571            theme = widget.darkTheme;
572          }
573        }
574        theme ??= widget.theme ?? ThemeData.fallback();
575
576        return AnimatedTheme(
577          data: theme,
578          isMaterialAppTheme: true,
579          child: widget.builder != null
580              ? Builder(
581                  builder: (BuildContext context) {
582                    // Why are we surrounding a builder with a builder?
583                    //
584                    // The widget.builder may contain code that invokes
585                    // Theme.of(), which should return the theme we selected
586                    // above in AnimatedTheme. However, if we invoke
587                    // widget.builder() directly as the child of AnimatedTheme
588                    // then there is no Context separating them, and the
589                    // widget.builder() will not find the theme. Therefore, we
590                    // surround widget.builder with yet another builder so that
591                    // a context separates them and Theme.of() correctly
592                    // resolves to the theme we passed to AnimatedTheme.
593                    return widget.builder(context, child);
594                  },
595                )
596              : child,
597        );
598      },
599      title: widget.title,
600      onGenerateTitle: widget.onGenerateTitle,
601      textStyle: _errorTextStyle,
602      // The color property is always pulled from the light theme, even if dark
603      // mode is activated. This was done to simplify the technical details
604      // of switching themes and it was deemed acceptable because this color
605      // property is only used on old Android OSes to color the app bar in
606      // Android's switcher UI.
607      //
608      // blue is the primary color of the default theme
609      color: widget.color ?? widget.theme?.primaryColor ?? Colors.blue,
610      locale: widget.locale,
611      localizationsDelegates: _localizationsDelegates,
612      localeResolutionCallback: widget.localeResolutionCallback,
613      localeListResolutionCallback: widget.localeListResolutionCallback,
614      supportedLocales: widget.supportedLocales,
615      showPerformanceOverlay: widget.showPerformanceOverlay,
616      checkerboardRasterCacheImages: widget.checkerboardRasterCacheImages,
617      checkerboardOffscreenLayers: widget.checkerboardOffscreenLayers,
618      showSemanticsDebugger: widget.showSemanticsDebugger,
619      debugShowCheckedModeBanner: widget.debugShowCheckedModeBanner,
620      inspectorSelectButtonBuilder: (BuildContext context, VoidCallback onPressed) {
621        return FloatingActionButton(
622          child: const Icon(Icons.search),
623          onPressed: onPressed,
624          mini: true,
625        );
626      },
627    );
628
629    assert(() {
630      if (widget.debugShowMaterialGrid) {
631        result = GridPaper(
632          color: const Color(0xE0F9BBE0),
633          interval: 8.0,
634          divisions: 2,
635          subdivisions: 1,
636          child: result,
637        );
638      }
639      return true;
640    }());
641
642    return ScrollConfiguration(
643      behavior: _MaterialScrollBehavior(),
644      child: result,
645    );
646  }
647}
648