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,
|
||||
}) : super(key: key, delegate: delegate);
|
||||
|
||||
@override
|
||||
SliverMultiBoxAdaptorElement createElement() => SliverMultiBoxAdaptorElement(this, replaceMovedChildren: true);
|
||||
|
||||
@override
|
||||
RenderSliverList createRenderObject(BuildContext context) {
|
||||
final SliverMultiBoxAdaptorElement element = context as SliverMultiBoxAdaptorElement;
|
||||
@ -1079,7 +1082,21 @@ class SliverGrid extends SliverMultiBoxAdaptorWidget {
|
||||
/// the children of subclasses of [RenderSliverMultiBoxAdaptor].
|
||||
class SliverMultiBoxAdaptorElement extends RenderObjectElement implements RenderSliverBoxChildManager {
|
||||
/// 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
|
||||
SliverMultiBoxAdaptorWidget get widget => super.widget as SliverMultiBoxAdaptorWidget;
|
||||
@ -1146,8 +1163,10 @@ class SliverMultiBoxAdaptorElement extends RenderObjectElement implements Render
|
||||
childParentData.layoutOffset = null;
|
||||
|
||||
newChildren[newIndex] = _childElements[index];
|
||||
// We need to make sure the original index gets processed.
|
||||
newChildren.putIfAbsent(index, () => null);
|
||||
if (_replaceMovedChildren) {
|
||||
// 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.
|
||||
_childElements.remove(index);
|
||||
} else {
|
||||
|
@ -2771,6 +2771,52 @@ void main() {
|
||||
await tester.pump();
|
||||
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 {
|
||||
@ -2826,3 +2872,41 @@ class TabBarDemo extends StatelessWidget {
|
||||
}
|
||||
|
||||
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