From d841d321469e6c964b530c58b11b6a8da859aa65 Mon Sep 17 00:00:00 2001 From: Bruno Leroux Date: Mon, 28 Nov 2022 16:51:17 +0100 Subject: [PATCH] TabBar should adjust scroll position when Controller is changed (#116019) Co-authored-by: Bruno Leroux --- packages/flutter/lib/src/material/tabs.dart | 38 +++++++++++----- packages/flutter/test/material/tabs_test.dart | 43 +++++++++++++++++++ 2 files changed, 70 insertions(+), 11 deletions(-) diff --git a/packages/flutter/lib/src/material/tabs.dart b/packages/flutter/lib/src/material/tabs.dart index 7602531f6d69..c7b51235cef6 100644 --- a/packages/flutter/lib/src/material/tabs.dart +++ b/packages/flutter/lib/src/material/tabs.dart @@ -519,26 +519,35 @@ class _TabBarScrollPosition extends ScrollPositionWithSingleContext { final _TabBarState tabBar; - bool? _initialViewportDimensionWasZero; + bool _viewportDimensionWasNonZero = false; + + // Position should be adjusted at least once. + bool _needsPixelsCorrection = true; @override bool applyContentDimensions(double minScrollExtent, double maxScrollExtent) { bool result = true; - if (_initialViewportDimensionWasZero != true) { - // If the viewport never had a non-zero dimension, we just want to jump - // to the initial scroll position to avoid strange scrolling effects in - // release mode: In release mode, the viewport temporarily may have a - // dimension of zero before the actual dimension is calculated. In that - // scenario, setting the actual dimension would cause a strange scroll - // effect without this guard because the super call below would starts a - // ballistic scroll activity. - assert(viewportDimension != null); - _initialViewportDimensionWasZero = viewportDimension != 0.0; + if (!_viewportDimensionWasNonZero) { + _viewportDimensionWasNonZero = viewportDimension != 0.0; + } + // If the viewport never had a non-zero dimension, we just want to jump + // to the initial scroll position to avoid strange scrolling effects in + // release mode: In release mode, the viewport temporarily may have a + // dimension of zero before the actual dimension is calculated. In that + // scenario, setting the actual dimension would cause a strange scroll + // effect without this guard because the super call below would starts a + // ballistic scroll activity. + if (!_viewportDimensionWasNonZero || _needsPixelsCorrection) { + _needsPixelsCorrection = false; correctPixels(tabBar._initialScrollOffset(viewportDimension, minScrollExtent, maxScrollExtent)); result = false; } return super.applyContentDimensions(minScrollExtent, maxScrollExtent) && result; } + + void markNeedsPixelsCorrection() { + _needsPixelsCorrection = true; + } } // This class, and TabBarScrollPosition, only exist to handle the case @@ -1027,6 +1036,13 @@ class _TabBarState extends State { if (widget.controller != oldWidget.controller) { _updateTabController(); _initIndicatorPainter(); + // Adjust scroll position. + if (_scrollController != null) { + final ScrollPosition position = _scrollController!.position; + if (position is _TabBarScrollPosition) { + position.markNeedsPixelsCorrection(); + } + } } else if (widget.indicatorColor != oldWidget.indicatorColor || widget.indicatorWeight != oldWidget.indicatorWeight || widget.indicatorSize != oldWidget.indicatorSize || diff --git a/packages/flutter/test/material/tabs_test.dart b/packages/flutter/test/material/tabs_test.dart index 48493d4b4f71..ef0074bd4248 100644 --- a/packages/flutter/test/material/tabs_test.dart +++ b/packages/flutter/test/material/tabs_test.dart @@ -3420,6 +3420,49 @@ void main() { )); }); + testWidgets('TabController changes with different initialIndex', (WidgetTester tester) async { + // This is a regression test for https://github.com/flutter/flutter/issues/115917 + const Key lastTabKey = Key('Last Tab'); + TabController? controller; + + Widget buildFrame(int length) { + controller = TabController( + vsync: const TestVSync(), + length: length, + initialIndex: length - 1, + ); + return boilerplate( + child: TabBar( + labelPadding: EdgeInsets.zero, + controller: controller, + isScrollable: true, + tabs: List.generate( + length, + (int index) { + return SizedBox( + width: 100, + child: Tab( + key: index == length - 1 ? lastTabKey : null, + text: 'Tab $index', + ), + ); + }, + ), + ), + ); + } + + await tester.pumpWidget(buildFrame(10)); + expect(controller!.index, 9); + expect(tester.getCenter(find.byKey(lastTabKey)).dx, equals(750.0)); + + // Rebuild with a new controller with more tabs and last tab selected. + // Last tab should be visible and on the right of the window. + await tester.pumpWidget(buildFrame(15)); + expect(controller!.index, 14); + expect(tester.getCenter(find.byKey(lastTabKey)).dx, equals(750.0)); + }); + testWidgets('Default tab indicator color is white', (WidgetTester tester) async { // Regression test for https://github.com/flutter/flutter/issues/15958 final List tabs = ['LEFT', 'RIGHT'];