Unverified Commit a65ce5ba authored by chunhtai's avatar chunhtai Committed by GitHub

ignore sliver underflow if the last children is no longer at the prev… (#71864)

parent f3c10bd2
......@@ -1131,19 +1131,21 @@ class SliverMultiBoxAdaptorElement extends RenderObjectElement implements Render
void performRebuild() {
super.performRebuild();
_currentBeforeChild = null;
bool childrenUpdated = false;
assert(_currentlyUpdatingChildIndex == null);
try {
final SplayTreeMap<int, Element?> newChildren = SplayTreeMap<int, Element?>();
final Map<int, double> indexToLayoutOffset = HashMap<int, double>();
void processElement(int index) {
_currentlyUpdatingChildIndex = index;
if (_childElements[index] != null && _childElements[index] != newChildren[index]) {
// This index has an old child that isn't used anywhere and should be deactivated.
_childElements[index] = updateChild(_childElements[index], null, index);
childrenUpdated = true;
}
final Element? newChild = updateChild(newChildren[index], _build(index), index);
if (newChild != null) {
childrenUpdated = childrenUpdated || _childElements[index] != newChild;
_childElements[index] = newChild;
final SliverMultiBoxAdaptorParentData parentData = newChild.renderObject!.parentData! as SliverMultiBoxAdaptorParentData;
if (index == 0) {
......@@ -1154,6 +1156,7 @@ class SliverMultiBoxAdaptorElement extends RenderObjectElement implements Render
if (!parentData.keptAlive)
_currentBeforeChild = newChild.renderObject as RenderBox?;
} else {
childrenUpdated = true;
_childElements.remove(index);
}
}
......@@ -1185,7 +1188,16 @@ class SliverMultiBoxAdaptorElement extends RenderObjectElement implements Render
renderObject.debugChildIntegrityEnabled = false; // Moving children will temporary violate the integrity.
newChildren.keys.forEach(processElement);
if (_didUnderflow) {
// An element rebuild only updates existing children. The underflow check
// is here to make sure we look ahead one more child if we were at the end
// of the child list before the update. By doing so, we can update the max
// scroll offset during the layout phase. Otherwise, the layout phase may
// be skipped, and the scroll view may be stuck at the previous max
// scroll offset.
//
// This logic is not needed if any existing children has been updated,
// because we will not skip the layout phase if that happens.
if (!childrenUpdated && _didUnderflow) {
final int lastKey = _childElements.lastKey() ?? -1;
final int rightBoundary = lastKey + 1;
newChildren[rightBoundary] = _childElements[rightBoundary];
......
......@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';
......@@ -271,6 +272,71 @@ void main() {
expect(find.text('4'), findsOneWidget);
});
testWidgets('SliverFixedExtentList handles underflow when its children changes', (WidgetTester tester) async {
final List<String> items = <String>['1', '2', '3', '4', '5', '6'];
final List<String> initializedChild = <String>[];
List<Widget> children = <Widget>[];
for (final String item in items) {
children.add(
StateInitSpy(
item, () => initializedChild.add(item), key: ValueKey<String>(item),
)
);
}
final ScrollController controller = ScrollController(initialScrollOffset: 5400);
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: CustomScrollView(
controller: controller,
slivers: <Widget>[
SliverFixedExtentList(
itemExtent: 900,
delegate: SliverChildListDelegate(children),
),
],
),
),
);
await tester.pumpAndSettle();
expect(find.text('1'), findsNothing);
expect(find.text('2'), findsNothing);
expect(find.text('3'), findsNothing);
expect(find.text('4'), findsNothing);
expect(find.text('5'), findsNothing);
expect(find.text('6'), findsOneWidget);
expect(listEquals<String>(initializedChild, <String>['6']), isTrue);
// move to item 1 and swap the children at the same time
controller.jumpTo(0);
final Widget temp = children[5];
children[5] = children[0];
children[0] = temp;
children = List<Widget>.from(children);
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: CustomScrollView(
controller: controller,
slivers: <Widget>[
SliverFixedExtentList(
itemExtent: 900,
delegate: SliverChildListDelegate(children),
),
],
),
),
);
expect(find.text('1'), findsNothing);
expect(find.text('2'), findsNothing);
expect(find.text('3'), findsNothing);
expect(find.text('4'), findsNothing);
expect(find.text('5'), findsNothing);
expect(find.text('6'), findsOneWidget);
// None of the children should be built.
expect(listEquals<String>(initializedChild, <String>['6']), isTrue);
});
testWidgets(
'SliverGrid Correctly layout children after rearranging',
(WidgetTester tester) async {
......@@ -1021,6 +1087,29 @@ class TestSliverFixedExtentList extends StatelessWidget {
}
}
class StateInitSpy extends StatefulWidget {
const StateInitSpy(this.data, this.onStateInit, { Key? key }) : super(key: key);
final String data;
final VoidCallback onStateInit;
@override
StateInitSpyState createState() => StateInitSpyState();
}
class StateInitSpyState extends State<StateInitSpy> {
@override
void initState() {
super.initState();
widget.onStateInit();
}
@override
Widget build(BuildContext context) {
return Text(widget.data);
}
}
class KeepAlive extends StatefulWidget {
const KeepAlive(this.data, { Key? key }) : super(key: key);
......
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