Skip to content

Commit d841d32

Browse files
authored
TabBar should adjust scroll position when Controller is changed (flutter#116019)
Co-authored-by: Bruno Leroux <[email protected]>
1 parent 2703a2b commit d841d32

File tree

2 files changed

+70
-11
lines changed

2 files changed

+70
-11
lines changed

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

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -519,26 +519,35 @@ class _TabBarScrollPosition extends ScrollPositionWithSingleContext {
519519

520520
final _TabBarState tabBar;
521521

522-
bool? _initialViewportDimensionWasZero;
522+
bool _viewportDimensionWasNonZero = false;
523+
524+
// Position should be adjusted at least once.
525+
bool _needsPixelsCorrection = true;
523526

524527
@override
525528
bool applyContentDimensions(double minScrollExtent, double maxScrollExtent) {
526529
bool result = true;
527-
if (_initialViewportDimensionWasZero != true) {
528-
// If the viewport never had a non-zero dimension, we just want to jump
529-
// to the initial scroll position to avoid strange scrolling effects in
530-
// release mode: In release mode, the viewport temporarily may have a
531-
// dimension of zero before the actual dimension is calculated. In that
532-
// scenario, setting the actual dimension would cause a strange scroll
533-
// effect without this guard because the super call below would starts a
534-
// ballistic scroll activity.
535-
assert(viewportDimension != null);
536-
_initialViewportDimensionWasZero = viewportDimension != 0.0;
530+
if (!_viewportDimensionWasNonZero) {
531+
_viewportDimensionWasNonZero = viewportDimension != 0.0;
532+
}
533+
// If the viewport never had a non-zero dimension, we just want to jump
534+
// to the initial scroll position to avoid strange scrolling effects in
535+
// release mode: In release mode, the viewport temporarily may have a
536+
// dimension of zero before the actual dimension is calculated. In that
537+
// scenario, setting the actual dimension would cause a strange scroll
538+
// effect without this guard because the super call below would starts a
539+
// ballistic scroll activity.
540+
if (!_viewportDimensionWasNonZero || _needsPixelsCorrection) {
541+
_needsPixelsCorrection = false;
537542
correctPixels(tabBar._initialScrollOffset(viewportDimension, minScrollExtent, maxScrollExtent));
538543
result = false;
539544
}
540545
return super.applyContentDimensions(minScrollExtent, maxScrollExtent) && result;
541546
}
547+
548+
void markNeedsPixelsCorrection() {
549+
_needsPixelsCorrection = true;
550+
}
542551
}
543552

544553
// This class, and TabBarScrollPosition, only exist to handle the case
@@ -1027,6 +1036,13 @@ class _TabBarState extends State<TabBar> {
10271036
if (widget.controller != oldWidget.controller) {
10281037
_updateTabController();
10291038
_initIndicatorPainter();
1039+
// Adjust scroll position.
1040+
if (_scrollController != null) {
1041+
final ScrollPosition position = _scrollController!.position;
1042+
if (position is _TabBarScrollPosition) {
1043+
position.markNeedsPixelsCorrection();
1044+
}
1045+
}
10301046
} else if (widget.indicatorColor != oldWidget.indicatorColor ||
10311047
widget.indicatorWeight != oldWidget.indicatorWeight ||
10321048
widget.indicatorSize != oldWidget.indicatorSize ||

packages/flutter/test/material/tabs_test.dart

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3420,6 +3420,49 @@ void main() {
34203420
));
34213421
});
34223422

3423+
testWidgets('TabController changes with different initialIndex', (WidgetTester tester) async {
3424+
// This is a regression test for https://github.com/flutter/flutter/issues/115917
3425+
const Key lastTabKey = Key('Last Tab');
3426+
TabController? controller;
3427+
3428+
Widget buildFrame(int length) {
3429+
controller = TabController(
3430+
vsync: const TestVSync(),
3431+
length: length,
3432+
initialIndex: length - 1,
3433+
);
3434+
return boilerplate(
3435+
child: TabBar(
3436+
labelPadding: EdgeInsets.zero,
3437+
controller: controller,
3438+
isScrollable: true,
3439+
tabs: List<Widget>.generate(
3440+
length,
3441+
(int index) {
3442+
return SizedBox(
3443+
width: 100,
3444+
child: Tab(
3445+
key: index == length - 1 ? lastTabKey : null,
3446+
text: 'Tab $index',
3447+
),
3448+
);
3449+
},
3450+
),
3451+
),
3452+
);
3453+
}
3454+
3455+
await tester.pumpWidget(buildFrame(10));
3456+
expect(controller!.index, 9);
3457+
expect(tester.getCenter(find.byKey(lastTabKey)).dx, equals(750.0));
3458+
3459+
// Rebuild with a new controller with more tabs and last tab selected.
3460+
// Last tab should be visible and on the right of the window.
3461+
await tester.pumpWidget(buildFrame(15));
3462+
expect(controller!.index, 14);
3463+
expect(tester.getCenter(find.byKey(lastTabKey)).dx, equals(750.0));
3464+
});
3465+
34233466
testWidgets('Default tab indicator color is white', (WidgetTester tester) async {
34243467
// Regression test for https://github.com/flutter/flutter/issues/15958
34253468
final List<String> tabs = <String>['LEFT', 'RIGHT'];

0 commit comments

Comments
 (0)