Fixes RenderSliverFixedExtentBoxAdaptor correctly calculates leadingGarbage and trailingGarbage. (#36302)
This commit is contained in:
parent
598ecb324d
commit
4eb11c9f52
@ -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);
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user