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
@override
void performLayout() {
assert(childManager.debugAssertChildListLocked());
childManager.didStartLayout();
childManager.setDidUnderflow(false);
final double itemExtent = this.itemExtent;
......@@ -136,6 +136,7 @@ abstract class RenderSliverFixedExtentBoxAdaptor extends RenderSliverMultiBoxAda
if (!addInitialChild(index: firstIndex, layoutOffset: indexToLayoutOffset(itemExtent, firstIndex))) {
// There are no children.
geometry = SliverGeometry.zero;
childManager.didFinishLayout();
return;
}
}
......@@ -206,7 +207,7 @@ abstract class RenderSliverFixedExtentBoxAdaptor extends RenderSliverMultiBoxAda
|| constraints.scrollOffset > 0.0,
);
assert(childManager.debugAssertChildListLocked());
childManager.didFinishLayout();
}
}
......
......@@ -477,7 +477,7 @@ class RenderSliverGrid extends RenderSliverMultiBoxAdaptor {
@override
void performLayout() {
assert(childManager.debugAssertChildListLocked());
childManager.didStartLayout();
childManager.setDidUnderflow(false);
final double scrollOffset = constraints.scrollOffset;
......@@ -510,6 +510,7 @@ class RenderSliverGrid extends RenderSliverMultiBoxAdaptor {
layoutOffset: firstChildGridGeometry.scrollOffset)) {
// There are no children.
geometry = SliverGeometry.zero;
childManager.didFinishLayout();
return;
}
}
......@@ -587,6 +588,6 @@ class RenderSliverGrid extends RenderSliverMultiBoxAdaptor {
hasVisualOverflow: true,
);
assert(childManager.debugAssertChildListLocked());
childManager.didFinishLayout();
}
}
......@@ -43,7 +43,7 @@ class RenderSliverList extends RenderSliverMultiBoxAdaptor {
@override
void performLayout() {
assert(childManager.debugAssertChildListLocked());
childManager.didStartLayout();
childManager.setDidUnderflow(false);
final double scrollOffset = constraints.scrollOffset;
......@@ -77,6 +77,7 @@ class RenderSliverList extends RenderSliverMultiBoxAdaptor {
if (!addInitialChild()) {
// There are no children.
geometry = SliverGeometry.zero;
childManager.didFinishLayout();
return;
}
}
......@@ -241,6 +242,6 @@ class RenderSliverList extends RenderSliverMultiBoxAdaptor {
hasVisualOverflow: endScrollOffset > targetEndScrollOffset || constraints.scrollOffset > 0.0,
);
assert(childManager.debugAssertChildListLocked());
childManager.didFinishLayout();
}
}
......@@ -87,6 +87,13 @@ abstract class RenderSliverBoxChildManager {
/// affect the visible contents of the [RenderSliverMultiBoxAdaptor].
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
/// modifications to the [RenderSliverMultiBoxAdaptor]'s child list.
///
......
......@@ -31,6 +31,13 @@ abstract class SliverChildDelegate {
/// be too difficult to estimate the number of children.
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(
int firstIndex,
int lastIndex,
......@@ -38,6 +45,16 @@ abstract class SliverChildDelegate {
double trailingScrollOffset,
) => 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);
@override
......@@ -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;
@override
......
......@@ -5,6 +5,17 @@
import 'package:flutter_test/flutter_test.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() {
testWidgets('ListView default control', (WidgetTester tester) async {
await tester.pumpWidget(new Center(child: new ListView(itemExtent: 100.0)));
......@@ -163,4 +174,43 @@ void main() {
expect(find.text('0'), 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