Unverified Commit ebe72d3f authored by LongCatIsLooong's avatar LongCatIsLooong Committed by GitHub

Call `markNeedsPaint` when adding overlayChild to `Overlay` (#135941)

Fixes https://github.com/flutter/flutter/issues/134656

`_skipMarkNeesLayout` was meant to only skip `markNeedsLayout` calls. Re-painting is still needed when a child gets added/removed from the `Overlay`.
parent 68d47e55
......@@ -412,10 +412,8 @@ class MouseTracker extends ChangeNotifier {
// hit-test order.
final PointerExitEvent baseExitEvent = PointerExitEvent.fromMouseEvent(latestEvent);
lastAnnotations.forEach((MouseTrackerAnnotation annotation, Matrix4 transform) {
if (!nextAnnotations.containsKey(annotation)) {
if (annotation.validForMouseTracker && annotation.onExit != null) {
annotation.onExit!(baseExitEvent.transformed(lastAnnotations[annotation]));
}
if (annotation.validForMouseTracker && !nextAnnotations.containsKey(annotation)) {
annotation.onExit?.call(baseExitEvent.transformed(lastAnnotations[annotation]));
}
});
......@@ -426,8 +424,8 @@ class MouseTracker extends ChangeNotifier {
).toList();
final PointerEnterEvent baseEnterEvent = PointerEnterEvent.fromMouseEvent(latestEvent);
for (final MouseTrackerAnnotation annotation in enteringAnnotations.reversed) {
if (annotation.validForMouseTracker && annotation.onEnter != null) {
annotation.onEnter!(baseEnterEvent.transformed(nextAnnotations[annotation]));
if (annotation.validForMouseTracker) {
annotation.onEnter?.call(baseEnterEvent.transformed(nextAnnotations[annotation]));
}
}
}
......
......@@ -1032,6 +1032,10 @@ class _RenderTheater extends RenderBox with ContainerRenderObjectMixin<RenderBox
assert(!_skipMarkNeedsLayout);
_skipMarkNeedsLayout = true;
adoptChild(child);
// The Overlay still needs repainting when a deferred child is added. Usually
// `markNeedsLayout` implies `markNeedsPaint`, but here `markNeedsLayout` is
// skipped when the `_skipMarkNeedsLayout` flag is set.
markNeedsPaint();
_skipMarkNeedsLayout = false;
// After adding `child` to the render tree, we want to make sure it will be
......@@ -1045,6 +1049,9 @@ class _RenderTheater extends RenderBox with ContainerRenderObjectMixin<RenderBox
assert(!_skipMarkNeedsLayout);
_skipMarkNeedsLayout = true;
dropChild(child);
// The Overlay still needs repainting when a deferred child is dropped. See
// the comment in `_addDeferredChild`.
markNeedsPaint();
_skipMarkNeedsLayout = false;
}
......
......@@ -777,6 +777,65 @@ void main() {
verifyTreeIsClean();
});
group('Adding/removing overlay child causes repaint', () {
// Regression test for https://github.com/flutter/flutter/issues/134656.
const Key childKey = Key('child');
final OverlayEntry overlayEntry = OverlayEntry(
builder: (BuildContext context) {
return RepaintBoundary(
child: OverlayPortal(
controller: controller1,
overlayChildBuilder: (BuildContext context) => const SizedBox(),
child: const SizedBox(key: childKey),
),
);
},
);
final Widget widget = Directionality(
key: GlobalKey(debugLabel: 'key'),
textDirection: TextDirection.ltr,
child: Overlay(initialEntries: <OverlayEntry>[overlayEntry]),
);
tearDown(overlayEntry.remove);
tearDownAll(overlayEntry.dispose);
testWidgetsWithLeakTracking('Adding child', (WidgetTester tester) async {
controller1.hide();
await tester.pumpWidget(widget);
final RenderBox renderTheater = tester.renderObject<RenderBox>(find.byType(Overlay));
final RenderBox renderChild = tester.renderObject<RenderBox>(find.byKey(childKey));
assert(!renderTheater.debugNeedsPaint);
assert(!renderChild.debugNeedsPaint);
controller1.show();
await tester.pump(null, EnginePhase.layout);
expect(renderTheater.debugNeedsPaint, isTrue);
expect(renderChild.debugNeedsPaint, isFalse);
// Discard the dirty render tree.
await tester.pumpWidget(const SizedBox());
});
testWidgetsWithLeakTracking('Removing child', (WidgetTester tester) async {
controller1.show();
await tester.pumpWidget(widget);
final RenderBox renderTheater = tester.renderObject<RenderBox>(find.byType(Overlay));
final RenderBox renderChild = tester.renderObject<RenderBox>(find.byKey(childKey));
assert(!renderTheater.debugNeedsPaint);
assert(!renderChild.debugNeedsPaint);
controller1.hide();
await tester.pump(null, EnginePhase.layout);
expect(renderTheater.debugNeedsPaint, isTrue);
expect(renderChild.debugNeedsPaint, isFalse);
// Discard the dirty render tree.
await tester.pumpWidget(const SizedBox());
});
});
testWidgetsWithLeakTracking('Adding/Removing OverlayPortal in LayoutBuilder during layout', (WidgetTester tester) async {
final GlobalKey widgetKey = GlobalKey(debugLabel: 'widget');
final GlobalKey overlayKey = GlobalKey(debugLabel: 'overlay');
......
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