diff --git a/packages/flutter/lib/src/material/tabs.dart b/packages/flutter/lib/src/material/tabs.dart index e20ab579d27a..7ed44d0e9d0c 100644 --- a/packages/flutter/lib/src/material/tabs.dart +++ b/packages/flutter/lib/src/material/tabs.dart @@ -1549,12 +1549,11 @@ class _TabBarState extends State { final double index = _controller!.index.toDouble(); final double value = _controller!.animation!.value; final double offset = switch (value - index) { - -1.0 || 1.0 => leadingPosition ?? middlePosition, - 0 => middlePosition, - < 0 when leadingPosition == null => middlePosition, - > 0 when trailingPosition == null => middlePosition, - < 0 => lerpDouble(middlePosition, leadingPosition, index - value)!, - _ => lerpDouble(middlePosition, trailingPosition, value - index)!, + -1.0 => leadingPosition ?? middlePosition, + 1.0 => trailingPosition ?? middlePosition, + 0 => middlePosition, + < 0 => leadingPosition == null ? middlePosition : lerpDouble(middlePosition, leadingPosition, index - value)!, + _ => trailingPosition == null ? middlePosition : lerpDouble(middlePosition, trailingPosition, value - index)!, }; _scrollController!.jumpTo(offset); diff --git a/packages/flutter/test/material/tabs_test.dart b/packages/flutter/test/material/tabs_test.dart index d0b66dce9016..a3f6284bf405 100644 --- a/packages/flutter/test/material/tabs_test.dart +++ b/packages/flutter/test/material/tabs_test.dart @@ -7158,4 +7158,96 @@ void main() { labelSize = tester.getSize(find.text('Tab 1')); expect(labelSize, equals(const Size(140.5, 40.0))); }, skip: isBrowser && !isSkiaWeb); // https://github.com/flutter/flutter/issues/87543 + + // This is a regression test for https://github.com/flutter/flutter/issues/150000. + testWidgets('Scrollable TabBar does not jitter in the middle position', (WidgetTester tester) async { + final List tabs = List.generate(20, (int index) => 'Tab $index'); + + await tester.pumpWidget(MaterialApp( + home: DefaultTabController( + length: tabs.length, + initialIndex: 10, + child: Scaffold( + appBar: AppBar( + bottom: TabBar( + isScrollable: true, + tabs: tabs.map((String tab) => Tab(text: tab)).toList(), + ), + ), + body: TabBarView( + children: [ + for (int i = 0; i < tabs.length; i++) + Center( + child: Text('Page $i'), + ), + ], + ), + ), + ), + )); + + final SingleChildScrollView scrollable = tester.widget(find.byType(SingleChildScrollView)); + expect(find.text('Page 10'), findsOneWidget); + expect(find.text('Page 11'), findsNothing); + expect(scrollable.controller!.position.pixels, closeTo(683.2, 0.1)); + + // Drag the TabBarView to the left. + await tester.drag(find.byType(TabBarView), const Offset(-800, 0)); + await tester.pump(); + await tester.pump(const Duration(milliseconds: 200)); + + expect(find.text('Page 10'), findsNothing); + expect(find.text('Page 11'), findsOneWidget); + expect(scrollable.controller!.position.pixels, closeTo(799.8, 0.1)); + }); + + // This is a regression test for https://github.com/flutter/flutter/issues/150000. + testWidgets('Scrollable TabBar does not jitter when the tab bar reaches the start', (WidgetTester tester) async { + final List tabs = List.generate(20, (int index) => 'Tab $index'); + + await tester.pumpWidget(MaterialApp( + home: DefaultTabController( + length: tabs.length, + initialIndex: 4, + child: Scaffold( + appBar: AppBar( + bottom: TabBar( + isScrollable: true, + tabs: tabs.map((String tab) => Tab(text: tab)).toList(), + ), + ), + body: TabBarView( + children: [ + for (int i = 0; i < tabs.length; i++) + Center( + child: Text('Page $i'), + ), + ], + ), + ), + ), + )); + + final SingleChildScrollView scrollable = tester.widget(find.byType(SingleChildScrollView)); + + expect(find.text('Page 4'), findsOneWidget); + expect(find.text('Page 3'), findsNothing); + expect(scrollable.controller!.position.pixels, closeTo(61.25, 0.1)); + + // Drag the TabBarView to the right. + final TestGesture gesture = await tester.startGesture(tester.getCenter(find.text('Page 4'))); + await gesture.moveBy(const Offset(600.0, 0.0)); + await gesture.up(); + await tester.pump(); + await tester.pump(const Duration(milliseconds: 500)); + + expect(find.text('Page 4'), findsOneWidget); + expect(find.text('Page 3'), findsOneWidget); + expect(scrollable.controller!.position.pixels, closeTo(0.2, 0.1)); + + await tester.pumpAndSettle(); + expect(find.text('Page 4'), findsNothing); + expect(find.text('Page 3'), findsOneWidget); + expect(scrollable.controller!.position.pixels, equals(0.0)); + }); }