Commit 0e43e581 authored by Adam Barth's avatar Adam Barth Committed by GitHub

SliverChildDelegate should know which children are live (#9073)

This patch adds a notification to SliverChildDelegate that says which
children are alive after each layout. The delegate can use this
information to optimize it's underlying model of the children (e.g., by
discarding models for children that are far outside the live range).

Fixes #9045
parent abfee824
...@@ -103,7 +103,7 @@ abstract class RenderSliverFixedExtentBoxAdaptor extends RenderSliverMultiBoxAda ...@@ -103,7 +103,7 @@ abstract class RenderSliverFixedExtentBoxAdaptor extends RenderSliverMultiBoxAda
@override @override
void performLayout() { void performLayout() {
assert(childManager.debugAssertChildListLocked()); childManager.didStartLayout();
childManager.setDidUnderflow(false); childManager.setDidUnderflow(false);
final double itemExtent = this.itemExtent; final double itemExtent = this.itemExtent;
...@@ -136,6 +136,7 @@ abstract class RenderSliverFixedExtentBoxAdaptor extends RenderSliverMultiBoxAda ...@@ -136,6 +136,7 @@ abstract class RenderSliverFixedExtentBoxAdaptor extends RenderSliverMultiBoxAda
if (!addInitialChild(index: firstIndex, layoutOffset: indexToLayoutOffset(itemExtent, firstIndex))) { if (!addInitialChild(index: firstIndex, layoutOffset: indexToLayoutOffset(itemExtent, firstIndex))) {
// There are no children. // There are no children.
geometry = SliverGeometry.zero; geometry = SliverGeometry.zero;
childManager.didFinishLayout();
return; return;
} }
} }
...@@ -206,7 +207,7 @@ abstract class RenderSliverFixedExtentBoxAdaptor extends RenderSliverMultiBoxAda ...@@ -206,7 +207,7 @@ abstract class RenderSliverFixedExtentBoxAdaptor extends RenderSliverMultiBoxAda
|| constraints.scrollOffset > 0.0, || constraints.scrollOffset > 0.0,
); );
assert(childManager.debugAssertChildListLocked()); childManager.didFinishLayout();
} }
} }
......
...@@ -477,7 +477,7 @@ class RenderSliverGrid extends RenderSliverMultiBoxAdaptor { ...@@ -477,7 +477,7 @@ class RenderSliverGrid extends RenderSliverMultiBoxAdaptor {
@override @override
void performLayout() { void performLayout() {
assert(childManager.debugAssertChildListLocked()); childManager.didStartLayout();
childManager.setDidUnderflow(false); childManager.setDidUnderflow(false);
final double scrollOffset = constraints.scrollOffset; final double scrollOffset = constraints.scrollOffset;
...@@ -510,6 +510,7 @@ class RenderSliverGrid extends RenderSliverMultiBoxAdaptor { ...@@ -510,6 +510,7 @@ class RenderSliverGrid extends RenderSliverMultiBoxAdaptor {
layoutOffset: firstChildGridGeometry.scrollOffset)) { layoutOffset: firstChildGridGeometry.scrollOffset)) {
// There are no children. // There are no children.
geometry = SliverGeometry.zero; geometry = SliverGeometry.zero;
childManager.didFinishLayout();
return; return;
} }
} }
...@@ -587,6 +588,6 @@ class RenderSliverGrid extends RenderSliverMultiBoxAdaptor { ...@@ -587,6 +588,6 @@ class RenderSliverGrid extends RenderSliverMultiBoxAdaptor {
hasVisualOverflow: true, hasVisualOverflow: true,
); );
assert(childManager.debugAssertChildListLocked()); childManager.didFinishLayout();
} }
} }
...@@ -43,7 +43,7 @@ class RenderSliverList extends RenderSliverMultiBoxAdaptor { ...@@ -43,7 +43,7 @@ class RenderSliverList extends RenderSliverMultiBoxAdaptor {
@override @override
void performLayout() { void performLayout() {
assert(childManager.debugAssertChildListLocked()); childManager.didStartLayout();
childManager.setDidUnderflow(false); childManager.setDidUnderflow(false);
final double scrollOffset = constraints.scrollOffset; final double scrollOffset = constraints.scrollOffset;
...@@ -77,6 +77,7 @@ class RenderSliverList extends RenderSliverMultiBoxAdaptor { ...@@ -77,6 +77,7 @@ class RenderSliverList extends RenderSliverMultiBoxAdaptor {
if (!addInitialChild()) { if (!addInitialChild()) {
// There are no children. // There are no children.
geometry = SliverGeometry.zero; geometry = SliverGeometry.zero;
childManager.didFinishLayout();
return; return;
} }
} }
...@@ -241,6 +242,6 @@ class RenderSliverList extends RenderSliverMultiBoxAdaptor { ...@@ -241,6 +242,6 @@ class RenderSliverList extends RenderSliverMultiBoxAdaptor {
hasVisualOverflow: endScrollOffset > targetEndScrollOffset || constraints.scrollOffset > 0.0, hasVisualOverflow: endScrollOffset > targetEndScrollOffset || constraints.scrollOffset > 0.0,
); );
assert(childManager.debugAssertChildListLocked()); childManager.didFinishLayout();
} }
} }
...@@ -87,6 +87,13 @@ abstract class RenderSliverBoxChildManager { ...@@ -87,6 +87,13 @@ abstract class RenderSliverBoxChildManager {
/// affect the visible contents of the [RenderSliverMultiBoxAdaptor]. /// affect the visible contents of the [RenderSliverMultiBoxAdaptor].
void setDidUnderflow(bool value); void setDidUnderflow(bool value);
/// Called at the beginning of layout to indicate that layout is about to
/// occur.
void didStartLayout() { }
/// Called at the end of layout to indicate that layout is now complete.
void didFinishLayout() { }
/// In debug mode, asserts that this manager is not expecting any /// In debug mode, asserts that this manager is not expecting any
/// modifications to the [RenderSliverMultiBoxAdaptor]'s child list. /// modifications to the [RenderSliverMultiBoxAdaptor]'s child list.
/// ///
......
...@@ -31,6 +31,13 @@ abstract class SliverChildDelegate { ...@@ -31,6 +31,13 @@ abstract class SliverChildDelegate {
/// be too difficult to estimate the number of children. /// be too difficult to estimate the number of children.
int get estimatedChildCount => null; int get estimatedChildCount => null;
/// Returns an estimate of the max scroll extent for all the children.
///
/// Subclasses should override this function if they have additional
/// information about their max scroll extent.
///
/// The default implementation returns null, which causes the caller to
/// extrapolate the max scroll offset from the given parameters.
double estimateMaxScrollOffset( double estimateMaxScrollOffset(
int firstIndex, int firstIndex,
int lastIndex, int lastIndex,
...@@ -38,6 +45,16 @@ abstract class SliverChildDelegate { ...@@ -38,6 +45,16 @@ abstract class SliverChildDelegate {
double trailingScrollOffset, double trailingScrollOffset,
) => null; ) => null;
/// Called at the end of layout to indicate that layout is now complete.
///
/// The `firstIndex` argument is the index of the first child that was
/// included in the current layout. The `lastIndex` argument is the index of
/// the last child that was included in the current layout.
///
/// Useful for subclasses that which to track which children are included in
/// the underlying render tree.
void didFinishLayout(int firstIndex, int lastIndex) {}
bool shouldRebuild(covariant SliverChildDelegate oldDelegate); bool shouldRebuild(covariant SliverChildDelegate oldDelegate);
@override @override
...@@ -415,6 +432,19 @@ class SliverMultiBoxAdaptorElement extends RenderObjectElement implements Render ...@@ -415,6 +432,19 @@ class SliverMultiBoxAdaptorElement extends RenderObjectElement implements Render
); );
} }
@override
void didStartLayout() {
assert(debugAssertChildListLocked());
}
@override
void didFinishLayout() {
assert(debugAssertChildListLocked());
final int firstIndex = _childElements.firstKey() ?? 0;
final int lastIndex = _childElements.lastKey() ?? 0;
widget.delegate.didFinishLayout(firstIndex, lastIndex);
}
int _currentlyUpdatingChildIndex; int _currentlyUpdatingChildIndex;
@override @override
......
...@@ -5,6 +5,17 @@ ...@@ -5,6 +5,17 @@
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
class TestSliverChildListDelegate extends SliverChildListDelegate {
TestSliverChildListDelegate(List<Widget> children) : super(children);
final List<String> log = <String>[];
@override
void didFinishLayout(int firstIndex, int lastIndex) {
log.add('didFinishLayout firstIndex=$firstIndex lastIndex=$lastIndex');
}
}
void main() { void main() {
testWidgets('ListView default control', (WidgetTester tester) async { testWidgets('ListView default control', (WidgetTester tester) async {
await tester.pumpWidget(new Center(child: new ListView(itemExtent: 100.0))); await tester.pumpWidget(new Center(child: new ListView(itemExtent: 100.0)));
...@@ -163,4 +174,43 @@ void main() { ...@@ -163,4 +174,43 @@ void main() {
expect(find.text('0'), findsOneWidget); expect(find.text('0'), findsOneWidget);
expect(find.text('19'), findsOneWidget); expect(find.text('19'), findsOneWidget);
}); });
testWidgets('didFinishLayout has correct indicies', (WidgetTester tester) async {
final TestSliverChildListDelegate delegate = new TestSliverChildListDelegate(
new List<Widget>.generate(20, (int i) {
return new Container(
child: new Text('$i'),
);
})
);
await tester.pumpWidget(
new ListView.custom(
itemExtent: 110.0,
childrenDelegate: delegate,
),
);
expect(delegate.log, equals(<String>['didFinishLayout firstIndex=0 lastIndex=5']));
delegate.log.clear();
await tester.pumpWidget(
new ListView.custom(
itemExtent: 210.0,
childrenDelegate: delegate,
),
);
expect(delegate.log, equals(<String>['didFinishLayout firstIndex=0 lastIndex=2']));
delegate.log.clear();
await tester.drag(find.byType(ListView), const Offset(0.0, -600.0));
expect(delegate.log, isEmpty);
await tester.pump();
expect(delegate.log, equals(<String>['didFinishLayout firstIndex=2 lastIndex=5']));
delegate.log.clear();
});
} }
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