Skip to content
This repository was archived by the owner on Feb 22, 2023. It is now read-only.

Commit a45727d

Browse files
Add MediaQuery to View (#118004)
* Add MediaQuery to View * unify API * fix test * add test * comment * better doc * Apply suggestions from code review Co-authored-by: Greg Spencer <[email protected]> Co-authored-by: Greg Spencer <[email protected]>
1 parent 3be330a commit a45727d

19 files changed

+934
-353
lines changed

packages/flutter/lib/src/widgets/app.dart

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1730,8 +1730,7 @@ class _WidgetsAppState extends State<WidgetsApp> with WidgetsBindingObserver {
17301730
child: title,
17311731
);
17321732

1733-
final MediaQueryData? data = MediaQuery.maybeOf(context);
1734-
if (!widget.useInheritedMediaQuery || data == null) {
1733+
if (!widget.useInheritedMediaQuery || MediaQuery.maybeOf(context) == null) {
17351734
child = MediaQuery.fromWindow(
17361735
child: child,
17371736
);

packages/flutter/lib/src/widgets/media_query.dart

Lines changed: 165 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -138,8 +138,8 @@ enum _MediaQueryAspect {
138138
class MediaQueryData {
139139
/// Creates data for a media query with explicit values.
140140
///
141-
/// Consider using [MediaQueryData.fromWindow] to create data based on a
142-
/// [dart:ui.PlatformDispatcher].
141+
/// Consider using [MediaQueryData.fromView] to create data based on a
142+
/// [dart:ui.FlutterView].
143143
const MediaQueryData({
144144
this.size = Size.zero,
145145
this.devicePixelRatio = 1.0,
@@ -167,24 +167,60 @@ class MediaQueryData {
167167
/// window's metrics change. For example, see
168168
/// [WidgetsBindingObserver.didChangeMetrics] or
169169
/// [dart:ui.PlatformDispatcher.onMetricsChanged].
170-
MediaQueryData.fromWindow(ui.FlutterView window)
171-
: size = window.physicalSize / window.devicePixelRatio,
172-
devicePixelRatio = window.devicePixelRatio,
173-
textScaleFactor = window.platformDispatcher.textScaleFactor,
174-
platformBrightness = window.platformDispatcher.platformBrightness,
175-
padding = EdgeInsets.fromWindowPadding(window.padding, window.devicePixelRatio),
176-
viewPadding = EdgeInsets.fromWindowPadding(window.viewPadding, window.devicePixelRatio),
177-
viewInsets = EdgeInsets.fromWindowPadding(window.viewInsets, window.devicePixelRatio),
178-
systemGestureInsets = EdgeInsets.fromWindowPadding(window.systemGestureInsets, window.devicePixelRatio),
179-
accessibleNavigation = window.platformDispatcher.accessibilityFeatures.accessibleNavigation,
180-
invertColors = window.platformDispatcher.accessibilityFeatures.invertColors,
181-
disableAnimations = window.platformDispatcher.accessibilityFeatures.disableAnimations,
182-
boldText = window.platformDispatcher.accessibilityFeatures.boldText,
183-
highContrast = window.platformDispatcher.accessibilityFeatures.highContrast,
184-
alwaysUse24HourFormat = window.platformDispatcher.alwaysUse24HourFormat,
185-
navigationMode = NavigationMode.traditional,
186-
gestureSettings = DeviceGestureSettings.fromWindow(window),
187-
displayFeatures = window.displayFeatures;
170+
factory MediaQueryData.fromWindow(ui.FlutterView window) => MediaQueryData.fromView(window);
171+
172+
/// Creates data for a [MediaQuery] based on the given `view`.
173+
///
174+
/// If provided, the `platformData` is used to fill in the platform-specific
175+
/// aspects of the newly created [MediaQueryData]. If `platformData` is null,
176+
/// the `view`'s [PlatformDispatcher] is consulted to construct the
177+
/// platform-specific data.
178+
///
179+
/// Data which is exposed directly on the [FlutterView] is considered
180+
/// view-specific. Data which is only exposed via the
181+
/// [FlutterView.platformDispatcher] property is considered platform-specific.
182+
///
183+
/// Callers of this method should ensure that they also register for
184+
/// notifications so that the [MediaQueryData] can be updated when any data
185+
/// used to construct it changes. Notifications to consider are:
186+
///
187+
/// * [WidgetsBindingObserver.didChangeMetrics] or
188+
/// [dart:ui.PlatformDispatcher.onMetricsChanged],
189+
/// * [WidgetsBindingObserver.didChangeAccessibilityFeatures] or
190+
/// [dart:ui.PlatformDispatcher.onAccessibilityFeaturesChanged],
191+
/// * [WidgetsBindingObserver.didChangeTextScaleFactor] or
192+
/// [dart:ui.PlatformDispatcher.onTextScaleFactorChanged],
193+
/// * [WidgetsBindingObserver.didChangePlatformBrightness] or
194+
/// [dart:ui.PlatformDispatcher.onPlatformBrightnessChanged].
195+
///
196+
/// The last three notifications are only relevant if no `platformData` is
197+
/// provided. If `platformData` is provided, callers should ensure to call
198+
/// this method again when it changes to keep the constructed [MediaQueryData]
199+
/// updated.
200+
///
201+
/// See also:
202+
///
203+
/// * [MediaQuery.fromView], which constructs [MediaQueryData] from a provided
204+
/// [FlutterView], makes it available to descendant widgets, and sets up
205+
/// the appropriate notification listeners to keep the data updated.
206+
MediaQueryData.fromView(ui.FlutterView view, {MediaQueryData? platformData})
207+
: size = view.physicalSize / view.devicePixelRatio,
208+
devicePixelRatio = view.devicePixelRatio,
209+
textScaleFactor = platformData?.textScaleFactor ?? view.platformDispatcher.textScaleFactor,
210+
platformBrightness = platformData?.platformBrightness ?? view.platformDispatcher.platformBrightness,
211+
padding = EdgeInsets.fromWindowPadding(view.padding, view.devicePixelRatio),
212+
viewPadding = EdgeInsets.fromWindowPadding(view.viewPadding, view.devicePixelRatio),
213+
viewInsets = EdgeInsets.fromWindowPadding(view.viewInsets, view.devicePixelRatio),
214+
systemGestureInsets = EdgeInsets.fromWindowPadding(view.systemGestureInsets, view.devicePixelRatio),
215+
accessibleNavigation = platformData?.accessibleNavigation ?? view.platformDispatcher.accessibilityFeatures.accessibleNavigation,
216+
invertColors = platformData?.invertColors ?? view.platformDispatcher.accessibilityFeatures.invertColors,
217+
disableAnimations = platformData?.disableAnimations ?? view.platformDispatcher.accessibilityFeatures.disableAnimations,
218+
boldText = platformData?.boldText ?? view.platformDispatcher.accessibilityFeatures.boldText,
219+
highContrast = platformData?.highContrast ?? view.platformDispatcher.accessibilityFeatures.highContrast,
220+
alwaysUse24HourFormat = platformData?.alwaysUse24HourFormat ?? view.platformDispatcher.alwaysUse24HourFormat,
221+
navigationMode = platformData?.navigationMode ?? NavigationMode.traditional,
222+
gestureSettings = DeviceGestureSettings.fromWindow(view),
223+
displayFeatures = view.displayFeatures;
188224

189225
/// The size of the media in logical pixels (e.g, the size of the screen).
190226
///
@@ -889,17 +925,43 @@ class MediaQuery extends InheritedModel<_MediaQueryAspect> {
889925
/// and its dependents are updated when `window` changes, instead of
890926
/// rebuilding the whole widget tree.
891927
///
892-
/// This should be inserted into the widget tree when the [MediaQuery] view
893-
/// padding is consumed by a widget in such a way that the view padding is no
894-
/// longer exposed to the widget's descendants or siblings.
895-
///
896928
/// The [child] argument is required and must not be null.
897929
static Widget fromWindow({
898930
Key? key,
899931
required Widget child,
900932
}) {
901-
return _MediaQueryFromWindow(
933+
return _MediaQueryFromView(
934+
key: key,
935+
view: WidgetsBinding.instance.window,
936+
ignoreParentData: true,
937+
child: child,
938+
);
939+
}
940+
941+
/// Wraps the [child] in a [MediaQuery] which is built using data from the
942+
/// provided [view].
943+
///
944+
/// The [MediaQuery] is constructed using the platform-specific data of the
945+
/// surrounding [MediaQuery] and the view-specific data of the provided
946+
/// [view]. If no surrounding [MediaQuery] exists, the platform-specific data
947+
/// is generated from the [PlatformDispatcher] associated with the provided
948+
/// [view]. Any information that's exposed via the [PlatformDispatcher] is
949+
/// considered platform-specific. Data exposed directly on the [FlutterView]
950+
/// (excluding its [FlutterView.platformDispatcher] property) is considered
951+
/// view-specific.
952+
///
953+
/// The injected [MediaQuery] automatically updates when any of the data used
954+
/// to construct it changes.
955+
///
956+
/// The [view] and [child] argument is required and must not be null.
957+
static Widget fromView({
958+
Key? key,
959+
required FlutterView view,
960+
required Widget child,
961+
}) {
962+
return _MediaQueryFromView(
902963
key: key,
964+
view: view,
903965
child: child,
904966
);
905967
}
@@ -1399,99 +1461,121 @@ enum NavigationMode {
13991461
directional,
14001462
}
14011463

1402-
/// Provides a [MediaQuery] which is built and updated using the latest
1403-
/// [WidgetsBinding.window] values.
1404-
///
1405-
/// Receives `window` updates by listening to [WidgetsBinding].
1406-
///
1407-
/// The standalone widget ensures that it rebuilds **only** [MediaQuery] and
1408-
/// its dependents when `window` changes, instead of rebuilding the entire
1409-
/// widget tree.
1410-
///
1411-
/// It is used by [WidgetsApp] if no other [MediaQuery] is available above it.
1412-
///
1413-
/// See also:
1414-
///
1415-
/// * [MediaQuery], which establishes a subtree in which media queries resolve
1416-
/// to a [MediaQueryData].
1417-
class _MediaQueryFromWindow extends StatefulWidget {
1418-
/// Creates a [_MediaQueryFromWindow] that provides a [MediaQuery] to its
1419-
/// descendants using the `window` to keep [MediaQueryData] up to date.
1420-
///
1421-
/// The [child] must not be null.
1422-
const _MediaQueryFromWindow({
1464+
class _MediaQueryFromView extends StatefulWidget {
1465+
const _MediaQueryFromView({
14231466
super.key,
1467+
required this.view,
1468+
this.ignoreParentData = false,
14241469
required this.child,
14251470
});
14261471

1427-
/// {@macro flutter.widgets.ProxyWidget.child}
1472+
final FlutterView view;
1473+
final bool ignoreParentData;
14281474
final Widget child;
14291475

14301476
@override
1431-
State<_MediaQueryFromWindow> createState() => _MediaQueryFromWindowState();
1477+
State<_MediaQueryFromView> createState() => _MediaQueryFromViewState();
14321478
}
14331479

1434-
class _MediaQueryFromWindowState extends State<_MediaQueryFromWindow> with WidgetsBindingObserver {
1480+
class _MediaQueryFromViewState extends State<_MediaQueryFromView> with WidgetsBindingObserver {
1481+
MediaQueryData? _parentData;
1482+
MediaQueryData? _data;
1483+
14351484
@override
14361485
void initState() {
14371486
super.initState();
14381487
WidgetsBinding.instance.addObserver(this);
14391488
}
14401489

1441-
// ACCESSIBILITY
1490+
@override
1491+
void didChangeDependencies() {
1492+
super.didChangeDependencies();
1493+
_updateParentData();
1494+
_updateData();
1495+
assert(_data != null);
1496+
}
14421497

14431498
@override
1444-
void didChangeAccessibilityFeatures() {
1445-
setState(() {
1446-
// The properties of window have changed. We use them in our build
1447-
// function, so we need setState(), but we don't cache anything locally.
1448-
});
1499+
void didUpdateWidget(_MediaQueryFromView oldWidget) {
1500+
super.didUpdateWidget(oldWidget);
1501+
if (widget.ignoreParentData != oldWidget.ignoreParentData) {
1502+
_updateParentData();
1503+
}
1504+
if (_data == null || oldWidget.view != widget.view) {
1505+
_updateData();
1506+
}
1507+
assert(_data != null);
1508+
}
1509+
1510+
void _updateParentData() {
1511+
_parentData = widget.ignoreParentData ? null : MediaQuery.maybeOf(context);
1512+
_data = null; // _updateData must be called again after changing parent data.
14491513
}
14501514

1451-
// METRICS
1515+
void _updateData() {
1516+
final MediaQueryData newData = MediaQueryData.fromView(widget.view, platformData: _parentData);
1517+
if (newData != _data) {
1518+
setState(() {
1519+
_data = newData;
1520+
});
1521+
}
1522+
}
1523+
1524+
@override
1525+
void didChangeAccessibilityFeatures() {
1526+
// If we have a parent, it dictates our accessibility features. If we don't
1527+
// have a parent, we get our accessibility features straight from the
1528+
// PlatformDispatcher and need to update our data in response to the
1529+
// PlatformDispatcher changing its accessibility features setting.
1530+
if (_parentData == null) {
1531+
_updateData();
1532+
}
1533+
}
14521534

14531535
@override
14541536
void didChangeMetrics() {
1455-
setState(() {
1456-
// The properties of window have changed. We use them in our build
1457-
// function, so we need setState(), but we don't cache anything locally.
1458-
});
1537+
_updateData();
14591538
}
14601539

14611540
@override
14621541
void didChangeTextScaleFactor() {
1463-
setState(() {
1464-
// The textScaleFactor property of window has changed. We reference
1465-
// window in our build function, so we need to call setState(), but
1466-
// we don't need to cache anything locally.
1467-
});
1542+
// If we have a parent, it dictates our text scale factor. If we don't have
1543+
// a parent, we get our text scale factor from the PlatformDispatcher and
1544+
// need to update our data in response to the PlatformDispatcher changing
1545+
// its text scale factor setting.
1546+
if (_parentData == null) {
1547+
_updateData();
1548+
}
14681549
}
14691550

1470-
// RENDERING
14711551
@override
14721552
void didChangePlatformBrightness() {
1473-
setState(() {
1474-
// The platformBrightness property of window has changed. We reference
1475-
// window in our build function, so we need to call setState(), but
1476-
// we don't need to cache anything locally.
1477-
});
1553+
// If we have a parent, it dictates our platform brightness. If we don't
1554+
// have a parent, we get our platform brightness from the PlatformDispatcher
1555+
// and need to update our data in response to the PlatformDispatcher
1556+
// changing its platform brightness setting.
1557+
if (_parentData == null) {
1558+
_updateData();
1559+
}
1560+
}
1561+
1562+
@override
1563+
void dispose() {
1564+
WidgetsBinding.instance.removeObserver(this);
1565+
super.dispose();
14781566
}
14791567

14801568
@override
14811569
Widget build(BuildContext context) {
1482-
MediaQueryData data = MediaQueryData.fromWindow(WidgetsBinding.instance.window);
1483-
if (!kReleaseMode) {
1484-
data = data.copyWith(platformBrightness: debugBrightnessOverride);
1570+
MediaQueryData effectiveData = _data!;
1571+
// If we get our platformBrightness from the PlatformDispatcher (i.e. we have no parentData) replace it
1572+
// with the debugBrightnessOverride in non-release mode.
1573+
if (!kReleaseMode && _parentData == null && effectiveData.platformBrightness != debugBrightnessOverride) {
1574+
effectiveData = effectiveData.copyWith(platformBrightness: debugBrightnessOverride);
14851575
}
14861576
return MediaQuery(
1487-
data: data,
1577+
data: effectiveData,
14881578
child: widget.child,
14891579
);
14901580
}
1491-
1492-
@override
1493-
void dispose() {
1494-
WidgetsBinding.instance.removeObserver(this);
1495-
super.dispose();
1496-
}
14971581
}

0 commit comments

Comments
 (0)