Skip to content

Commit a9f554a

Browse files
authored
Fix scrollable TabBar jittering (#150041)
fixes [TabBar with isScrollable set to true is broken](flutter/flutter#150000) This regressed due to a tiny mistake in flutter/flutter#146293 ### Code sample <details> <summary>expand to view the code sample</summary> ```dart import 'dart:ui'; import 'package:flutter/material.dart'; /// Flutter code sample for [TabBar]. void main() => runApp(const TabBarApp()); class TabBarApp extends StatelessWidget { const TabBarApp({super.key}); @OverRide Widget build(BuildContext context) { return const MaterialApp( home: TabBarExample(), ); } } class TabBarExample extends StatelessWidget { const TabBarExample({super.key}); @OverRide Widget build(BuildContext context) { final List<Tab> tabs = List<Tab>.generate(20, (int index) => Tab(text: 'Tab $index')); return ScrollConfiguration( behavior: ScrollConfiguration.of(context) .copyWith(dragDevices: <PointerDeviceKind>{ PointerDeviceKind.touch, PointerDeviceKind.mouse, }), child: DefaultTabController( length: tabs.length, child: Scaffold( appBar: AppBar( title: const Text('TabBar Sample'), bottom: TabBar( isScrollable: true, tabs: tabs, tabAlignment: TabAlignment.start, ), ), body: TabBarView( children: <Widget>[ for (int i = 0; i < tabs.length; i++) Center( child: Text('Page $i'), ), ], ), ), ), ); } } ``` </details> ### Before https://github.com/flutter/flutter/assets/48603081/b7aa98a2-a6a5-431e-8327-859a11efa129 ### After https://github.com/flutter/flutter/assets/48603081/0435719f-03d4-4d76-8b5a-532894fcf4a3
1 parent 6399107 commit a9f554a

File tree

2 files changed

+97
-6
lines changed

2 files changed

+97
-6
lines changed

packages/flutter/lib/src/material/tabs.dart

+5-6
Original file line numberDiff line numberDiff line change
@@ -1549,12 +1549,11 @@ class _TabBarState extends State<TabBar> {
15491549
final double index = _controller!.index.toDouble();
15501550
final double value = _controller!.animation!.value;
15511551
final double offset = switch (value - index) {
1552-
-1.0 || 1.0 => leadingPosition ?? middlePosition,
1553-
0 => middlePosition,
1554-
< 0 when leadingPosition == null => middlePosition,
1555-
> 0 when trailingPosition == null => middlePosition,
1556-
< 0 => lerpDouble(middlePosition, leadingPosition, index - value)!,
1557-
_ => lerpDouble(middlePosition, trailingPosition, value - index)!,
1552+
-1.0 => leadingPosition ?? middlePosition,
1553+
1.0 => trailingPosition ?? middlePosition,
1554+
0 => middlePosition,
1555+
< 0 => leadingPosition == null ? middlePosition : lerpDouble(middlePosition, leadingPosition, index - value)!,
1556+
_ => trailingPosition == null ? middlePosition : lerpDouble(middlePosition, trailingPosition, value - index)!,
15581557
};
15591558

15601559
_scrollController!.jumpTo(offset);

packages/flutter/test/material/tabs_test.dart

+92
Original file line numberDiff line numberDiff line change
@@ -7158,4 +7158,96 @@ void main() {
71587158
labelSize = tester.getSize(find.text('Tab 1'));
71597159
expect(labelSize, equals(const Size(140.5, 40.0)));
71607160
}, skip: isBrowser && !isSkiaWeb); // https://github.com/flutter/flutter/issues/87543
7161+
7162+
// This is a regression test for https://github.com/flutter/flutter/issues/150000.
7163+
testWidgets('Scrollable TabBar does not jitter in the middle position', (WidgetTester tester) async {
7164+
final List<String> tabs = List<String>.generate(20, (int index) => 'Tab $index');
7165+
7166+
await tester.pumpWidget(MaterialApp(
7167+
home: DefaultTabController(
7168+
length: tabs.length,
7169+
initialIndex: 10,
7170+
child: Scaffold(
7171+
appBar: AppBar(
7172+
bottom: TabBar(
7173+
isScrollable: true,
7174+
tabs: tabs.map((String tab) => Tab(text: tab)).toList(),
7175+
),
7176+
),
7177+
body: TabBarView(
7178+
children: <Widget>[
7179+
for (int i = 0; i < tabs.length; i++)
7180+
Center(
7181+
child: Text('Page $i'),
7182+
),
7183+
],
7184+
),
7185+
),
7186+
),
7187+
));
7188+
7189+
final SingleChildScrollView scrollable = tester.widget(find.byType(SingleChildScrollView));
7190+
expect(find.text('Page 10'), findsOneWidget);
7191+
expect(find.text('Page 11'), findsNothing);
7192+
expect(scrollable.controller!.position.pixels, closeTo(683.2, 0.1));
7193+
7194+
// Drag the TabBarView to the left.
7195+
await tester.drag(find.byType(TabBarView), const Offset(-800, 0));
7196+
await tester.pump();
7197+
await tester.pump(const Duration(milliseconds: 200));
7198+
7199+
expect(find.text('Page 10'), findsNothing);
7200+
expect(find.text('Page 11'), findsOneWidget);
7201+
expect(scrollable.controller!.position.pixels, closeTo(799.8, 0.1));
7202+
});
7203+
7204+
// This is a regression test for https://github.com/flutter/flutter/issues/150000.
7205+
testWidgets('Scrollable TabBar does not jitter when the tab bar reaches the start', (WidgetTester tester) async {
7206+
final List<String> tabs = List<String>.generate(20, (int index) => 'Tab $index');
7207+
7208+
await tester.pumpWidget(MaterialApp(
7209+
home: DefaultTabController(
7210+
length: tabs.length,
7211+
initialIndex: 4,
7212+
child: Scaffold(
7213+
appBar: AppBar(
7214+
bottom: TabBar(
7215+
isScrollable: true,
7216+
tabs: tabs.map((String tab) => Tab(text: tab)).toList(),
7217+
),
7218+
),
7219+
body: TabBarView(
7220+
children: <Widget>[
7221+
for (int i = 0; i < tabs.length; i++)
7222+
Center(
7223+
child: Text('Page $i'),
7224+
),
7225+
],
7226+
),
7227+
),
7228+
),
7229+
));
7230+
7231+
final SingleChildScrollView scrollable = tester.widget(find.byType(SingleChildScrollView));
7232+
7233+
expect(find.text('Page 4'), findsOneWidget);
7234+
expect(find.text('Page 3'), findsNothing);
7235+
expect(scrollable.controller!.position.pixels, closeTo(61.25, 0.1));
7236+
7237+
// Drag the TabBarView to the right.
7238+
final TestGesture gesture = await tester.startGesture(tester.getCenter(find.text('Page 4')));
7239+
await gesture.moveBy(const Offset(600.0, 0.0));
7240+
await gesture.up();
7241+
await tester.pump();
7242+
await tester.pump(const Duration(milliseconds: 500));
7243+
7244+
expect(find.text('Page 4'), findsOneWidget);
7245+
expect(find.text('Page 3'), findsOneWidget);
7246+
expect(scrollable.controller!.position.pixels, closeTo(0.2, 0.1));
7247+
7248+
await tester.pumpAndSettle();
7249+
expect(find.text('Page 4'), findsNothing);
7250+
expect(find.text('Page 3'), findsOneWidget);
7251+
expect(scrollable.controller!.position.pixels, equals(0.0));
7252+
});
71617253
}

0 commit comments

Comments
 (0)