Unverified Commit 91478d96 authored by Michael Goderbauer's avatar Michael Goderbauer Committed by GitHub

Do not instantiate intermediate tabs during transition (#68124)

parent 52c715fe
...@@ -861,6 +861,9 @@ class SliverList extends SliverMultiBoxAdaptorWidget { ...@@ -861,6 +861,9 @@ class SliverList extends SliverMultiBoxAdaptorWidget {
required SliverChildDelegate delegate, required SliverChildDelegate delegate,
}) : super(key: key, delegate: delegate); }) : super(key: key, delegate: delegate);
@override
SliverMultiBoxAdaptorElement createElement() => SliverMultiBoxAdaptorElement(this, replaceMovedChildren: true);
@override @override
RenderSliverList createRenderObject(BuildContext context) { RenderSliverList createRenderObject(BuildContext context) {
final SliverMultiBoxAdaptorElement element = context as SliverMultiBoxAdaptorElement; final SliverMultiBoxAdaptorElement element = context as SliverMultiBoxAdaptorElement;
...@@ -1079,7 +1082,21 @@ class SliverGrid extends SliverMultiBoxAdaptorWidget { ...@@ -1079,7 +1082,21 @@ class SliverGrid extends SliverMultiBoxAdaptorWidget {
/// the children of subclasses of [RenderSliverMultiBoxAdaptor]. /// the children of subclasses of [RenderSliverMultiBoxAdaptor].
class SliverMultiBoxAdaptorElement extends RenderObjectElement implements RenderSliverBoxChildManager { class SliverMultiBoxAdaptorElement extends RenderObjectElement implements RenderSliverBoxChildManager {
/// Creates an element that lazily builds children for the given widget. /// Creates an element that lazily builds children for the given widget.
SliverMultiBoxAdaptorElement(SliverMultiBoxAdaptorWidget widget) : super(widget); ///
/// If `replaceMovedChildren` is set to true, a new child is proactively
/// inflate for the index that was previously occupied by a child that moved
/// to a new index. The layout offset of the moved child is copied over to the
/// new child. RenderObjects, that depend on the layout offset of existing
/// children during [RenderObject.performLayout] should set this to true
/// (example: [RenderSliverList]). For RenderObjects that figure out the
/// layout offset of their children without looking at the layout offset of
/// existing children this should be set to false (example:
/// [RenderSliverFixedExtentList]) to avoid inflating unnecessary children.
SliverMultiBoxAdaptorElement(SliverMultiBoxAdaptorWidget widget, {bool replaceMovedChildren = false})
: _replaceMovedChildren = replaceMovedChildren,
super(widget);
final bool _replaceMovedChildren;
@override @override
SliverMultiBoxAdaptorWidget get widget => super.widget as SliverMultiBoxAdaptorWidget; SliverMultiBoxAdaptorWidget get widget => super.widget as SliverMultiBoxAdaptorWidget;
...@@ -1146,8 +1163,10 @@ class SliverMultiBoxAdaptorElement extends RenderObjectElement implements Render ...@@ -1146,8 +1163,10 @@ class SliverMultiBoxAdaptorElement extends RenderObjectElement implements Render
childParentData.layoutOffset = null; childParentData.layoutOffset = null;
newChildren[newIndex] = _childElements[index]; newChildren[newIndex] = _childElements[index];
// We need to make sure the original index gets processed. if (_replaceMovedChildren) {
newChildren.putIfAbsent(index, () => null); // We need to make sure the original index gets processed.
newChildren.putIfAbsent(index, () => null);
}
// We do not want the remapped child to get deactivated during processElement. // We do not want the remapped child to get deactivated during processElement.
_childElements.remove(index); _childElements.remove(index);
} else { } else {
......
...@@ -2771,6 +2771,52 @@ void main() { ...@@ -2771,6 +2771,52 @@ void main() {
await tester.pump(); await tester.pump();
expect(tabController.animation!.value, pageController.page); expect(tabController.animation!.value, pageController.page);
}); });
testWidgets('Does not instantiate intermediate tabs during animation', (WidgetTester tester) async {
// Regression test for https://github.com/flutter/flutter/issues/14316.
final List<String> log = <String>[];
await tester.pumpWidget(MaterialApp(
home: DefaultTabController(
length: 5,
child: Scaffold(
appBar: AppBar(
bottom: const TabBar(
tabs: <Widget>[
Tab(text: 'car'),
Tab(text: 'transit'),
Tab(text: 'bike'),
Tab(text: 'boat'),
Tab(text: 'bus'),
],
),
title: const Text('Tabs Test'),
),
body: TabBarView(
children: <Widget>[
TabBody(index: 0, log: log),
TabBody(index: 1, log: log),
TabBody(index: 2, log: log),
TabBody(index: 3, log: log),
TabBody(index: 4, log: log),
],
),
),
),
));
expect(find.text('0'), findsOneWidget);
expect(find.text('3'), findsNothing);
expect(log, <String>['init: 0']);
await tester.tap(find.text('boat'));
await tester.pumpAndSettle();
expect(find.text('0'), findsNothing);
expect(find.text('3'), findsOneWidget);
// No other tab got instantiated during the animation.
expect(log, <String>['init: 0', 'init: 3', 'dispose: 0']);
});
} }
class KeepAliveInk extends StatefulWidget { class KeepAliveInk extends StatefulWidget {
...@@ -2826,3 +2872,41 @@ class TabBarDemo extends StatelessWidget { ...@@ -2826,3 +2872,41 @@ class TabBarDemo extends StatelessWidget {
} }
class MockScrollMetrics extends Fake implements ScrollMetrics {} class MockScrollMetrics extends Fake implements ScrollMetrics {}
class TabBody extends StatefulWidget {
const TabBody({ Key? key, required this.index, required this.log }) : super(key: key);
final int index;
final List<String> log;
@override
State<TabBody> createState() => TabBodyState();
}
class TabBodyState extends State<TabBody> {
@override
void initState() {
widget.log.add('init: ${widget.index}');
super.initState();
}
@override
void didUpdateWidget(TabBody oldWidget) {
super.didUpdateWidget(oldWidget);
// To keep the logging straight, widgets must not change their index.
assert(oldWidget.index == widget.index);
}
@override
void dispose() {
widget.log.add('dispose: ${widget.index}');
super.dispose();
}
@override
Widget build(BuildContext context) {
return Center(
child: Text('${widget.index}'),
);
}
}
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