diff --git a/packages/flutter/lib/src/rendering/object.dart b/packages/flutter/lib/src/rendering/object.dart index 868b05100a..9e560d4532 100644 --- a/packages/flutter/lib/src/rendering/object.dart +++ b/packages/flutter/lib/src/rendering/object.dart @@ -1088,6 +1088,21 @@ base class PipelineOwner with DiagnosticableTreeMixin { bool _shouldMergeDirtyNodes = false; List _nodesNeedingLayout = []; + /// The [RenderObject]s representing relayout boundaries which need to be laid out + /// in the next [flushLayout] pass. + /// + /// Relayout boundaries are added when they are marked for layout. + /// Subclasses of [PipelineOwner] may use them to invalidate caches or + /// otherwise make performance optimizations. Since nodes may be marked for + /// layout at any time, they are best checked during [flushLayout]. + /// + /// Relayout boundaries owned by child [PipelineOwner]s are not included here. + /// + /// Boundaries appear in an arbitrary order, and may appear multiple times. + @protected + @nonVirtual + Iterable get nodesNeedingLayout => _nodesNeedingLayout; + /// Whether this pipeline is currently in the layout phase. /// /// Specifically, whether [flushLayout] is currently running. @@ -1232,6 +1247,19 @@ base class PipelineOwner with DiagnosticableTreeMixin { List _nodesNeedingPaint = []; + /// The [RenderObject]s which need to be painted in the next [flushPaint] pass. + /// + /// [RenderObject]s marked with [RenderObject.isRepaintBoundary] are added + /// when they are marked needing paint. Subclasses of [PipelineOwner] may use them + /// to invalidate caches or otherwise make performance optimizations. + /// Since nodes may be marked for layout at any time, they are best checked during + /// [flushPaint]. + /// + /// Marked children of child [PipelineOwner]s are not included here. + @protected + @nonVirtual + Iterable get nodesNeedingPaint => _nodesNeedingPaint; + /// Whether this pipeline is currently in the paint phase. /// /// Specifically, whether [flushPaint] is currently running. diff --git a/packages/flutter/test/rendering/object_test.dart b/packages/flutter/test/rendering/object_test.dart index 5ac2fd7af8..05531f42ab 100644 --- a/packages/flutter/test/rendering/object_test.dart +++ b/packages/flutter/test/rendering/object_test.dart @@ -22,6 +22,37 @@ void main() { ); }); + test('nodesNeedingLayout updated with layout changes', () { + final _TestPipelineOwner owner = _TestPipelineOwner(); + final TestRenderObject renderObject = TestRenderObject()..isRepaintBoundary = true; + renderObject.attach(owner); + expect(owner.needLayout, isEmpty); + + renderObject.layout(const BoxConstraints.tightForFinite()); + renderObject.markNeedsLayout(); + expect(owner.needLayout, contains(renderObject)); + + owner.flushLayout(); + expect(owner.needLayout, isEmpty); + }); + + test('nodesNeedingPaint updated with paint changes', () { + final _TestPipelineOwner owner = _TestPipelineOwner(); + final TestRenderObject renderObject = TestRenderObject(allowPaintBounds: true) + ..isRepaintBoundary = true; + final OffsetLayer layer = OffsetLayer(); + layer.attach(owner); + renderObject.attach(owner); + expect(owner.needPaint, isEmpty); + + renderObject.markNeedsPaint(); + renderObject.scheduleInitialPaint(layer); + expect(owner.needPaint, contains(renderObject)); + + owner.flushPaint(); + expect(owner.needPaint, isEmpty); + }); + test('ensure frame is scheduled for markNeedsSemanticsUpdate', () { // Initialize all bindings because owner.flushSemantics() requires a window final TestRenderObject renderObject = TestRenderObject(); @@ -686,3 +717,10 @@ class TestThrowingRenderObject extends RenderObject { return Rect.zero; } } + +final class _TestPipelineOwner extends PipelineOwner { + // Make these protected fields visible for testing. + Iterable get needLayout => super.nodesNeedingLayout; + + Iterable get needPaint => super.nodesNeedingPaint; +}