Skip to content

Commit

Permalink
Fix scrollable TabBar jittering (#150041)
Browse files Browse the repository at this point in the history
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
  • Loading branch information
TahaTesser authored Jun 18, 2024
1 parent 6399107 commit a9f554a
Show file tree
Hide file tree
Showing 2 changed files with 97 additions and 6 deletions.
11 changes: 5 additions & 6 deletions packages/flutter/lib/src/material/tabs.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1549,12 +1549,11 @@ class _TabBarState extends State<TabBar> {
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);
Expand Down
92 changes: 92 additions & 0 deletions packages/flutter/test/material/tabs_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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<String> tabs = List<String>.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: <Widget>[
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<String> tabs = List<String>.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: <Widget>[
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));
});
}

0 comments on commit a9f554a

Please sign in to comment.