Unverified Commit bc5ea0a0 authored by Michael Goderbauer's avatar Michael Goderbauer Committed by GitHub

Reland "Do not rebuild Routes when a new opaque Route is pushed on top" (#49376)

parent c341d4b7
...@@ -425,7 +425,8 @@ class RenderStack extends RenderBox ...@@ -425,7 +425,8 @@ class RenderStack extends RenderBox
} }
} }
double _getIntrinsicDimension(double mainChildSizeGetter(RenderBox child)) { /// Helper function for calculating the intrinsics metrics of a Stack.
static double getIntrinsicDimension(RenderBox firstChild, double mainChildSizeGetter(RenderBox child)) {
double extent = 0.0; double extent = 0.0;
RenderBox child = firstChild; RenderBox child = firstChild;
while (child != null) { while (child != null) {
...@@ -440,22 +441,22 @@ class RenderStack extends RenderBox ...@@ -440,22 +441,22 @@ class RenderStack extends RenderBox
@override @override
double computeMinIntrinsicWidth(double height) { double computeMinIntrinsicWidth(double height) {
return _getIntrinsicDimension((RenderBox child) => child.getMinIntrinsicWidth(height)); return getIntrinsicDimension(firstChild, (RenderBox child) => child.getMinIntrinsicWidth(height));
} }
@override @override
double computeMaxIntrinsicWidth(double height) { double computeMaxIntrinsicWidth(double height) {
return _getIntrinsicDimension((RenderBox child) => child.getMaxIntrinsicWidth(height)); return getIntrinsicDimension(firstChild, (RenderBox child) => child.getMaxIntrinsicWidth(height));
} }
@override @override
double computeMinIntrinsicHeight(double width) { double computeMinIntrinsicHeight(double width) {
return _getIntrinsicDimension((RenderBox child) => child.getMinIntrinsicHeight(width)); return getIntrinsicDimension(firstChild, (RenderBox child) => child.getMinIntrinsicHeight(width));
} }
@override @override
double computeMaxIntrinsicHeight(double width) { double computeMaxIntrinsicHeight(double width) {
return _getIntrinsicDimension((RenderBox child) => child.getMaxIntrinsicHeight(width)); return getIntrinsicDimension(firstChild, (RenderBox child) => child.getMaxIntrinsicHeight(width));
} }
@override @override
...@@ -463,6 +464,57 @@ class RenderStack extends RenderBox ...@@ -463,6 +464,57 @@ class RenderStack extends RenderBox
return defaultComputeDistanceToHighestActualBaseline(baseline); return defaultComputeDistanceToHighestActualBaseline(baseline);
} }
/// Lays out the positioned `child` according to `alignment` within a Stack of `size`.
///
/// Returns true when the child has visual overflow.
static bool layoutPositionedChild(RenderBox child, StackParentData childParentData, Size size, Alignment alignment) {
assert(childParentData.isPositioned);
assert(child.parentData == childParentData);
bool hasVisualOverflow = false;
BoxConstraints childConstraints = const BoxConstraints();
if (childParentData.left != null && childParentData.right != null)
childConstraints = childConstraints.tighten(width: size.width - childParentData.right - childParentData.left);
else if (childParentData.width != null)
childConstraints = childConstraints.tighten(width: childParentData.width);
if (childParentData.top != null && childParentData.bottom != null)
childConstraints = childConstraints.tighten(height: size.height - childParentData.bottom - childParentData.top);
else if (childParentData.height != null)
childConstraints = childConstraints.tighten(height: childParentData.height);
child.layout(childConstraints, parentUsesSize: true);
double x;
if (childParentData.left != null) {
x = childParentData.left;
} else if (childParentData.right != null) {
x = size.width - childParentData.right - child.size.width;
} else {
x = alignment.alongOffset(size - child.size as Offset).dx;
}
if (x < 0.0 || x + child.size.width > size.width)
hasVisualOverflow = true;
double y;
if (childParentData.top != null) {
y = childParentData.top;
} else if (childParentData.bottom != null) {
y = size.height - childParentData.bottom - child.size.height;
} else {
y = alignment.alongOffset(size - child.size as Offset).dy;
}
if (y < 0.0 || y + child.size.height > size.height)
hasVisualOverflow = true;
childParentData.offset = Offset(x, y);
return hasVisualOverflow;
}
@override @override
void performLayout() { void performLayout() {
_resolve(); _resolve();
...@@ -527,45 +579,7 @@ class RenderStack extends RenderBox ...@@ -527,45 +579,7 @@ class RenderStack extends RenderBox
if (!childParentData.isPositioned) { if (!childParentData.isPositioned) {
childParentData.offset = _resolvedAlignment.alongOffset(size - child.size as Offset); childParentData.offset = _resolvedAlignment.alongOffset(size - child.size as Offset);
} else { } else {
BoxConstraints childConstraints = const BoxConstraints(); _hasVisualOverflow = layoutPositionedChild(child, childParentData, size, _resolvedAlignment) || _hasVisualOverflow;
if (childParentData.left != null && childParentData.right != null)
childConstraints = childConstraints.tighten(width: size.width - childParentData.right - childParentData.left);
else if (childParentData.width != null)
childConstraints = childConstraints.tighten(width: childParentData.width);
if (childParentData.top != null && childParentData.bottom != null)
childConstraints = childConstraints.tighten(height: size.height - childParentData.bottom - childParentData.top);
else if (childParentData.height != null)
childConstraints = childConstraints.tighten(height: childParentData.height);
child.layout(childConstraints, parentUsesSize: true);
double x;
if (childParentData.left != null) {
x = childParentData.left;
} else if (childParentData.right != null) {
x = size.width - childParentData.right - child.size.width;
} else {
x = _resolvedAlignment.alongOffset(size - child.size as Offset).dx;
}
if (x < 0.0 || x + child.size.width > size.width)
_hasVisualOverflow = true;
double y;
if (childParentData.top != null) {
y = childParentData.top;
} else if (childParentData.bottom != null) {
y = size.height - childParentData.bottom - child.size.height;
} else {
y = _resolvedAlignment.alongOffset(size - child.size as Offset).dy;
}
if (y < 0.0 || y + child.size.height > size.height)
_hasVisualOverflow = true;
childParentData.offset = Offset(x, y);
} }
assert(child.parentData == childParentData); assert(child.parentData == childParentData);
......
...@@ -72,12 +72,9 @@ CupertinoPageScaffold scaffoldForNavBar(Widget navBar) { ...@@ -72,12 +72,9 @@ CupertinoPageScaffold scaffoldForNavBar(Widget navBar) {
} }
Finder flying(WidgetTester tester, Finder finder) { Finder flying(WidgetTester tester, Finder finder) {
final RenderObjectWithChildMixin<RenderStack> theater = final ContainerRenderObjectMixin<RenderBox, StackParentData> theater = tester.renderObject(find.byType(Overlay));
tester.renderObject(find.byType(Overlay));
final RenderStack theaterStack = theater.child;
final Finder lastOverlayFinder = find.byElementPredicate((Element element) { final Finder lastOverlayFinder = find.byElementPredicate((Element element) {
return element is RenderObjectElement && return element is RenderObjectElement && element.renderObject == theater.lastChild;
element.renderObject == theaterStack.lastChild;
}); });
assert( assert(
......
...@@ -132,8 +132,8 @@ void main() { ...@@ -132,8 +132,8 @@ void main() {
' Offstage\n' ' Offstage\n'
' _ModalScopeStatus\n' ' _ModalScopeStatus\n'
' _ModalScope<dynamic>-[LabeledGlobalKey<_ModalScopeState<dynamic>>#969b7]\n' ' _ModalScope<dynamic>-[LabeledGlobalKey<_ModalScopeState<dynamic>>#969b7]\n'
' _OverlayEntry-[LabeledGlobalKey<_OverlayEntryState>#7a3ae]\n' ' TickerMode\n'
' Stack\n' ' _OverlayEntryWidget-[LabeledGlobalKey<_OverlayEntryWidgetState>#545d0]\n'
' _Theatre\n' ' _Theatre\n'
' Overlay-[LabeledGlobalKey<OverlayState>#31a52]\n' ' Overlay-[LabeledGlobalKey<OverlayState>#31a52]\n'
' _FocusMarker\n' ' _FocusMarker\n'
......
...@@ -524,23 +524,27 @@ void main() { ...@@ -524,23 +524,27 @@ void main() {
expect(errorDetails, isNotNull); expect(errorDetails, isNotNull);
expect(errorDetails.stack, isNotNull); expect(errorDetails.stack, isNotNull);
// Check the ErrorDetails without the stack trace // Check the ErrorDetails without the stack trace
final List<String> lines = errorDetails.toString().split('\n'); final String fullErrorMessage = errorDetails.toString();
final List<String> lines = fullErrorMessage.split('\n');
// The lines in the middle of the error message contain the stack trace // The lines in the middle of the error message contain the stack trace
// which will change depending on where the test is run. // which will change depending on where the test is run.
expect(lines.length, greaterThan(7)); final String errorMessage = lines.takeWhile(
expect( (String line) => line != '',
lines.take(8).join('\n'), ).join('\n');
equalsIgnoringHashCodes( expect(errorMessage.length, lessThan(fullErrorMessage.length));
expect(errorMessage, startsWith(
'══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞════════════════════════\n' '══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞════════════════════════\n'
'The following assertion was thrown building Stepper(dirty,\n' 'The following assertion was thrown building Stepper('
'dependencies: [_LocalizationsScope-[GlobalKey#00000]], state:\n' ));
'_StepperState#00000):\n' // The description string of the stepper looks slightly different depending
// on the platform and is omitted here.
expect(errorMessage, endsWith(
'):\n'
'Steppers must not be nested.\n' 'Steppers must not be nested.\n'
'The material specification advises that one should avoid\n' 'The material specification advises that one should avoid\n'
'embedding steppers within steppers.\n' 'embedding steppers within steppers.\n'
'https://material.io/archive/guidelines/components/steppers.html#steppers-usage' 'https://material.io/archive/guidelines/components/steppers.html#steppers-usage'
), ));
);
}); });
///https://github.com/flutter/flutter/issues/16920 ///https://github.com/flutter/flutter/issues/16920
......
...@@ -1465,6 +1465,33 @@ void main() { ...@@ -1465,6 +1465,33 @@ void main() {
expect(find.byKey(const ValueKey<String>('/A/B')), findsNothing); // popped expect(find.byKey(const ValueKey<String>('/A/B')), findsNothing); // popped
expect(find.byKey(const ValueKey<String>('/C')), findsOneWidget); expect(find.byKey(const ValueKey<String>('/C')), findsOneWidget);
}); });
testWidgets('Pushing opaque Route does not rebuild routes below', (WidgetTester tester) async {
// Regression test for https://github.com/flutter/flutter/issues/45797.
final GlobalKey<NavigatorState> navigator = GlobalKey<NavigatorState>();
final Key bottomRoute = UniqueKey();
final Key topRoute = UniqueKey();
await tester.pumpWidget(
MaterialApp(
navigatorKey: navigator,
routes: <String, WidgetBuilder>{
'/' : (BuildContext context) => StatefulTestWidget(key: bottomRoute),
'/a': (BuildContext context) => StatefulTestWidget(key: topRoute),
},
),
);
expect(tester.state<StatefulTestState>(find.byKey(bottomRoute)).rebuildCount, 1);
navigator.currentState.pushNamed('/a');
await tester.pumpAndSettle();
// Bottom route is offstage and did not rebuild.
expect(find.byKey(bottomRoute), findsNothing);
expect(tester.state<StatefulTestState>(find.byKey(bottomRoute, skipOffstage: false)).rebuildCount, 1);
expect(tester.state<StatefulTestState>(find.byKey(topRoute)).rebuildCount, 1);
});
} }
class NoAnimationPageRoute extends PageRouteBuilder<void> { class NoAnimationPageRoute extends PageRouteBuilder<void> {
...@@ -1478,3 +1505,20 @@ class NoAnimationPageRoute extends PageRouteBuilder<void> { ...@@ -1478,3 +1505,20 @@ class NoAnimationPageRoute extends PageRouteBuilder<void> {
return super.createAnimationController()..value = 1.0; return super.createAnimationController()..value = 1.0;
} }
} }
class StatefulTestWidget extends StatefulWidget {
const StatefulTestWidget({Key key}) : super(key: key);
@override
State<StatefulTestWidget> createState() => StatefulTestState();
}
class StatefulTestState extends State<StatefulTestWidget> {
int rebuildCount = 0;
@override
Widget build(BuildContext context) {
rebuildCount += 1;
return Container();
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment