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;
|
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
|
@override
|
||||||
void performLayout() {
|
void performLayout() {
|
||||||
childManager.didStartLayout();
|
childManager.didStartLayout();
|
||||||
@ -165,10 +185,8 @@ abstract class RenderSliverFixedExtentBoxAdaptor extends RenderSliverMultiBoxAda
|
|||||||
getMaxChildIndexForScrollOffset(targetEndScrollOffset, itemExtent) : null;
|
getMaxChildIndexForScrollOffset(targetEndScrollOffset, itemExtent) : null;
|
||||||
|
|
||||||
if (firstChild != null) {
|
if (firstChild != null) {
|
||||||
final int oldFirstIndex = indexOf(firstChild);
|
final int leadingGarbage = _calculateLeadingGarbage(firstIndex);
|
||||||
final int oldLastIndex = indexOf(lastChild);
|
final int trailingGarbage = _calculateTrailingGarbage(targetLastIndex);
|
||||||
final int leadingGarbage = (firstIndex - oldFirstIndex).clamp(0, childCount);
|
|
||||||
final int trailingGarbage = targetLastIndex == null ? 0 : (oldLastIndex - targetLastIndex).clamp(0, childCount);
|
|
||||||
collectGarbage(leadingGarbage, trailingGarbage);
|
collectGarbage(leadingGarbage, trailingGarbage);
|
||||||
} else {
|
} else {
|
||||||
collectGarbage(0, 0);
|
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) {
|
void verify(WidgetTester tester, List<Offset> idealPositions, List<bool> idealVisibles) {
|
||||||
final List<Offset> actualPositions = tester.renderObjectList<RenderBox>(find.byType(SizedBox, skipOffstage: false)).map<Offset>(
|
final List<Offset> actualPositions = tester.renderObjectList<RenderBox>(find.byType(SizedBox, skipOffstage: false)).map<Offset>(
|
||||||
(RenderBox target) => target.localToGlobal(const Offset(0.0, 0.0))
|
(RenderBox target) => target.localToGlobal(const Offset(0.0, 0.0))
|
||||||
@ -196,6 +227,47 @@ void main() {
|
|||||||
expect(find.text('BOTTOM'), findsOneWidget);
|
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 {
|
testWidgets('SliverGrid Correctly layout children after rearranging', (WidgetTester tester) async {
|
||||||
await tester.pumpWidget(const TestSliverGrid(
|
await tester.pumpWidget(const TestSliverGrid(
|
||||||
<Widget>[
|
<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