Unverified Commit 4eb11c9f authored by chunhtai's avatar chunhtai Committed by GitHub

Fixes RenderSliverFixedExtentBoxAdaptor correctly calculates leadingGarbage...

Fixes RenderSliverFixedExtentBoxAdaptor correctly calculates leadingGarbage and trailingGarbage. (#36302)
parent 598ecb32
......@@ -142,6 +142,26 @@ abstract class RenderSliverFixedExtentBoxAdaptor extends RenderSliverMultiBoxAda
return childManager.childCount * itemExtent;
}
int _calculateLeadingGarbage(int firstIndex) {
RenderBox walker = firstChild;
int leadingGarbage = 0;
while(walker != null && indexOf(walker) < firstIndex){
leadingGarbage += 1;
walker = childAfter(walker);
}
return leadingGarbage;
}
int _calculateTrailingGarbage(int targetLastIndex) {
RenderBox walker = lastChild;
int trailingGarbage = 0;
while(walker != null && indexOf(walker) > targetLastIndex){
trailingGarbage += 1;
walker = childBefore(walker);
}
return trailingGarbage;
}
@override
void performLayout() {
childManager.didStartLayout();
......@@ -165,10 +185,8 @@ abstract class RenderSliverFixedExtentBoxAdaptor extends RenderSliverMultiBoxAda
getMaxChildIndexForScrollOffset(targetEndScrollOffset, itemExtent) : null;
if (firstChild != null) {
final int oldFirstIndex = indexOf(firstChild);
final int oldLastIndex = indexOf(lastChild);
final int leadingGarbage = (firstIndex - oldFirstIndex).clamp(0, childCount);
final int trailingGarbage = targetLastIndex == null ? 0 : (oldLastIndex - targetLastIndex).clamp(0, childCount);
final int leadingGarbage = _calculateLeadingGarbage(firstIndex);
final int trailingGarbage = _calculateTrailingGarbage(targetLastIndex);
collectGarbage(leadingGarbage, trailingGarbage);
} else {
collectGarbage(0, 0);
......
......@@ -25,6 +25,37 @@ Future<void> test(WidgetTester tester, double offset, { double anchor = 0.0 }) {
);
}
Future<void> testSliverFixedExtentList(WidgetTester tester, List<String> items) {
return tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: CustomScrollView(
slivers: <Widget>[
SliverFixedExtentList(
itemExtent: 900,
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
return Center(
key: ValueKey<String>(items[index]),
child: KeepAlive(
items[index],
)
);
},
childCount : items.length,
findChildIndexCallback: (Key key) {
final ValueKey<String> valueKey = key;
final String data = valueKey.value;
return items.indexOf(data);
}
),
),
],
),
),
);
}
void verify(WidgetTester tester, List<Offset> idealPositions, List<bool> idealVisibles) {
final List<Offset> actualPositions = tester.renderObjectList<RenderBox>(find.byType(SizedBox, skipOffstage: false)).map<Offset>(
(RenderBox target) => target.localToGlobal(const Offset(0.0, 0.0))
......@@ -196,6 +227,47 @@ void main() {
expect(find.text('BOTTOM'), findsOneWidget);
});
testWidgets('SliverFixedExtentList correctly clears garbage', (WidgetTester tester) async {
final List<String> items = <String>['1', '2', '3', '4', '5', '6'];
await testSliverFixedExtentList(tester, items);
// Keep alive widgets require 1 frame to notify their parents. Pumps in between
// drags to ensure widgets are kept alive.
await tester.drag(find.byType(CustomScrollView),const Offset(0.0, -1200.0));
await tester.pump();
await tester.drag(find.byType(CustomScrollView),const Offset(0.0, -1200.0));
await tester.pump();
await tester.drag(find.byType(CustomScrollView),const Offset(0.0, -800.0));
await tester.pump();
expect(find.text('1'), findsNothing);
expect(find.text('2'), findsNothing);
expect(find.text('3'), findsNothing);
expect(find.text('4'), findsOneWidget);
expect(find.text('5'), findsOneWidget);
// Indexes [0, 1, 2] are kept alive and [3, 4] are in viewport, thus the sliver
// will need to keep updating the elements at these indexes whenever a rebuild is
// triggered. The current child list in RenderSliverFixedExtentList is
// '4' -> '5' -> null.
//
// With the insertion below, all items will get shifted back 1 position. The sliver
// will have to update indexes [0, 1, 2, 3, 4, 5]. Since this is the first time
// item '0' gets initialized, mounting the element will cause it to attach to
// child list in RenderSliverFixedExtentList. This will create a gap.
// '0' -> '4' -> '5' -> null.
items.insert(0, '0');
await testSliverFixedExtentList(tester, items);
// Sliver should collect leading and trailing garbage correctly.
//
// The child list update should occur in following order.
// '0' -> '4' -> '5' -> null Started with Original list.
// '4' -> null Removed 1 leading garbage and 1 trailing garbage.
// '3' -> '4' -> null Prepended '3' because viewport is still at [3, 4].
expect(find.text('0'), findsNothing);
expect(find.text('1'), findsNothing);
expect(find.text('2'), findsNothing);
expect(find.text('3'), findsOneWidget);
expect(find.text('4'), findsOneWidget);
});
testWidgets('SliverGrid Correctly layout children after rearranging', (WidgetTester tester) async {
await tester.pumpWidget(const TestSliverGrid(
<Widget>[
......@@ -332,3 +404,23 @@ class TestSliverFixedExtentList extends StatelessWidget {
);
}
}
class KeepAlive extends StatefulWidget {
const KeepAlive(this.data);
final String data;
@override
KeepAliveState createState() => KeepAliveState();
}
class KeepAliveState extends State<KeepAlive> with AutomaticKeepAliveClientMixin {
@override
bool get wantKeepAlive => true;
@override
Widget build(BuildContext context) {
super.build(context);
return Text(widget.data);
}
}
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