@@ -88,6 +88,20 @@ enum TabAlignment {
8888 center,
8989}
9090
91+ /// Defines how the tab indicator animates when the selected tab changes.
92+ ///
93+ /// See also:
94+ /// * [TabBar] , which displays a row of tabs.
95+ /// * [TabBarTheme] , which can be used to configure the appearance of the tab
96+ /// indicator.
97+ enum TabIndicatorAnimation {
98+ /// The tab indicator animates linearly.
99+ linear,
100+
101+ /// The tab indicator animates with an elastic effect.
102+ elastic,
103+ }
104+
91105/// A Material Design [TabBar] tab.
92106///
93107/// If both [icon] and [text] are provided, the text is displayed below
@@ -446,6 +460,7 @@ class _IndicatorPainter extends CustomPainter {
446460 this .dividerHeight,
447461 required this .showDivider,
448462 this .devicePixelRatio,
463+ required this .indicatorAnimation,
449464 }) : super (repaint: controller.animation) {
450465 // TODO(polina-c): stop duplicating code across disposables
451466 // https://github.com/flutter/flutter/issues/137435
@@ -471,6 +486,7 @@ class _IndicatorPainter extends CustomPainter {
471486 final double ? dividerHeight;
472487 final bool showDivider;
473488 final double ? devicePixelRatio;
489+ final TabIndicatorAnimation indicatorAnimation;
474490
475491 // _currentTabOffsets and _currentTextDirection are set each time TabBar
476492 // layout is completed. These values can be null when TabBar contains no
@@ -556,10 +572,9 @@ class _IndicatorPainter extends CustomPainter {
556572 final Rect toRect = indicatorRect (size, to);
557573 _currentRect = Rect .lerp (fromRect, toRect, (value - from).abs ());
558574
559- _currentRect = switch (indicatorSize) {
560- TabBarIndicatorSize .label => _applyStretchEffect (_currentRect! , fromRect),
561- // Do nothing.
562- TabBarIndicatorSize .tab => _currentRect,
575+ _currentRect = switch (indicatorAnimation) {
576+ TabIndicatorAnimation .linear => _currentRect,
577+ TabIndicatorAnimation .elastic => _applyElasticEffect (_currentRect! , fromRect),
563578 };
564579
565580 assert (_currentRect != null );
@@ -588,8 +603,8 @@ class _IndicatorPainter extends CustomPainter {
588603 return 1.0 - math.cos ((fraction * math.pi) / 2.0 );
589604 }
590605
591- /// Applies the stretch effect to the indicator.
592- Rect _applyStretchEffect (Rect rect, Rect targetRect) {
606+ /// Applies the elastic effect to the indicator.
607+ Rect _applyElasticEffect (Rect rect, Rect targetRect) {
593608 // If the tab animation is completed, there is no need to stretch the indicator
594609 // This only works for the tab change animation via tab index, not when
595610 // dragging a [TabBarView], but it's still ok, to avoid unnecessary calculations.
@@ -851,6 +866,7 @@ class TabBar extends StatefulWidget implements PreferredSizeWidget {
851866 this .splashBorderRadius,
852867 this .tabAlignment,
853868 this .textScaler,
869+ this .indicatorAnimation,
854870 }) : _isPrimary = true ,
855871 assert (indicator != null || (indicatorWeight > 0.0 ));
856872
@@ -903,6 +919,7 @@ class TabBar extends StatefulWidget implements PreferredSizeWidget {
903919 this .splashBorderRadius,
904920 this .tabAlignment,
905921 this .textScaler,
922+ this .indicatorAnimation,
906923 }) : _isPrimary = false ,
907924 assert (indicator != null || (indicatorWeight > 0.0 ));
908925
@@ -1248,6 +1265,25 @@ class TabBar extends StatefulWidget implements PreferredSizeWidget {
12481265 /// * [TextScaler] , which is used to scale text based on the device's text scale factor.
12491266 final TextScaler ? textScaler;
12501267
1268+ /// Specifies the animation behavior of the tab indicator.
1269+ ///
1270+ /// If this is null, then the value of [TabBarTheme.indicatorAnimation] is used.
1271+ /// If that is also null, then the tab indicator will animate linearly if the
1272+ /// [indicatorSize] is [TabBarIndicatorSize.tab] , otherwise it will animate
1273+ /// with an elastic effect if the [indicatorSize] is [TabBarIndicatorSize.label] .
1274+ ///
1275+ /// {@tool dartpad}
1276+ /// This sample shows how to customize the animation behavior of the tab indicator
1277+ /// by using the [indicatorAnimation] property.
1278+ ///
1279+ /// ** See code in examples/api/lib/material/tabs/tab_bar.indicator_animation.0.dart **
1280+ /// {@end-tool}
1281+ ///
1282+ /// See also:
1283+ ///
1284+ /// * [TabIndicatorAnimation] , which specifies the animation behavior of the tab indicator.
1285+ final TabIndicatorAnimation ? indicatorAnimation;
1286+
12511287 /// A size whose height depends on if the tabs have both icons and text.
12521288 ///
12531289 /// [AppBar] uses this size to compute its own preferred size.
@@ -1426,6 +1462,11 @@ class _TabBarState extends State<TabBar> {
14261462
14271463 final _IndicatorPainter ? oldPainter = _indicatorPainter;
14281464
1465+ final TabIndicatorAnimation defaultTabIndicatorAnimation = switch (indicatorSize) {
1466+ TabBarIndicatorSize .label => TabIndicatorAnimation .elastic,
1467+ TabBarIndicatorSize .tab => TabIndicatorAnimation .linear,
1468+ };
1469+
14291470 _indicatorPainter = ! _controllerIsValid ? null : _IndicatorPainter (
14301471 controller: _controller! ,
14311472 indicator: _getIndicator (indicatorSize),
@@ -1439,6 +1480,7 @@ class _TabBarState extends State<TabBar> {
14391480 dividerHeight: widget.dividerHeight ?? tabBarTheme.dividerHeight ?? _defaults.dividerHeight,
14401481 showDivider: theme.useMaterial3 && ! widget.isScrollable,
14411482 devicePixelRatio: MediaQuery .devicePixelRatioOf (context),
1483+ indicatorAnimation: widget.indicatorAnimation ?? tabBarTheme.indicatorAnimation ?? defaultTabIndicatorAnimation,
14421484 );
14431485
14441486 oldPainter? .dispose ();
@@ -1471,7 +1513,8 @@ class _TabBarState extends State<TabBar> {
14711513 widget.indicatorPadding != oldWidget.indicatorPadding ||
14721514 widget.indicator != oldWidget.indicator ||
14731515 widget.dividerColor != oldWidget.dividerColor ||
1474- widget.dividerHeight != oldWidget.dividerHeight) {
1516+ widget.dividerHeight != oldWidget.dividerHeight||
1517+ widget.indicatorAnimation != oldWidget.indicatorAnimation) {
14751518 _initIndicatorPainter ();
14761519 }
14771520
0 commit comments