From 82afe3ea4336c8b280181b8eb265a35d035c9948 Mon Sep 17 00:00:00 2001 From: xubaolin Date: Fri, 6 May 2022 09:39:10 +0800 Subject: [PATCH] Clear the cached data of `RenderBox` if its parent re-layout (#101493) --- packages/flutter/lib/src/rendering/box.dart | 27 ++++++-- .../rendering/cached_intrinsics_test.dart | 66 +++++++++++++++++++ 2 files changed, 87 insertions(+), 6 deletions(-) diff --git a/packages/flutter/lib/src/rendering/box.dart b/packages/flutter/lib/src/rendering/box.dart index bef7ca101df8..b72381a7171f 100644 --- a/packages/flutter/lib/src/rendering/box.dart +++ b/packages/flutter/lib/src/rendering/box.dart @@ -2348,8 +2348,7 @@ abstract class RenderBox extends RenderObject { }()); } - @override - void markNeedsLayout() { + bool _clearCachedData() { if ((_cachedBaselines != null && _cachedBaselines!.isNotEmpty) || (_cachedIntrinsicDimensions != null && _cachedIntrinsicDimensions!.isNotEmpty) || (_cachedDryLayoutSizes != null && _cachedDryLayoutSizes!.isNotEmpty)) { @@ -2361,14 +2360,30 @@ abstract class RenderBox extends RenderObject { _cachedBaselines?.clear(); _cachedIntrinsicDimensions?.clear(); _cachedDryLayoutSizes?.clear(); - if (parent is RenderObject) { - markParentNeedsLayout(); - return; - } + return true; + } + return false; + } + + @override + void markNeedsLayout() { + if (_clearCachedData() && parent is RenderObject) { + markParentNeedsLayout(); + return; } super.markNeedsLayout(); } + @override + void layout(Constraints constraints, {bool parentUsesSize = false}) { + if (hasSize && constraints != this.constraints && + _cachedBaselines != null && _cachedBaselines!.isNotEmpty) { + // The cached baselines data may need update if the constraints change. + _cachedBaselines?.clear(); + } + super.layout(constraints, parentUsesSize: parentUsesSize); + } + /// {@macro flutter.rendering.RenderObject.performResize} /// /// By default this method sets [size] to the result of [computeDryLayout] diff --git a/packages/flutter/test/rendering/cached_intrinsics_test.dart b/packages/flutter/test/rendering/cached_intrinsics_test.dart index 29456942161f..756a4967d54d 100644 --- a/packages/flutter/test/rendering/cached_intrinsics_test.dart +++ b/packages/flutter/test/rendering/cached_intrinsics_test.dart @@ -5,7 +5,11 @@ import 'package:flutter/rendering.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'rendering_tester.dart'; + class RenderTestBox extends RenderBox { + late Size boxSize; + int calls = 0; double value = 0.0; double next() { value += 1.0; @@ -19,9 +23,23 @@ class RenderTestBox extends RenderBox { double computeMinIntrinsicHeight(double width) => next(); @override double computeMaxIntrinsicHeight(double width) => next(); + + @override + void performLayout() { + size = constraints.biggest; + boxSize = size; + } + + @override + double? computeDistanceToActualBaseline(TextBaseline baseline) { + calls += 1; + return boxSize.height / 2.0; + } } void main() { + TestRenderingFlutterBinding.ensureInitialized(); + test('Intrinsics cache', () { final RenderBox test = RenderTestBox(); @@ -68,4 +86,52 @@ void main() { expect(test.getMinIntrinsicWidth(0.0), equals(1.0)); }); + + // Regression test for https://github.com/flutter/flutter/issues/101179 + test('Cached baselines should be cleared if its parent re-layout', () { + double viewHeight = 200.0; + final RenderTestBox test = RenderTestBox(); + final RenderBox baseline = RenderBaseline( + baseline: 0.0, + baselineType: TextBaseline.alphabetic, + child: test, + ); + final RenderConstrainedBox root = RenderConstrainedBox( + additionalConstraints: BoxConstraints.tightFor(width: 200.0, height: viewHeight), + child: baseline, + ); + + layout(RenderPositionedBox( + child: root, + )); + + BoxParentData? parentData = test.parentData as BoxParentData?; + expect(parentData!.offset.dy, -(viewHeight / 2.0)); + expect(test.calls, 1); + + // Trigger the root render re-layout. + viewHeight = 300.0; + root.additionalConstraints = BoxConstraints.tightFor(width: 200.0, height: viewHeight); + pumpFrame(); + + parentData = test.parentData as BoxParentData?; + expect(parentData!.offset.dy, -(viewHeight / 2.0)); + expect(test.calls, 2); // The layout constraints change will clear the cached data. + + final RenderObject parent = test.parent! as RenderObject; + expect(parent.debugNeedsLayout, false); + + // Do not forget notify parent dirty after the cached data be cleared by `layout()` + test.markNeedsLayout(); + expect(parent.debugNeedsLayout, true); + + pumpFrame(); + expect(parent.debugNeedsLayout, false); + expect(test.calls, 3); // Self dirty will clear the cached data. + + parent.markNeedsLayout(); + pumpFrame(); + + expect(test.calls, 3); // Use the cached data if the layout constraints do not change. + }); }