parent
f8d2615d49
commit
ea1182effd
@ -170,6 +170,7 @@ class CupertinoApp extends StatefulWidget {
|
||||
this.actions,
|
||||
this.restorationScopeId,
|
||||
this.scrollBehavior,
|
||||
this.useInheritedMediaQuery = false,
|
||||
}) : assert(routes != null),
|
||||
assert(navigatorObservers != null),
|
||||
assert(title != null),
|
||||
@ -210,6 +211,7 @@ class CupertinoApp extends StatefulWidget {
|
||||
this.actions,
|
||||
this.restorationScopeId,
|
||||
this.scrollBehavior,
|
||||
this.useInheritedMediaQuery = false,
|
||||
}) : assert(title != null),
|
||||
assert(showPerformanceOverlay != null),
|
||||
assert(checkerboardRasterCacheImages != null),
|
||||
@ -407,6 +409,9 @@ class CupertinoApp extends StatefulWidget {
|
||||
/// in a subtree.
|
||||
final ScrollBehavior? scrollBehavior;
|
||||
|
||||
/// {@macro flutter.widgets.widgetsApp.useInheritedMediaQuery}
|
||||
final bool useInheritedMediaQuery;
|
||||
|
||||
@override
|
||||
State<CupertinoApp> createState() => _CupertinoAppState();
|
||||
|
||||
@ -530,6 +535,7 @@ class _CupertinoAppState extends State<CupertinoApp> {
|
||||
shortcuts: widget.shortcuts,
|
||||
actions: widget.actions,
|
||||
restorationScopeId: widget.restorationScopeId,
|
||||
useInheritedMediaQuery: widget.useInheritedMediaQuery,
|
||||
);
|
||||
}
|
||||
return WidgetsApp(
|
||||
@ -564,6 +570,7 @@ class _CupertinoAppState extends State<CupertinoApp> {
|
||||
shortcuts: widget.shortcuts,
|
||||
actions: widget.actions,
|
||||
restorationScopeId: widget.restorationScopeId,
|
||||
useInheritedMediaQuery: widget.useInheritedMediaQuery,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -200,6 +200,7 @@ class MaterialApp extends StatefulWidget {
|
||||
this.actions,
|
||||
this.restorationScopeId,
|
||||
this.scrollBehavior,
|
||||
this.useInheritedMediaQuery = false,
|
||||
}) : assert(routes != null),
|
||||
assert(navigatorObservers != null),
|
||||
assert(title != null),
|
||||
@ -247,6 +248,7 @@ class MaterialApp extends StatefulWidget {
|
||||
this.actions,
|
||||
this.restorationScopeId,
|
||||
this.scrollBehavior,
|
||||
this.useInheritedMediaQuery = false,
|
||||
}) : assert(routeInformationParser != null),
|
||||
assert(routerDelegate != null),
|
||||
assert(title != null),
|
||||
@ -665,6 +667,9 @@ class MaterialApp extends StatefulWidget {
|
||||
/// * <https://material.io/design/layout/spacing-methods.html>
|
||||
final bool debugShowMaterialGrid;
|
||||
|
||||
/// {@macro flutter.widgets.widgetsApp.useInheritedMediaQuery}
|
||||
final bool useInheritedMediaQuery;
|
||||
|
||||
@override
|
||||
State<MaterialApp> createState() => _MaterialAppState();
|
||||
|
||||
@ -858,6 +863,7 @@ class _MaterialAppState extends State<MaterialApp> {
|
||||
shortcuts: widget.shortcuts,
|
||||
actions: widget.actions,
|
||||
restorationScopeId: widget.restorationScopeId,
|
||||
useInheritedMediaQuery: widget.useInheritedMediaQuery,
|
||||
);
|
||||
}
|
||||
|
||||
@ -893,6 +899,7 @@ class _MaterialAppState extends State<MaterialApp> {
|
||||
shortcuts: widget.shortcuts,
|
||||
actions: widget.actions,
|
||||
restorationScopeId: widget.restorationScopeId,
|
||||
useInheritedMediaQuery: widget.useInheritedMediaQuery,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -236,6 +236,9 @@ typedef InitialRouteListFactory = List<Route<dynamic>> Function(String initialRo
|
||||
/// It is used by both [MaterialApp] and [CupertinoApp] to implement base
|
||||
/// functionality for an app.
|
||||
///
|
||||
/// Builds a [MediaQuery] using [MediaQuery.fromWindow]. To use an inherited
|
||||
/// [MediaQuery] instead, set [useInheritedMediaQuery] to true.
|
||||
///
|
||||
/// Find references to many of the widgets that [WidgetsApp] wraps in the "See
|
||||
/// also" section.
|
||||
///
|
||||
@ -247,6 +250,8 @@ typedef InitialRouteListFactory = List<Route<dynamic>> Function(String initialRo
|
||||
/// without an explicit style.
|
||||
/// * [MediaQuery], which establishes a subtree in which media queries resolve
|
||||
/// to a [MediaQueryData].
|
||||
/// * [MediaQuery.fromWindow], which builds a [MediaQuery] with data derived
|
||||
/// from [WidgetsBinding.window].
|
||||
/// * [Localizations], which defines the [Locale] for its `child`.
|
||||
/// * [Title], a widget that describes this app in the operating system.
|
||||
/// * [Navigator], a widget that manages a set of child widgets with a stack
|
||||
@ -327,6 +332,7 @@ class WidgetsApp extends StatefulWidget {
|
||||
this.shortcuts,
|
||||
this.actions,
|
||||
this.restorationScopeId,
|
||||
this.useInheritedMediaQuery = false,
|
||||
}) : assert(navigatorObservers != null),
|
||||
assert(routes != null),
|
||||
assert(
|
||||
@ -423,6 +429,7 @@ class WidgetsApp extends StatefulWidget {
|
||||
this.shortcuts,
|
||||
this.actions,
|
||||
this.restorationScopeId,
|
||||
this.useInheritedMediaQuery = false,
|
||||
}) : assert(
|
||||
routeInformationParser != null &&
|
||||
routerDelegate != null,
|
||||
@ -1111,6 +1118,14 @@ class WidgetsApp extends StatefulWidget {
|
||||
/// {@endtemplate}
|
||||
final String? restorationScopeId;
|
||||
|
||||
/// {@template flutter.widgets.widgetsApp.useInheritedMediaQuery}
|
||||
/// If true, an inherited MediaQuery will be used. If one is not available,
|
||||
/// or this is false, one will be built from the window.
|
||||
///
|
||||
/// Cannot be null, defaults to false.
|
||||
/// {@endtemplate}
|
||||
final bool useInheritedMediaQuery;
|
||||
|
||||
/// If true, forces the performance overlay to be visible in all instances.
|
||||
///
|
||||
/// Used by the `showPerformanceOverlay` observatory extension.
|
||||
@ -1635,6 +1650,19 @@ class _WidgetsAppState extends State<WidgetsApp> with WidgetsBindingObserver {
|
||||
|
||||
assert(_debugCheckLocalizations(appLocale));
|
||||
|
||||
Widget child = Localizations(
|
||||
locale: appLocale,
|
||||
delegates: _localizationsDelegates.toList(),
|
||||
child: title,
|
||||
);
|
||||
|
||||
final MediaQueryData? data = MediaQuery.maybeOf(context);
|
||||
if (!widget.useInheritedMediaQuery || data == null) {
|
||||
child = MediaQuery.fromWindow(
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
|
||||
return RootRestorationScope(
|
||||
restorationId: widget.restorationScopeId,
|
||||
child: Shortcuts(
|
||||
@ -1648,13 +1676,7 @@ class _WidgetsAppState extends State<WidgetsApp> with WidgetsBindingObserver {
|
||||
child: DefaultTextEditingActions(
|
||||
child: FocusTraversalGroup(
|
||||
policy: ReadingOrderTraversalPolicy(),
|
||||
child: _MediaQueryFromWindow(
|
||||
child: Localizations(
|
||||
locale: appLocale,
|
||||
delegates: _localizationsDelegates.toList(),
|
||||
child: title,
|
||||
),
|
||||
),
|
||||
child: child,
|
||||
),
|
||||
),
|
||||
),
|
||||
@ -1663,81 +1685,3 @@ class _WidgetsAppState extends State<WidgetsApp> with WidgetsBindingObserver {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Builds [MediaQuery] from `window` by listening to [WidgetsBinding].
|
||||
///
|
||||
/// It is performed in a standalone widget to rebuild **only** [MediaQuery] and
|
||||
/// its dependents when `window` changes, instead of rebuilding the entire widget tree.
|
||||
class _MediaQueryFromWindow extends StatefulWidget {
|
||||
const _MediaQueryFromWindow({Key? key, required this.child}) : super(key: key);
|
||||
|
||||
final Widget child;
|
||||
|
||||
@override
|
||||
_MediaQueryFromWindowsState createState() => _MediaQueryFromWindowsState();
|
||||
}
|
||||
|
||||
class _MediaQueryFromWindowsState extends State<_MediaQueryFromWindow> with WidgetsBindingObserver {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
WidgetsBinding.instance!.addObserver(this);
|
||||
}
|
||||
|
||||
// ACCESSIBILITY
|
||||
|
||||
@override
|
||||
void didChangeAccessibilityFeatures() {
|
||||
setState(() {
|
||||
// The properties of window have changed. We use them in our build
|
||||
// function, so we need setState(), but we don't cache anything locally.
|
||||
});
|
||||
}
|
||||
|
||||
// METRICS
|
||||
|
||||
@override
|
||||
void didChangeMetrics() {
|
||||
setState(() {
|
||||
// The properties of 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 window has changed. We reference
|
||||
// window in our build function, so we need to call setState(), but
|
||||
// we don't need to cache anything locally.
|
||||
});
|
||||
}
|
||||
|
||||
// RENDERING
|
||||
@override
|
||||
void didChangePlatformBrightness() {
|
||||
setState(() {
|
||||
// The platformBrightness property of window has changed. We reference
|
||||
// window in our build function, so we need to call setState(), but
|
||||
// we don't need to cache anything locally.
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
MediaQueryData data = MediaQueryData.fromWindow(WidgetsBinding.instance!.window);
|
||||
if (!kReleaseMode) {
|
||||
data = data.copyWith(platformBrightness: debugBrightnessOverride);
|
||||
}
|
||||
return MediaQuery(
|
||||
data: data,
|
||||
child: widget.child,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
WidgetsBinding.instance!.removeObserver(this);
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ import 'dart:ui' show Brightness;
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
import 'basic.dart';
|
||||
import 'binding.dart';
|
||||
import 'debug.dart';
|
||||
import 'framework.dart';
|
||||
|
||||
@ -787,6 +788,28 @@ class MediaQuery extends InheritedWidget {
|
||||
);
|
||||
}
|
||||
|
||||
/// Provides a [MediaQuery] which is built and updated using the latest
|
||||
/// [WidgetsBinding.window] values.
|
||||
///
|
||||
/// The [MediaQuery] is wrapped in a separate widget to ensure that only it
|
||||
/// and its dependents are updated when `window` changes, instead of
|
||||
/// rebuilding the whole widget tree.
|
||||
///
|
||||
/// This should be inserted into the widget tree when the [MediaQuery] view
|
||||
/// padding is consumed by a widget in such a way that the view padding is no
|
||||
/// longer exposed to the widget's descendants or siblings.
|
||||
///
|
||||
/// The [child] argument is required and must not be null.
|
||||
static Widget fromWindow({
|
||||
Key? key,
|
||||
required Widget child,
|
||||
}) {
|
||||
return _MediaQueryFromWindow(
|
||||
key: key,
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
|
||||
/// Contains information about the current media.
|
||||
///
|
||||
/// For example, the [MediaQueryData.size] property contains the width and
|
||||
@ -922,3 +945,100 @@ enum NavigationMode {
|
||||
/// focus (although they remain disabled) when traversed.
|
||||
directional,
|
||||
}
|
||||
|
||||
/// Provides a [MediaQuery] which is built and updated using the latest
|
||||
/// [WidgetsBinding.window] values.
|
||||
///
|
||||
/// Receives `window` updates by listening to [WidgetsBinding].
|
||||
///
|
||||
/// The standalone widget ensures that it rebuilds **only** [MediaQuery] and
|
||||
/// its dependents when `window` changes, instead of rebuilding the entire
|
||||
/// widget tree.
|
||||
///
|
||||
/// It is used by [WidgetsApp] if no other [MediaQuery] is available above it.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [MediaQuery], which establishes a subtree in which media queries resolve
|
||||
/// to a [MediaQueryData].
|
||||
class _MediaQueryFromWindow extends StatefulWidget {
|
||||
/// Creates a [_MediaQueryFromWindow] that provides a [MediaQuery] to its
|
||||
/// descendants using the `window` to keep [MediaQueryData] up to date.
|
||||
///
|
||||
/// The [child] must not be null.
|
||||
const _MediaQueryFromWindow({
|
||||
Key? key,
|
||||
required this.child,
|
||||
}) : super(key: key);
|
||||
|
||||
/// {@macro flutter.widgets.ProxyWidget.child}
|
||||
final Widget child;
|
||||
|
||||
@override
|
||||
State<_MediaQueryFromWindow> createState() => _MediaQueryFromWindowState();
|
||||
}
|
||||
|
||||
class _MediaQueryFromWindowState extends State<_MediaQueryFromWindow> with WidgetsBindingObserver {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
WidgetsBinding.instance!.addObserver(this);
|
||||
}
|
||||
|
||||
// ACCESSIBILITY
|
||||
|
||||
@override
|
||||
void didChangeAccessibilityFeatures() {
|
||||
setState(() {
|
||||
// The properties of window have changed. We use them in our build
|
||||
// function, so we need setState(), but we don't cache anything locally.
|
||||
});
|
||||
}
|
||||
|
||||
// METRICS
|
||||
|
||||
@override
|
||||
void didChangeMetrics() {
|
||||
setState(() {
|
||||
// The properties of 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 window has changed. We reference
|
||||
// window in our build function, so we need to call setState(), but
|
||||
// we don't need to cache anything locally.
|
||||
});
|
||||
}
|
||||
|
||||
// RENDERING
|
||||
@override
|
||||
void didChangePlatformBrightness() {
|
||||
setState(() {
|
||||
// The platformBrightness property of window has changed. We reference
|
||||
// window in our build function, so we need to call setState(), but
|
||||
// we don't need to cache anything locally.
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
MediaQueryData data = MediaQueryData.fromWindow(WidgetsBinding.instance!.window);
|
||||
if (!kReleaseMode) {
|
||||
data = data.copyWith(platformBrightness: debugBrightnessOverride);
|
||||
}
|
||||
return MediaQuery(
|
||||
data: data,
|
||||
child: widget.child,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
WidgetsBinding.instance!.removeObserver(this);
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
@ -208,6 +208,26 @@ void main() {
|
||||
expect(scrollBehavior.runtimeType, MockScrollBehavior);
|
||||
expect(scrollBehavior.getScrollPhysics(capturedContext).runtimeType, NeverScrollableScrollPhysics);
|
||||
});
|
||||
|
||||
testWidgets('When `useInheritedMediaQuery` is true an existing MediaQuery is used if one is available', (WidgetTester tester) async {
|
||||
late BuildContext capturedContext;
|
||||
final UniqueKey uniqueKey = UniqueKey();
|
||||
await tester.pumpWidget(
|
||||
MediaQuery(
|
||||
key: uniqueKey,
|
||||
data: const MediaQueryData(),
|
||||
child: CupertinoApp(
|
||||
useInheritedMediaQuery: true,
|
||||
builder: (BuildContext context, Widget? child) {
|
||||
capturedContext = context;
|
||||
return const Placeholder();
|
||||
},
|
||||
color: const Color(0xFF123456),
|
||||
),
|
||||
),
|
||||
);
|
||||
expect(capturedContext.dependOnInheritedWidgetOfExactType<MediaQuery>()?.key, uniqueKey);
|
||||
});
|
||||
}
|
||||
|
||||
class MockScrollBehavior extends ScrollBehavior {
|
||||
|
@ -1069,6 +1069,26 @@ void main() {
|
||||
expect(scrollBehavior.runtimeType, MockScrollBehavior);
|
||||
expect(scrollBehavior.getScrollPhysics(capturedContext).runtimeType, NeverScrollableScrollPhysics);
|
||||
});
|
||||
|
||||
testWidgets('When `useInheritedMediaQuery` is true an existing MediaQuery is used if one is available', (WidgetTester tester) async {
|
||||
late BuildContext capturedContext;
|
||||
final UniqueKey uniqueKey = UniqueKey();
|
||||
await tester.pumpWidget(
|
||||
MediaQuery(
|
||||
key: uniqueKey,
|
||||
data: const MediaQueryData(),
|
||||
child: MaterialApp(
|
||||
useInheritedMediaQuery: true,
|
||||
builder: (BuildContext context, Widget? child) {
|
||||
capturedContext = context;
|
||||
return const Placeholder();
|
||||
},
|
||||
color: const Color(0xFF123456),
|
||||
),
|
||||
),
|
||||
);
|
||||
expect(capturedContext.dependOnInheritedWidgetOfExactType<MediaQuery>()?.key, uniqueKey);
|
||||
});
|
||||
}
|
||||
|
||||
class MockScrollBehavior extends ScrollBehavior {
|
||||
|
@ -455,6 +455,56 @@ void main() {
|
||||
const Locale('zh'),
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('WidgetsApp creates a MediaQuery if `useInheritedMediaQuery` is set to false', (WidgetTester tester) async {
|
||||
late BuildContext capturedContext;
|
||||
await tester.pumpWidget(
|
||||
WidgetsApp(
|
||||
useInheritedMediaQuery: false,
|
||||
builder: (BuildContext context, Widget? child) {
|
||||
capturedContext = context;
|
||||
return const Placeholder();
|
||||
},
|
||||
color: const Color(0xFF123456),
|
||||
),
|
||||
);
|
||||
expect(MediaQuery.of(capturedContext), isNotNull);
|
||||
});
|
||||
|
||||
testWidgets('WidgetsApp does not create MediaQuery if `useInheritedMediaQuery` is set to true and one is available', (WidgetTester tester) async {
|
||||
late BuildContext capturedContext;
|
||||
final UniqueKey uniqueKey = UniqueKey();
|
||||
await tester.pumpWidget(
|
||||
MediaQuery(
|
||||
key: uniqueKey,
|
||||
data: const MediaQueryData(),
|
||||
child: WidgetsApp(
|
||||
useInheritedMediaQuery: true,
|
||||
builder: (BuildContext context, Widget? child) {
|
||||
capturedContext = context;
|
||||
return const Placeholder();
|
||||
},
|
||||
color: const Color(0xFF123456),
|
||||
),
|
||||
),
|
||||
);
|
||||
expect(capturedContext.dependOnInheritedWidgetOfExactType<MediaQuery>()?.key, uniqueKey);
|
||||
});
|
||||
|
||||
testWidgets('WidgetsApp does create a MediaQuery if `useInheritedMediaQuery` is set to true and none is available', (WidgetTester tester) async {
|
||||
late BuildContext capturedContext;
|
||||
await tester.pumpWidget(
|
||||
WidgetsApp(
|
||||
useInheritedMediaQuery: true,
|
||||
builder: (BuildContext context, Widget? child) {
|
||||
capturedContext = context;
|
||||
return const Placeholder();
|
||||
},
|
||||
color: const Color(0xFF123456),
|
||||
),
|
||||
);
|
||||
expect(MediaQuery.of(capturedContext), isNotNull);
|
||||
});
|
||||
}
|
||||
|
||||
typedef SimpleRouterDelegateBuilder = Widget Function(BuildContext, RouteInformation);
|
||||
|
@ -627,4 +627,49 @@ void main() {
|
||||
expect(outsideBoldTextOverride, false);
|
||||
expect(insideBoldTextOverride, true);
|
||||
});
|
||||
|
||||
testWidgets('MediaQuery.fromWindow creates a MediaQuery', (WidgetTester tester) async {
|
||||
bool hasMediaQueryAsParentOutside = false;
|
||||
bool hasMediaQueryAsParentInside = false;
|
||||
|
||||
await tester.pumpWidget(
|
||||
Builder(
|
||||
builder: (BuildContext context) {
|
||||
hasMediaQueryAsParentOutside =
|
||||
context.findAncestorWidgetOfExactType<MediaQuery>() != null;
|
||||
return MediaQuery.fromWindow(
|
||||
child: Builder(
|
||||
builder: (BuildContext context) {
|
||||
hasMediaQueryAsParentInside =
|
||||
context.findAncestorWidgetOfExactType<MediaQuery>() != null;
|
||||
return const SizedBox();
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
expect(hasMediaQueryAsParentOutside, false);
|
||||
expect(hasMediaQueryAsParentInside, true);
|
||||
});
|
||||
|
||||
testWidgets('MediaQueryData.fromWindow is created using window values', (WidgetTester tester)
|
||||
async {
|
||||
final MediaQueryData windowData = MediaQueryData.fromWindow(WidgetsBinding.instance!.window);
|
||||
late MediaQueryData fromWindowData;
|
||||
|
||||
await tester.pumpWidget(
|
||||
MediaQuery.fromWindow(
|
||||
child: Builder(
|
||||
builder: (BuildContext context) {
|
||||
fromWindowData = MediaQuery.of(context);
|
||||
return const SizedBox();
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
expect(windowData, equals(fromWindowData));
|
||||
});
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user