Provide a way to override global InheritedWidgets (#14348)
For example, so that the gallery can override the media query globally.
This commit is contained in:
parent
12ceaefb18
commit
c12d120bbf
@ -51,6 +51,7 @@ class GalleryAppState extends State<GalleryApp> {
|
||||
bool _showPerformanceOverlay = false;
|
||||
bool _checkerboardRasterCacheImages = false;
|
||||
bool _checkerboardOffscreenLayers = false;
|
||||
TextDirection _overrideDirection = TextDirection.ltr;
|
||||
double _timeDilation = 1.0;
|
||||
TargetPlatform _platform;
|
||||
|
||||
@ -139,6 +140,12 @@ class GalleryAppState extends State<GalleryApp> {
|
||||
_textScaleFactor = value;
|
||||
});
|
||||
},
|
||||
overrideDirection: _overrideDirection,
|
||||
onOverrideDirectionChanged: (TextDirection value) {
|
||||
setState(() {
|
||||
_overrideDirection = value;
|
||||
});
|
||||
},
|
||||
onSendFeedback: widget.onSendFeedback,
|
||||
);
|
||||
|
||||
@ -155,7 +162,7 @@ class GalleryAppState extends State<GalleryApp> {
|
||||
// using named routes, consider the example in the Navigator class documentation:
|
||||
// https://docs.flutter.io/flutter/widgets/Navigator-class.html
|
||||
_kRoutes[item.routeName] = (BuildContext context) {
|
||||
return _applyScaleFactor(item.buildRoute(context));
|
||||
return item.buildRoute(context);
|
||||
};
|
||||
}
|
||||
|
||||
@ -168,6 +175,12 @@ class GalleryAppState extends State<GalleryApp> {
|
||||
checkerboardOffscreenLayers: _checkerboardOffscreenLayers,
|
||||
routes: _kRoutes,
|
||||
home: _applyScaleFactor(home),
|
||||
builder: (BuildContext context, Widget child) {
|
||||
return new Directionality(
|
||||
textDirection: _overrideDirection,
|
||||
child: _applyScaleFactor(child),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -120,6 +120,8 @@ class GalleryDrawer extends StatelessWidget {
|
||||
this.checkerboardOffscreenLayers,
|
||||
this.onCheckerboardOffscreenLayersChanged,
|
||||
this.onPlatformChanged,
|
||||
this.overrideDirection: TextDirection.ltr,
|
||||
this.onOverrideDirectionChanged,
|
||||
this.onSendFeedback,
|
||||
}) : assert(onThemeChanged != null),
|
||||
assert(onTimeDilationChanged != null),
|
||||
@ -145,6 +147,9 @@ class GalleryDrawer extends StatelessWidget {
|
||||
|
||||
final ValueChanged<TargetPlatform> onPlatformChanged;
|
||||
|
||||
final TextDirection overrideDirection;
|
||||
final ValueChanged<TextDirection> onOverrideDirectionChanged;
|
||||
|
||||
final VoidCallback onSendFeedback;
|
||||
|
||||
@override
|
||||
@ -220,6 +225,16 @@ class GalleryDrawer extends StatelessWidget {
|
||||
selected: timeDilation != 1.0,
|
||||
);
|
||||
|
||||
final Widget overrideDirectionItem = new CheckboxListTile(
|
||||
title: const Text('Force RTL'),
|
||||
value: overrideDirection == TextDirection.rtl,
|
||||
onChanged: (bool value) {
|
||||
onOverrideDirectionChanged(value ? TextDirection.rtl : TextDirection.ltr);
|
||||
},
|
||||
secondary: const Icon(Icons.format_textdirection_r_to_l),
|
||||
selected: overrideDirection == TextDirection.rtl,
|
||||
);
|
||||
|
||||
final Widget sendFeedbackItem = new ListTile(
|
||||
leading: const Icon(Icons.report),
|
||||
title: const Text('Send feedback'),
|
||||
@ -285,6 +300,7 @@ class GalleryDrawer extends StatelessWidget {
|
||||
allDrawerItems.addAll(textSizeItems);
|
||||
|
||||
allDrawerItems..addAll(<Widget>[
|
||||
overrideDirectionItem,
|
||||
const Divider(),
|
||||
animateSlowlyItem,
|
||||
const Divider(),
|
||||
|
@ -77,6 +77,8 @@ class GalleryHome extends StatefulWidget {
|
||||
this.checkerboardOffscreenLayers,
|
||||
this.onCheckerboardOffscreenLayersChanged,
|
||||
this.onPlatformChanged,
|
||||
this.overrideDirection: TextDirection.ltr,
|
||||
this.onOverrideDirectionChanged,
|
||||
this.onSendFeedback,
|
||||
}) : assert(onThemeChanged != null),
|
||||
assert(onTimeDilationChanged != null),
|
||||
@ -102,6 +104,9 @@ class GalleryHome extends StatefulWidget {
|
||||
|
||||
final ValueChanged<TargetPlatform> onPlatformChanged;
|
||||
|
||||
final TextDirection overrideDirection;
|
||||
final ValueChanged<TextDirection> onOverrideDirectionChanged;
|
||||
|
||||
final VoidCallback onSendFeedback;
|
||||
|
||||
@override
|
||||
@ -177,6 +182,8 @@ class GalleryHomeState extends State<GalleryHome> with SingleTickerProviderState
|
||||
checkerboardOffscreenLayers: widget.checkerboardOffscreenLayers,
|
||||
onCheckerboardOffscreenLayersChanged: widget.onCheckerboardOffscreenLayersChanged,
|
||||
onPlatformChanged: widget.onPlatformChanged,
|
||||
overrideDirection: widget.overrideDirection,
|
||||
onOverrideDirectionChanged: widget.onOverrideDirectionChanged,
|
||||
onSendFeedback: widget.onSendFeedback,
|
||||
),
|
||||
body: new CustomScrollView(
|
||||
|
@ -69,7 +69,7 @@ void main() {
|
||||
expect(newTextSize, equals(origTextSize));
|
||||
|
||||
// Scroll to the bottom of the menu.
|
||||
await tester.drag(find.text('Small'), const Offset(0.0, -450.0));
|
||||
await tester.drag(find.text('Small'), const Offset(0.0, -1000.0));
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(seconds: 1)); // Wait until it's changed.
|
||||
|
||||
|
@ -159,7 +159,7 @@ Future<Null> runSmokeTest(WidgetTester tester) async {
|
||||
await tester.pump(const Duration(seconds: 1)); // Wait until it's changed.
|
||||
|
||||
// Scroll the 'Send feedback' item into view.
|
||||
await tester.drag(find.text('Small'), const Offset(0.0, -450.0));
|
||||
await tester.drag(find.text('Small'), const Offset(0.0, -1000.0));
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(seconds: 1)); // Wait until it's changed.
|
||||
|
||||
|
@ -51,13 +51,16 @@ const TextStyle _errorTextStyle = const TextStyle(
|
||||
///
|
||||
/// 4. Finally if all else fails [onUnknownRoute] is called.
|
||||
///
|
||||
/// At least one of these options must handle the `/` route, since it is used
|
||||
/// when an invalid [initialRoute] is specified on startup (e.g. by another
|
||||
/// application launching this one with an intent on Android; see
|
||||
/// [Window.defaultRouteName]).
|
||||
/// If a [Navigator] is created, at least one of these options must handle the
|
||||
/// `/` route, since it is used when an invalid [initialRoute] is specified on
|
||||
/// startup (e.g. by another application launching this one with an intent on
|
||||
/// Android; see [Window.defaultRouteName]).
|
||||
///
|
||||
/// This widget also configures the top-level [Navigator]'s observer to perform
|
||||
/// [Hero] animations.
|
||||
/// This widget also configures the observer of the top-level [Navigator] (if
|
||||
/// any) to perform [Hero] animations.
|
||||
///
|
||||
/// If [home], [routes], [onGenerateRoute], and [onUnknownRoute] are all null,
|
||||
/// and [builder] is not null, then no [Navigator] is created.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
@ -68,8 +71,8 @@ const TextStyle _errorTextStyle = const TextStyle(
|
||||
class MaterialApp extends StatefulWidget {
|
||||
/// Creates a MaterialApp.
|
||||
///
|
||||
/// At least one of [home], [routes], or [onGenerateRoute] must be given. If
|
||||
/// only [routes] is given, it must include an entry for the
|
||||
/// At least one of [home], [routes], [onGenerateRoute], or [builder] must be
|
||||
/// non-null. If only [routes] is given, it must include an entry for the
|
||||
/// [Navigator.defaultRouteName] (`/`), since that is the route used when the
|
||||
/// application is launched with an intent that specifies an otherwise
|
||||
/// unsupported route.
|
||||
@ -80,35 +83,29 @@ class MaterialApp extends StatefulWidget {
|
||||
MaterialApp({ // can't be const because the asserts use methods on Map :-(
|
||||
Key key,
|
||||
this.navigatorKey,
|
||||
this.title: '',
|
||||
this.onGenerateTitle,
|
||||
this.color,
|
||||
this.theme,
|
||||
this.home,
|
||||
this.routes: const <String, WidgetBuilder>{},
|
||||
this.initialRoute,
|
||||
this.onGenerateRoute,
|
||||
this.onUnknownRoute,
|
||||
this.navigatorObservers: const <NavigatorObserver>[],
|
||||
this.builder,
|
||||
this.title: '',
|
||||
this.onGenerateTitle,
|
||||
this.color,
|
||||
this.theme,
|
||||
this.locale,
|
||||
this.localizationsDelegates,
|
||||
this.localeResolutionCallback,
|
||||
this.supportedLocales: const <Locale>[const Locale('en', 'US')],
|
||||
this.navigatorObservers: const <NavigatorObserver>[],
|
||||
this.debugShowMaterialGrid: false,
|
||||
this.showPerformanceOverlay: false,
|
||||
this.checkerboardRasterCacheImages: false,
|
||||
this.checkerboardOffscreenLayers: false,
|
||||
this.showSemanticsDebugger: false,
|
||||
this.debugShowCheckedModeBanner: true
|
||||
}) : assert(title != null),
|
||||
assert(routes != null),
|
||||
this.debugShowCheckedModeBanner: true,
|
||||
}) : assert(routes != null),
|
||||
assert(navigatorObservers != null),
|
||||
assert(debugShowMaterialGrid != null),
|
||||
assert(showPerformanceOverlay != null),
|
||||
assert(checkerboardRasterCacheImages != null),
|
||||
assert(checkerboardOffscreenLayers != null),
|
||||
assert(showSemanticsDebugger != null),
|
||||
assert(debugShowCheckedModeBanner != null),
|
||||
assert(
|
||||
home == null ||
|
||||
!routes.containsKey(Navigator.defaultRouteName),
|
||||
@ -116,6 +113,7 @@ class MaterialApp extends StatefulWidget {
|
||||
'cannot include an entry for "/", since it would be redundant.'
|
||||
),
|
||||
assert(
|
||||
builder != null ||
|
||||
home != null ||
|
||||
routes.containsKey(Navigator.defaultRouteName) ||
|
||||
onGenerateRoute != null ||
|
||||
@ -124,9 +122,35 @@ class MaterialApp extends StatefulWidget {
|
||||
'or the routes table must include an entry for "/", '
|
||||
'or there must be on onGenerateRoute callback specified, '
|
||||
'or there must be an onUnknownRoute callback specified, '
|
||||
'or the builder property must be specified, '
|
||||
'because otherwise there is nothing to fall back on if the '
|
||||
'app is started with an intent that specifies an unknown route.'
|
||||
),
|
||||
assert(
|
||||
(home != null ||
|
||||
routes.isNotEmpty ||
|
||||
onGenerateRoute != null ||
|
||||
onUnknownRoute != null)
|
||||
||
|
||||
(builder != null &&
|
||||
navigatorKey == null &&
|
||||
initialRoute == null &&
|
||||
navigatorObservers.isEmpty),
|
||||
'If no route is provided using '
|
||||
'home, routes, onGenerateRoute, or onUnknownRoute, '
|
||||
'a non-null callback for the builder property must be provided, '
|
||||
'and the other navigator-related properties, '
|
||||
'navigatorKey, initialRoute, and navigatorObservers, '
|
||||
'must have their initial values '
|
||||
'(null, null, and the empty list, respectively).'
|
||||
),
|
||||
assert(title != null),
|
||||
assert(debugShowMaterialGrid != null),
|
||||
assert(showPerformanceOverlay != null),
|
||||
assert(checkerboardRasterCacheImages != null),
|
||||
assert(checkerboardOffscreenLayers != null),
|
||||
assert(showSemanticsDebugger != null),
|
||||
assert(debugShowCheckedModeBanner != null),
|
||||
super(key: key);
|
||||
|
||||
/// A key to use when building the [Navigator].
|
||||
@ -140,8 +164,173 @@ class MaterialApp extends StatefulWidget {
|
||||
/// application state in the process; in that case, the [navigatorObservers]
|
||||
/// must also be changed, since the previous observers will be attached to the
|
||||
/// previous navigator.
|
||||
///
|
||||
/// The [Navigator] is only built if routes are provided (either via [home],
|
||||
/// [routes], [onGenerateRoute], or [onUnknownRoute]); if they are not,
|
||||
/// [navigatorKey] must be null and [builder] must not be null.
|
||||
final GlobalKey<NavigatorState> navigatorKey;
|
||||
|
||||
/// The widget for the default route of the app ([Navigator.defaultRouteName],
|
||||
/// which is `/`).
|
||||
///
|
||||
/// This is the route that is displayed first when the application is started
|
||||
/// normally, unless [initialRoute] is specified. It's also the route that's
|
||||
/// displayed if the [initialRoute] can't be displayed.
|
||||
///
|
||||
/// To be able to directly call [Theme.of], [MediaQuery.of], etc, in the code
|
||||
/// that sets the [home] argument in the constructor, you can use a [Builder]
|
||||
/// widget to get a [BuildContext].
|
||||
///
|
||||
/// If [home] is specified, then [routes] must not include an entry for `/`,
|
||||
/// as [home] takes its place.
|
||||
///
|
||||
/// The [Navigator] is only built if routes are provided (either via [home],
|
||||
/// [routes], [onGenerateRoute], or [onUnknownRoute]); if they are not,
|
||||
/// [builder] must not be null.
|
||||
///
|
||||
/// The difference between using [home] and using [builder] is that the [home]
|
||||
/// subtree is inserted into the application below a [Navigator] (and thus
|
||||
/// below an [Overlay], which [Navigator] uses). With [home], therefore,
|
||||
/// dialog boxes will work automatically, [Tooltip]s will work, the [routes]
|
||||
/// table will be used, and APIs such as [Navigator.push] and [Navigator.pop]
|
||||
/// will work as expected. In contrast, the widget returned from [builder] is
|
||||
/// inserted _above_ the [MaterialApp]'s [Navigator] (if any).
|
||||
final Widget home;
|
||||
|
||||
/// The application's top-level routing table.
|
||||
///
|
||||
/// When a named route is pushed with [Navigator.pushNamed], the route name is
|
||||
/// looked up in this map. If the name is present, the associated
|
||||
/// [WidgetBuilder] is used to construct a [MaterialPageRoute] that performs
|
||||
/// an appropriate transition, including [Hero] animations, to the new route.
|
||||
///
|
||||
/// If the app only has one page, then you can specify it using [home] instead.
|
||||
///
|
||||
/// If [home] is specified, then it implies an entry in this table for the
|
||||
/// [Navigator.defaultRouteName] route (`/`), and it is an error to
|
||||
/// redundantly provide such a route in the [routes] table.
|
||||
///
|
||||
/// If a route is requested that is not specified in this table (or by
|
||||
/// [home]), then the [onGenerateRoute] callback is called to build the page
|
||||
/// instead.
|
||||
///
|
||||
/// The [Navigator] is only built if routes are provided (either via [home],
|
||||
/// [routes], [onGenerateRoute], or [onUnknownRoute]); if they are not,
|
||||
/// [builder] must not be null.
|
||||
final Map<String, WidgetBuilder> routes;
|
||||
|
||||
/// The name of the first route to show, if a [Navigator] is built.
|
||||
///
|
||||
/// Defaults to [Window.defaultRouteName], which may be overridden by the code
|
||||
/// that launched the application.
|
||||
///
|
||||
/// If the route contains slashes, then it is treated as a "deep link", and
|
||||
/// before this route is pushed, the routes leading to this one are pushed
|
||||
/// also. For example, if the route was `/a/b/c`, then the app would start
|
||||
/// with the three routes `/a`, `/a/b`, and `/a/b/c` loaded, in that order.
|
||||
///
|
||||
/// If any part of this process fails to generate routes, then the
|
||||
/// [initialRoute] is ignored and [Navigator.defaultRouteName] is used instead
|
||||
/// (`/`). This can happen if the app is started with an intent that specifies
|
||||
/// a non-existent route.
|
||||
///
|
||||
/// The [Navigator] is only built if routes are provided (either via [home],
|
||||
/// [routes], [onGenerateRoute], or [onUnknownRoute]); if they are not,
|
||||
/// [initialRoute] must be null and [builder] must not be null.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [Navigator.initialRoute], which is used to implement this property.
|
||||
/// * [Navigator.push], for pushing additional routes.
|
||||
/// * [Navigator.pop], for removing a route from the stack.
|
||||
final String initialRoute;
|
||||
|
||||
/// The route generator callback used when the app is navigated to a
|
||||
/// named route.
|
||||
///
|
||||
/// This is used if [routes] does not contain the requested route.
|
||||
///
|
||||
/// If this returns null when building the routes to handle the specified
|
||||
/// [initialRoute], then all the routes are discarded and
|
||||
/// [Navigator.defaultRouteName] is used instead (`/`). See [initialRoute].
|
||||
///
|
||||
/// During normal app operation, the [onGenerateRoute] callback will only be
|
||||
/// applied to route names pushed by the application, and so should never
|
||||
/// return null.
|
||||
///
|
||||
/// The [Navigator] is only built if routes are provided (either via [home],
|
||||
/// [routes], [onGenerateRoute], or [onUnknownRoute]); if they are not,
|
||||
/// [builder] must not be null.
|
||||
final RouteFactory onGenerateRoute;
|
||||
|
||||
/// Called when [onGenerateRoute] fails to generate a route, except for the
|
||||
/// [initialRoute].
|
||||
///
|
||||
/// This callback is typically used for error handling. For example, this
|
||||
/// callback might always generate a "not found" page that describes the route
|
||||
/// that wasn't found.
|
||||
///
|
||||
/// The default implementation pushes a route that displays an ugly error
|
||||
/// message.
|
||||
///
|
||||
/// The [Navigator] is only built if routes are provided (either via [home],
|
||||
/// [routes], [onGenerateRoute], or [onUnknownRoute]); if they are not,
|
||||
/// [builder] must not be null.
|
||||
final RouteFactory onUnknownRoute;
|
||||
|
||||
/// The list of observers for the [Navigator] created for this app.
|
||||
///
|
||||
/// This list must be replaced by a list of newly-created observers if the
|
||||
/// [navigatorKey] is changed.
|
||||
///
|
||||
/// The [Navigator] is only built if routes are provided (either via [home],
|
||||
/// [routes], [onGenerateRoute], or [onUnknownRoute]); if they are not,
|
||||
/// [navigatorObservers] must be the empty list and [builder] must not be null.
|
||||
final List<NavigatorObserver> navigatorObservers;
|
||||
|
||||
/// A builder for inserting widgets above the [Navigator] but below the other
|
||||
/// widgets created by the [MaterialApp] widget, or for replacing the
|
||||
/// [Navigator] entirely.
|
||||
///
|
||||
/// For example, from the [BuildContext] passed to this method, the
|
||||
/// [Directionality], [Localizations], [DefaultTextStyle], [MediaQuery], etc,
|
||||
/// are all available. They can also be overridden in a way that impacts all
|
||||
/// the routes in the [Navigator].
|
||||
///
|
||||
/// This is rarely useful, but can be used in applications that wish to
|
||||
/// override those defaults, e.g. to force the application into right-to-left
|
||||
/// mode despite being in English, or to override the [MediaQuery] metrics
|
||||
/// (e.g. to leave a gap for advertisements shown by a plugin from OEM code).
|
||||
///
|
||||
/// The [builder] callback is passed two arguments, the [BuildContext] (as
|
||||
/// `context`) and a [Navigator] widget (as `child`).
|
||||
///
|
||||
/// If no routes are provided using [home], [routes], [onGenerateRoute], or
|
||||
/// [onUnknownRoute], the `child` will be null, and it is the responsibility
|
||||
/// of the [builder] to provide the application's routing machinery.
|
||||
///
|
||||
/// If routes _are_ provided using one or more of those properties, then
|
||||
/// `child` is not null, and the returned value should include the `child` in
|
||||
/// the widget subtree; if it does not, then the application will have no
|
||||
/// navigator and the [navigatorKey], [home], [routes], [onGenerateRoute],
|
||||
/// [onUnknownRoute], [initialRoute], and [navigatorObservers] properties will
|
||||
/// have no effect.
|
||||
///
|
||||
/// If [builder] is null, it is as if a builder was specified that returned
|
||||
/// the `child` directly. If it is null, routes must be provided using one of
|
||||
/// the other properties listed above.
|
||||
///
|
||||
/// Unless a [Navigator] is provided, either implicitly from [builder] being
|
||||
/// null, or by a [builder] including its `child` argument, or by a [builder]
|
||||
/// explicitly providing a [Navigator] of its own, features such as
|
||||
/// [showDialog] and [showMenu], widgets such as [Tooltip], [PopupMenuButton],
|
||||
/// or [Hero], and APIs such as [Navigator.push] and [Navigator.pop], will not
|
||||
/// function.
|
||||
///
|
||||
/// For specifically overriding the [title] with a value based on the
|
||||
/// [Localizations], consider [onGenerateTitle] instead.
|
||||
final TransitionBuilder builder;
|
||||
|
||||
/// A one-line description used by the device to identify the app for the user.
|
||||
///
|
||||
/// On Android the titles appear above the task manager's app snapshots which are
|
||||
@ -172,21 +361,6 @@ class MaterialApp extends StatefulWidget {
|
||||
/// The colors to use for the application's widgets.
|
||||
final ThemeData theme;
|
||||
|
||||
/// The widget for the default route of the app ([Navigator.defaultRouteName],
|
||||
/// which is `/`).
|
||||
///
|
||||
/// This is the route that is displayed first when the application is started
|
||||
/// normally, unless [initialRoute] is specified. It's also the route that's
|
||||
/// displayed if the [initialRoute] can't be displayed.
|
||||
///
|
||||
/// To be able to directly call [Theme.of], [MediaQuery.of], etc, in the code
|
||||
/// that sets the [home] argument in the constructor, you can use a [Builder]
|
||||
/// widget to get a [BuildContext].
|
||||
///
|
||||
/// If [home] is specified, then [routes] must not include an entry for `/`,
|
||||
/// as [home] takes its place.
|
||||
final Widget home;
|
||||
|
||||
/// The primary color to use for the application in the operating system
|
||||
/// interface.
|
||||
///
|
||||
@ -194,71 +368,6 @@ class MaterialApp extends StatefulWidget {
|
||||
/// application switcher.
|
||||
final Color color;
|
||||
|
||||
/// The application's top-level routing table.
|
||||
///
|
||||
/// When a named route is pushed with [Navigator.pushNamed], the route name is
|
||||
/// looked up in this map. If the name is present, the associated
|
||||
/// [WidgetBuilder] is used to construct a [MaterialPageRoute] that performs
|
||||
/// an appropriate transition, including [Hero] animations, to the new route.
|
||||
///
|
||||
/// If the app only has one page, then you can specify it using [home] instead.
|
||||
///
|
||||
/// If [home] is specified, then it implies an entry in this table for the
|
||||
/// [Navigator.defaultRouteName] route (`/`), and it is an error to
|
||||
/// redundantly provide such a route in the [routes] table.
|
||||
///
|
||||
/// If a route is requested that is not specified in this table (or by
|
||||
/// [home]), then the [onGenerateRoute] callback is called to build the page
|
||||
/// instead.
|
||||
final Map<String, WidgetBuilder> routes;
|
||||
|
||||
/// The name of the first route to show.
|
||||
///
|
||||
/// Defaults to [Window.defaultRouteName], which may be overridden by the code
|
||||
/// that launched the application.
|
||||
///
|
||||
/// If the route contains slashes, then it is treated as a "deep link", and
|
||||
/// before this route is pushed, the routes leading to this one are pushed
|
||||
/// also. For example, if the route was `/a/b/c`, then the app would start
|
||||
/// with the three routes `/a`, `/a/b`, and `/a/b/c` loaded, in that order.
|
||||
///
|
||||
/// If any part of this process fails to generate routes, then the
|
||||
/// [initialRoute] is ignored and [Navigator.defaultRouteName] is used instead
|
||||
/// (`/`). This can happen if the app is started with an intent that specifies
|
||||
/// a non-existent route.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [Navigator.initialRoute], which is used to implement this property.
|
||||
/// * [Navigator.push], for pushing additional routes.
|
||||
/// * [Navigator.pop], for removing a route from the stack.
|
||||
final String initialRoute;
|
||||
|
||||
/// The route generator callback used when the app is navigated to a
|
||||
/// named route.
|
||||
///
|
||||
/// This is used if [routes] does not contain the requested route.
|
||||
///
|
||||
/// If this returns null when building the routes to handle the specified
|
||||
/// [initialRoute], then all the routes are discarded and
|
||||
/// [Navigator.defaultRouteName] is used instead (`/`). See [initialRoute].
|
||||
///
|
||||
/// During normal app operation, the [onGenerateRoute] callback will only be
|
||||
/// applied to route names pushed by the application, and so should never
|
||||
/// return null.
|
||||
final RouteFactory onGenerateRoute;
|
||||
|
||||
/// Called when [onGenerateRoute] fails to generate a route, except for the
|
||||
/// [initialRoute].
|
||||
///
|
||||
/// This callback is typically used for error handling. For example, this
|
||||
/// callback might always generate a "not found" page that describes the route
|
||||
/// that wasn't found.
|
||||
///
|
||||
/// The default implementation pushes a route that displays an ugly error
|
||||
/// message.
|
||||
final RouteFactory onUnknownRoute;
|
||||
|
||||
/// The initial locale for this app's [Localizations] widget.
|
||||
///
|
||||
/// If the `locale` is null the system's locale value is used.
|
||||
@ -417,12 +526,6 @@ class MaterialApp extends StatefulWidget {
|
||||
/// representative of what will happen in release mode.
|
||||
final bool debugShowCheckedModeBanner;
|
||||
|
||||
/// The list of observers for the [Navigator] created for this app.
|
||||
///
|
||||
/// This list must be replaced by a list of newly-created observers if the
|
||||
/// [navigatorKey] is changed.
|
||||
final List<NavigatorObserver> navigatorObservers;
|
||||
|
||||
/// Turns on a [GridPaper] overlay that paints a baseline grid
|
||||
/// Material apps.
|
||||
///
|
||||
@ -469,6 +572,7 @@ class _MaterialAppState extends State<MaterialApp> {
|
||||
void initState() {
|
||||
super.initState();
|
||||
_heroController = new HeroController(createRectTween: _createRectTween);
|
||||
_updateNavigator();
|
||||
}
|
||||
|
||||
@override
|
||||
@ -481,17 +585,19 @@ class _MaterialAppState extends State<MaterialApp> {
|
||||
// Navigator has a GlobalKey).
|
||||
_heroController = new HeroController(createRectTween: _createRectTween);
|
||||
}
|
||||
_updateNavigator();
|
||||
}
|
||||
|
||||
// Combine the Localizations for Material with the ones contributed
|
||||
// by the localizationsDelegates parameter, if any. Only the first delegate
|
||||
// of a particular LocalizationsDelegate.type is loaded so the
|
||||
// localizationsDelegate parameter can be used to override
|
||||
// _MaterialLocalizationsDelegate.
|
||||
Iterable<LocalizationsDelegate<dynamic>> get _localizationsDelegates sync* {
|
||||
if (widget.localizationsDelegates != null)
|
||||
yield* widget.localizationsDelegates;
|
||||
yield DefaultMaterialLocalizations.delegate;
|
||||
bool _haveNavigator;
|
||||
List<NavigatorObserver> _navigatorObservers;
|
||||
|
||||
void _updateNavigator() {
|
||||
_haveNavigator = widget.home != null ||
|
||||
widget.routes.isNotEmpty ||
|
||||
widget.onGenerateRoute != null ||
|
||||
widget.onUnknownRoute != null;
|
||||
_navigatorObservers = new List<NavigatorObserver>.from(widget.navigatorObservers)
|
||||
..add(_heroController);
|
||||
}
|
||||
|
||||
RectTween _createRectTween(Rect begin, Rect end) {
|
||||
@ -548,6 +654,17 @@ class _MaterialAppState extends State<MaterialApp> {
|
||||
return result;
|
||||
}
|
||||
|
||||
// Combine the Localizations for Material with the ones contributed
|
||||
// by the localizationsDelegates parameter, if any. Only the first delegate
|
||||
// of a particular LocalizationsDelegate.type is loaded so the
|
||||
// localizationsDelegate parameter can be used to override
|
||||
// _MaterialLocalizationsDelegate.
|
||||
Iterable<LocalizationsDelegate<dynamic>> get _localizationsDelegates sync* {
|
||||
if (widget.localizationsDelegates != null)
|
||||
yield* widget.localizationsDelegates;
|
||||
yield DefaultMaterialLocalizations.delegate;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final ThemeData theme = widget.theme ?? new ThemeData.fallback();
|
||||
@ -557,17 +674,16 @@ class _MaterialAppState extends State<MaterialApp> {
|
||||
child: new WidgetsApp(
|
||||
key: new GlobalObjectKey(this),
|
||||
navigatorKey: widget.navigatorKey,
|
||||
navigatorObservers: _haveNavigator ? _navigatorObservers : null,
|
||||
initialRoute: widget.initialRoute,
|
||||
onGenerateRoute: _haveNavigator ? _onGenerateRoute : null,
|
||||
onUnknownRoute: _haveNavigator ? _onUnknownRoute : null,
|
||||
builder: widget.builder,
|
||||
title: widget.title,
|
||||
onGenerateTitle: widget.onGenerateTitle,
|
||||
textStyle: _errorTextStyle,
|
||||
// blue is the primary color of the default theme
|
||||
color: widget.color ?? theme?.primaryColor ?? Colors.blue,
|
||||
navigatorObservers:
|
||||
new List<NavigatorObserver>.from(widget.navigatorObservers)
|
||||
..add(_heroController),
|
||||
initialRoute: widget.initialRoute,
|
||||
onGenerateRoute: _onGenerateRoute,
|
||||
onUnknownRoute: _onUnknownRoute,
|
||||
locale: widget.locale,
|
||||
localizationsDelegates: _localizationsDelegates,
|
||||
localeResolutionCallback: widget.localeResolutionCallback,
|
||||
|
@ -53,29 +53,33 @@ typedef String GenerateAppTitle(BuildContext context);
|
||||
/// See also: [CheckedModeBanner], [DefaultTextStyle], [MediaQuery],
|
||||
/// [Localizations], [Title], [Navigator], [Overlay], [SemanticsDebugger] (the
|
||||
/// widgets wrapped by this one).
|
||||
///
|
||||
/// The [onGenerateRoute] argument is required, and corresponds to
|
||||
/// [Navigator.onGenerateRoute].
|
||||
class WidgetsApp extends StatefulWidget {
|
||||
/// Creates a widget that wraps a number of widgets that are commonly
|
||||
/// required for an application.
|
||||
///
|
||||
/// The boolean arguments, [color], [navigatorObservers], and
|
||||
/// [onGenerateRoute] must not be null.
|
||||
/// The boolean arguments, [color], and [navigatorObservers] must not be null.
|
||||
///
|
||||
/// If the [builder] is null, the [onGenerateRoute] argument is required, and
|
||||
/// corresponds to [Navigator.onGenerateRoute]. If the [builder] is non-null
|
||||
/// and the [onGenerateRoute] argument is null, then the [builder] will not be
|
||||
/// provided with a [Navigator]. If [onGenerateRoute] is not provided,
|
||||
/// [navigatorKey], [onUnknownRoute], [navigatorObservers], and [initialRoute]
|
||||
/// must have their default values, as they will have no effect.
|
||||
///
|
||||
/// The `supportedLocales` argument must be a list of one or more elements.
|
||||
/// By default supportedLocales is `[const Locale('en', 'US')]`.
|
||||
WidgetsApp({ // can't be const because the asserts use methods on Iterable :-(
|
||||
Key key,
|
||||
this.navigatorKey,
|
||||
@required this.onGenerateRoute,
|
||||
this.onGenerateRoute,
|
||||
this.onUnknownRoute,
|
||||
this.navigatorObservers: const <NavigatorObserver>[],
|
||||
this.initialRoute,
|
||||
this.builder,
|
||||
this.title: '',
|
||||
this.onGenerateTitle,
|
||||
this.textStyle,
|
||||
@required this.color,
|
||||
this.navigatorObservers: const <NavigatorObserver>[],
|
||||
this.initialRoute,
|
||||
this.locale,
|
||||
this.localizationsDelegates,
|
||||
this.localeResolutionCallback,
|
||||
@ -87,10 +91,14 @@ class WidgetsApp extends StatefulWidget {
|
||||
this.debugShowWidgetInspector: false,
|
||||
this.debugShowCheckedModeBanner: true,
|
||||
this.inspectorSelectButtonBuilder,
|
||||
}) : assert(title != null),
|
||||
assert(onGenerateRoute != null),
|
||||
}) : assert(navigatorObservers != null),
|
||||
assert(onGenerateRoute != null || navigatorKey == null),
|
||||
assert(onGenerateRoute != null || onUnknownRoute == null),
|
||||
assert(onGenerateRoute != null || navigatorObservers == const <NavigatorObserver>[]),
|
||||
assert(onGenerateRoute != null || initialRoute == null),
|
||||
assert(onGenerateRoute != null || builder != null),
|
||||
assert(title != null),
|
||||
assert(color != null),
|
||||
assert(navigatorObservers != null),
|
||||
assert(supportedLocales != null && supportedLocales.isNotEmpty),
|
||||
assert(showPerformanceOverlay != null),
|
||||
assert(checkerboardRasterCacheImages != null),
|
||||
@ -111,8 +119,109 @@ class WidgetsApp extends StatefulWidget {
|
||||
/// application state in the process; in that case, the [navigatorObservers]
|
||||
/// must also be changed, since the previous observers will be attached to the
|
||||
/// previous navigator.
|
||||
///
|
||||
/// The [Navigator] is only built if [onGenerateRoute] is not null; if it is
|
||||
/// null, [navigatorKey] must also be null.
|
||||
final GlobalKey<NavigatorState> navigatorKey;
|
||||
|
||||
/// The route generator callback used when the app is navigated to a
|
||||
/// named route.
|
||||
///
|
||||
/// If this returns null when building the routes to handle the specified
|
||||
/// [initialRoute], then all the routes are discarded and
|
||||
/// [Navigator.defaultRouteName] is used instead (`/`). See [initialRoute].
|
||||
///
|
||||
/// During normal app operation, the [onGenerateRoute] callback will only be
|
||||
/// applied to route names pushed by the application, and so should never
|
||||
/// return null.
|
||||
///
|
||||
/// The [Navigator] is only built if [onGenerateRoute] is not null. If
|
||||
/// [onGenerateRoute] is null, the [builder] must be non-null.
|
||||
final RouteFactory onGenerateRoute;
|
||||
|
||||
/// Called when [onGenerateRoute] fails to generate a route.
|
||||
///
|
||||
/// This callback is typically used for error handling. For example, this
|
||||
/// callback might always generate a "not found" page that describes the route
|
||||
/// that wasn't found.
|
||||
///
|
||||
/// Unknown routes can arise either from errors in the app or from external
|
||||
/// requests to push routes, such as from Android intents.
|
||||
///
|
||||
/// The [Navigator] is only built if [onGenerateRoute] is not null; if it is
|
||||
/// null, [onUnknownRoute] must also be null.
|
||||
final RouteFactory onUnknownRoute;
|
||||
|
||||
/// The name of the first route to show.
|
||||
///
|
||||
/// Defaults to [Window.defaultRouteName], which may be overridden by the code
|
||||
/// that launched the application.
|
||||
///
|
||||
/// If the route contains slashes, then it is treated as a "deep link", and
|
||||
/// before this route is pushed, the routes leading to this one are pushed
|
||||
/// also. For example, if the route was `/a/b/c`, then the app would start
|
||||
/// with the three routes `/a`, `/a/b`, and `/a/b/c` loaded, in that order.
|
||||
///
|
||||
/// If any part of this process fails to generate routes, then the
|
||||
/// [initialRoute] is ignored and [Navigator.defaultRouteName] is used instead
|
||||
/// (`/`). This can happen if the app is started with an intent that specifies
|
||||
/// a non-existent route.
|
||||
///
|
||||
/// The [Navigator] is only built if [onGenerateRoute] is not null; if it is
|
||||
/// null, [initialRoute] must also be null.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [Navigator.initialRoute], which is used to implement this property.
|
||||
/// * [Navigator.push], for pushing additional routes.
|
||||
/// * [Navigator.pop], for removing a route from the stack.
|
||||
final String initialRoute;
|
||||
|
||||
/// The list of observers for the [Navigator] created for this app.
|
||||
///
|
||||
/// This list must be replaced by a list of newly-created observers if the
|
||||
/// [navigatorKey] is changed.
|
||||
///
|
||||
/// The [Navigator] is only built if [onGenerateRoute] is not null; if it is
|
||||
/// null, [navigatorObservers] must be left to its default value, the empty
|
||||
/// list.
|
||||
final List<NavigatorObserver> navigatorObservers;
|
||||
|
||||
/// A builder for inserting widgets above the [Navigator] but below the other
|
||||
/// widgets created by the [WidgetsApp] widget, or for replacing the
|
||||
/// [Navigator] entirely.
|
||||
///
|
||||
/// For example, from the [BuildContext] passed to this method, the
|
||||
/// [Directionality], [Localizations], [DefaultTextStyle], [MediaQuery], etc,
|
||||
/// are all available. They can also be overridden in a way that impacts all
|
||||
/// the routes in the [Navigator].
|
||||
///
|
||||
/// This is rarely useful, but can be used in applications that wish to
|
||||
/// override those defaults, e.g. to force the application into right-to-left
|
||||
/// mode despite being in English, or to override the [MediaQuery] metrics
|
||||
/// (e.g. to leave a gap for advertisements shown by a plugin from OEM code).
|
||||
///
|
||||
/// The [builder] callback is passed two arguments, the [BuildContext] (as
|
||||
/// `context`) and a [Navigator] widget (as `child`).
|
||||
///
|
||||
/// If [onGenerateRoute] is null, the `child` will be null, and it is the
|
||||
/// responsibility of the [builder] to provide the application's routing
|
||||
/// machinery.
|
||||
///
|
||||
/// If [onGenerateRoute] is not null, then `child` is not null, and the
|
||||
/// returned value should include the `child` in the widget subtree; if it
|
||||
/// does not, then the application will have no navigator and the
|
||||
/// [navigatorKey], [onGenerateRoute], [onUnknownRoute], [initialRoute], and
|
||||
/// [navigatorObservers] properties will have no effect.
|
||||
///
|
||||
/// If [builder] is null, it is as if a builder was specified that returned
|
||||
/// the `child` directly. At least one of either [onGenerateRoute] or
|
||||
/// [builder] must be non-null.
|
||||
///
|
||||
/// For specifically overriding the [title] with a value based on the
|
||||
/// [Localizations], consider [onGenerateTitle] instead.
|
||||
final TransitionBuilder builder;
|
||||
|
||||
/// A one-line description used by the device to identify the app for the user.
|
||||
///
|
||||
/// On Android the titles appear above the task manager's app snapshots which are
|
||||
@ -146,50 +255,6 @@ class WidgetsApp extends StatefulWidget {
|
||||
/// application switcher.
|
||||
final Color color;
|
||||
|
||||
/// The route generator callback used when the app is navigated to a
|
||||
/// named route.
|
||||
///
|
||||
/// If this returns null when building the routes to handle the specified
|
||||
/// [initialRoute], then all the routes are discarded and
|
||||
/// [Navigator.defaultRouteName] is used instead (`/`). See [initialRoute].
|
||||
///
|
||||
/// During normal app operation, the [onGenerateRoute] callback will only be
|
||||
/// applied to route names pushed by the application, and so should never
|
||||
/// return null.
|
||||
final RouteFactory onGenerateRoute;
|
||||
|
||||
/// Called when [onGenerateRoute] fails to generate a route.
|
||||
///
|
||||
/// This callback is typically used for error handling. For example, this
|
||||
/// callback might always generate a "not found" page that describes the route
|
||||
/// that wasn't found.
|
||||
///
|
||||
/// Unknown routes can arise either from errors in the app or from external
|
||||
/// requests to push routes, such as from Android intents.
|
||||
final RouteFactory onUnknownRoute;
|
||||
|
||||
/// The name of the first route to show.
|
||||
///
|
||||
/// Defaults to [Window.defaultRouteName], which may be overridden by the code
|
||||
/// that launched the application.
|
||||
///
|
||||
/// If the route contains slashes, then it is treated as a "deep link", and
|
||||
/// before this route is pushed, the routes leading to this one are pushed
|
||||
/// also. For example, if the route was `/a/b/c`, then the app would start
|
||||
/// with the three routes `/a`, `/a/b`, and `/a/b/c` loaded, in that order.
|
||||
///
|
||||
/// If any part of this process fails to generate routes, then the
|
||||
/// [initialRoute] is ignored and [Navigator.defaultRouteName] is used instead
|
||||
/// (`/`). This can happen if the app is started with an intent that specifies
|
||||
/// a non-existent route.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [Navigator.initialRoute], which is used to implement this property.
|
||||
/// * [Navigator.push], for pushing additional routes.
|
||||
/// * [Navigator.pop], for removing a route from the stack.
|
||||
final String initialRoute;
|
||||
|
||||
/// The initial locale for this app's [Localizations] widget.
|
||||
///
|
||||
/// If the 'locale' is null the system's locale value is used.
|
||||
@ -298,12 +363,6 @@ class WidgetsApp extends StatefulWidget {
|
||||
/// representative of what will happen in release mode.
|
||||
final bool debugShowCheckedModeBanner;
|
||||
|
||||
/// The list of observers for the [Navigator] created for this app.
|
||||
///
|
||||
/// This list must be replaced by a list of newly-created observers if the
|
||||
/// [navigatorKey] is changed.
|
||||
final List<NavigatorObserver> navigatorObservers;
|
||||
|
||||
/// If true, forces the performance overlay to be visible in all instances.
|
||||
///
|
||||
/// Used by the `showPerformanceOverlay` observatory extension.
|
||||
@ -332,7 +391,72 @@ class WidgetsApp extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _WidgetsAppState extends State<WidgetsApp> implements WidgetsBindingObserver {
|
||||
|
||||
// STATE LIFECYCLE
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_updateNavigator();
|
||||
_locale = _resolveLocale(ui.window.locale, widget.supportedLocales);
|
||||
WidgetsBinding.instance.addObserver(this);
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(WidgetsApp oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
if (widget.navigatorKey != oldWidget.navigatorKey)
|
||||
_updateNavigator();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
WidgetsBinding.instance.removeObserver(this);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
void didChangeAppLifecycleState(AppLifecycleState state) { }
|
||||
|
||||
@override
|
||||
void didHaveMemoryPressure() { }
|
||||
|
||||
|
||||
// NAVIGATOR
|
||||
|
||||
GlobalKey<NavigatorState> _navigator;
|
||||
|
||||
void _updateNavigator() {
|
||||
if (widget.onGenerateRoute == null) {
|
||||
_navigator = null;
|
||||
} else {
|
||||
_navigator = widget.navigatorKey ?? new GlobalObjectKey<NavigatorState>(this);
|
||||
}
|
||||
}
|
||||
|
||||
// On Android: the user has pressed the back button.
|
||||
@override
|
||||
Future<bool> didPopRoute() async {
|
||||
assert(mounted);
|
||||
final NavigatorState navigator = _navigator?.currentState;
|
||||
if (navigator == null)
|
||||
return false;
|
||||
return await navigator.maybePop();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> didPushRoute(String route) async {
|
||||
assert(mounted);
|
||||
final NavigatorState navigator = _navigator?.currentState;
|
||||
if (navigator == null)
|
||||
return false;
|
||||
navigator.pushNamed(route);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// LOCALIZATION
|
||||
|
||||
Locale _locale;
|
||||
|
||||
Locale _resolveLocale(Locale newLocale, Iterable<Locale> supportedLocales) {
|
||||
@ -352,66 +476,6 @@ class _WidgetsAppState extends State<WidgetsApp> implements WidgetsBindingObserv
|
||||
return matchesLanguageCode ?? supportedLocales.first;
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_updateNavigator();
|
||||
_locale = _resolveLocale(ui.window.locale, widget.supportedLocales);
|
||||
WidgetsBinding.instance.addObserver(this);
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(WidgetsApp oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
if (widget.navigatorKey != oldWidget.navigatorKey)
|
||||
_updateNavigator();
|
||||
}
|
||||
|
||||
void _updateNavigator() {
|
||||
_navigator = widget.navigatorKey ?? new GlobalObjectKey<NavigatorState>(this);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
WidgetsBinding.instance.removeObserver(this);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
// On Android: the user has pressed the back button.
|
||||
@override
|
||||
Future<bool> didPopRoute() async {
|
||||
assert(mounted);
|
||||
final NavigatorState navigator = _navigator.currentState;
|
||||
assert(navigator != null);
|
||||
return await navigator.maybePop();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> didPushRoute(String route) async {
|
||||
assert(mounted);
|
||||
final NavigatorState navigator = _navigator.currentState;
|
||||
assert(navigator != null);
|
||||
navigator.pushNamed(route);
|
||||
return true;
|
||||
}
|
||||
|
||||
@override
|
||||
void didChangeMetrics() {
|
||||
setState(() {
|
||||
// The properties of ui.window have changed. We use them in our build
|
||||
// function, so we need setState(), but we don't cache anything locally.
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void didChangeTextScaleFactor() {
|
||||
setState(() {
|
||||
// The textScaleFactor property of ui.window has changed. We reference
|
||||
// ui.window in our build function, so we need to call setState(), but
|
||||
// we don't need to cache anything locally.
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void didChangeLocale(Locale locale) {
|
||||
if (locale == _locale)
|
||||
@ -435,21 +499,53 @@ class _WidgetsAppState extends State<WidgetsApp> implements WidgetsBindingObserv
|
||||
yield DefaultWidgetsLocalizations.delegate;
|
||||
}
|
||||
|
||||
@override
|
||||
void didChangeAppLifecycleState(AppLifecycleState state) { }
|
||||
|
||||
// METRICS
|
||||
|
||||
@override
|
||||
void didHaveMemoryPressure() { }
|
||||
void didChangeMetrics() {
|
||||
setState(() {
|
||||
// The properties of ui.window have changed. We use them in our build
|
||||
// function, so we need setState(), but we don't cache anything locally.
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void didChangeTextScaleFactor() {
|
||||
setState(() {
|
||||
// The textScaleFactor property of ui.window has changed. We reference
|
||||
// ui.window in our build function, so we need to call setState(), but
|
||||
// we don't need to cache anything locally.
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// BUILDER
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Widget result = new Navigator(
|
||||
Widget navigator;
|
||||
if (_navigator != null) {
|
||||
navigator = new Navigator(
|
||||
key: _navigator,
|
||||
initialRoute: widget.initialRoute ?? ui.window.defaultRouteName,
|
||||
onGenerateRoute: widget.onGenerateRoute,
|
||||
onUnknownRoute: widget.onUnknownRoute,
|
||||
observers: widget.navigatorObservers,
|
||||
);
|
||||
}
|
||||
|
||||
Widget result;
|
||||
if (widget.builder != null) {
|
||||
result = new Builder(
|
||||
builder: (BuildContext context) {
|
||||
return widget.builder(context, navigator);
|
||||
},
|
||||
);
|
||||
} else {
|
||||
assert(navigator != null);
|
||||
result = navigator;
|
||||
}
|
||||
|
||||
if (widget.textStyle != null) {
|
||||
result = new DefaultTextStyle(
|
||||
@ -502,28 +598,36 @@ class _WidgetsAppState extends State<WidgetsApp> implements WidgetsBindingObserv
|
||||
return true;
|
||||
}());
|
||||
|
||||
return new MediaQuery(
|
||||
data: new MediaQueryData.fromWindow(ui.window),
|
||||
child: new Localizations(
|
||||
locale: widget.locale ?? _locale,
|
||||
delegates: _localizationsDelegates.toList(),
|
||||
Widget title;
|
||||
if (widget.onGenerateTitle != null) {
|
||||
title = new Builder(
|
||||
// This Builder exists to provide a context below the Localizations widget.
|
||||
// The onGenerateCallback() can refer to Localizations via its context
|
||||
// parameter.
|
||||
child: new Builder(
|
||||
builder: (BuildContext context) {
|
||||
String title = widget.title;
|
||||
if (widget.onGenerateTitle != null) {
|
||||
title = widget.onGenerateTitle(context);
|
||||
final String title = widget.onGenerateTitle(context);
|
||||
assert(title != null, 'onGenerateTitle must return a non-null String');
|
||||
}
|
||||
return new Title(
|
||||
title: title,
|
||||
color: widget.color,
|
||||
child: result,
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
} else {
|
||||
title = new Title(
|
||||
title: widget.title,
|
||||
color: widget.color,
|
||||
child: result,
|
||||
);
|
||||
}
|
||||
|
||||
return new MediaQuery(
|
||||
data: new MediaQueryData.fromWindow(ui.window),
|
||||
child: new Localizations(
|
||||
locale: widget.locale ?? _locale,
|
||||
delegates: _localizationsDelegates.toList(),
|
||||
child: title,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -3557,6 +3557,14 @@ typedef Widget WidgetBuilder(BuildContext context);
|
||||
/// Used by [ListView.builder] and other APIs that use lazily-generated widgets.
|
||||
typedef Widget IndexedWidgetBuilder(BuildContext context, int index);
|
||||
|
||||
/// A builder that builds a widget given a child.
|
||||
///
|
||||
/// The child should typically be part of the returned widget tree.
|
||||
///
|
||||
/// Used by [AnimatedBuilder.builder], as well as [WidgetsApp.builder] and
|
||||
/// [MaterialApp.builder].
|
||||
typedef Widget TransitionBuilder(BuildContext context, Widget child);
|
||||
|
||||
/// An [Element] that composes other [Element]s.
|
||||
///
|
||||
/// Rather than creating a [RenderObject] directly, a [ComponentElement] creates
|
||||
|
@ -527,13 +527,6 @@ class AlignTransition extends AnimatedWidget {
|
||||
}
|
||||
}
|
||||
|
||||
/// A builder that builds a widget given a child.
|
||||
///
|
||||
/// The child should typically be part of the returned widget tree.
|
||||
///
|
||||
/// Used by [AnimatedBuilder.builder].
|
||||
typedef Widget TransitionBuilder(BuildContext context, Widget child);
|
||||
|
||||
/// A general-purpose widget for building animations.
|
||||
///
|
||||
/// AnimatedBuilder is useful for more complex widgets that wish to include
|
||||
|
65
packages/flutter/test/material/app_builder_test.dart
Normal file
65
packages/flutter/test/material/app_builder_test.dart
Normal file
@ -0,0 +1,65 @@
|
||||
// Copyright 2018 The Chromium 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_test/flutter_test.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
void main() {
|
||||
testWidgets('builder doesn\'t get called if app doesn\'t change', (WidgetTester tester) async {
|
||||
final List<String> log = <String>[];
|
||||
final Widget app = new MaterialApp(
|
||||
theme: new ThemeData(
|
||||
primarySwatch: Colors.green,
|
||||
),
|
||||
home: const Placeholder(),
|
||||
builder: (BuildContext context, Widget child) {
|
||||
log.add('build');
|
||||
expect(Theme.of(context).primaryColor, Colors.green.shade500);
|
||||
expect(Directionality.of(context), TextDirection.ltr);
|
||||
expect(child, const isInstanceOf<Navigator>());
|
||||
return const Placeholder();
|
||||
},
|
||||
);
|
||||
await tester.pumpWidget(
|
||||
new Directionality(
|
||||
textDirection: TextDirection.rtl,
|
||||
child: app,
|
||||
),
|
||||
);
|
||||
expect(log, <String>['build']);
|
||||
await tester.pumpWidget(
|
||||
new Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: app,
|
||||
),
|
||||
);
|
||||
expect(log, <String>['build']);
|
||||
});
|
||||
|
||||
testWidgets('builder doesn\'t get called if app doesn\'t change', (WidgetTester tester) async {
|
||||
final List<String> log = <String>[];
|
||||
await tester.pumpWidget(
|
||||
new MaterialApp(
|
||||
theme: new ThemeData(
|
||||
primarySwatch: Colors.yellow,
|
||||
),
|
||||
home: new Builder(
|
||||
builder: (BuildContext context) {
|
||||
log.add('build');
|
||||
expect(Theme.of(context).primaryColor, Colors.yellow.shade500);
|
||||
expect(Directionality.of(context), TextDirection.rtl);
|
||||
return const Placeholder();
|
||||
},
|
||||
),
|
||||
builder: (BuildContext context, Widget child) {
|
||||
return new Directionality(
|
||||
textDirection: TextDirection.rtl,
|
||||
child: child,
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
expect(log, <String>['build']);
|
||||
});
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user