Do not instantiate intermediate tabs during transition (#68124)
This commit is contained in:
parent
52c715fe43
commit
91478d96a8
@ -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 {
|
|||||||
/// 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
|
|||||||
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() {
|
|||||||
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 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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}'),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user