@@ -88,6 +88,20 @@ enum TabAlignment {
88
88
center,
89
89
}
90
90
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
+
91
105
/// A Material Design [TabBar] tab.
92
106
///
93
107
/// If both [icon] and [text] are provided, the text is displayed below
@@ -446,6 +460,7 @@ class _IndicatorPainter extends CustomPainter {
446
460
this .dividerHeight,
447
461
required this .showDivider,
448
462
this .devicePixelRatio,
463
+ required this .indicatorAnimation,
449
464
}) : super (repaint: controller.animation) {
450
465
// TODO(polina-c): stop duplicating code across disposables
451
466
// https://github.com/flutter/flutter/issues/137435
@@ -471,6 +486,7 @@ class _IndicatorPainter extends CustomPainter {
471
486
final double ? dividerHeight;
472
487
final bool showDivider;
473
488
final double ? devicePixelRatio;
489
+ final TabIndicatorAnimation indicatorAnimation;
474
490
475
491
// _currentTabOffsets and _currentTextDirection are set each time TabBar
476
492
// layout is completed. These values can be null when TabBar contains no
@@ -556,10 +572,9 @@ class _IndicatorPainter extends CustomPainter {
556
572
final Rect toRect = indicatorRect (size, to);
557
573
_currentRect = Rect .lerp (fromRect, toRect, (value - from).abs ());
558
574
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),
563
578
};
564
579
565
580
assert (_currentRect != null );
@@ -588,8 +603,8 @@ class _IndicatorPainter extends CustomPainter {
588
603
return 1.0 - math.cos ((fraction * math.pi) / 2.0 );
589
604
}
590
605
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) {
593
608
// If the tab animation is completed, there is no need to stretch the indicator
594
609
// This only works for the tab change animation via tab index, not when
595
610
// dragging a [TabBarView], but it's still ok, to avoid unnecessary calculations.
@@ -851,6 +866,7 @@ class TabBar extends StatefulWidget implements PreferredSizeWidget {
851
866
this .splashBorderRadius,
852
867
this .tabAlignment,
853
868
this .textScaler,
869
+ this .indicatorAnimation,
854
870
}) : _isPrimary = true ,
855
871
assert (indicator != null || (indicatorWeight > 0.0 ));
856
872
@@ -903,6 +919,7 @@ class TabBar extends StatefulWidget implements PreferredSizeWidget {
903
919
this .splashBorderRadius,
904
920
this .tabAlignment,
905
921
this .textScaler,
922
+ this .indicatorAnimation,
906
923
}) : _isPrimary = false ,
907
924
assert (indicator != null || (indicatorWeight > 0.0 ));
908
925
@@ -1248,6 +1265,25 @@ class TabBar extends StatefulWidget implements PreferredSizeWidget {
1248
1265
/// * [TextScaler] , which is used to scale text based on the device's text scale factor.
1249
1266
final TextScaler ? textScaler;
1250
1267
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
+
1251
1287
/// A size whose height depends on if the tabs have both icons and text.
1252
1288
///
1253
1289
/// [AppBar] uses this size to compute its own preferred size.
@@ -1426,6 +1462,11 @@ class _TabBarState extends State<TabBar> {
1426
1462
1427
1463
final _IndicatorPainter ? oldPainter = _indicatorPainter;
1428
1464
1465
+ final TabIndicatorAnimation defaultTabIndicatorAnimation = switch (indicatorSize) {
1466
+ TabBarIndicatorSize .label => TabIndicatorAnimation .elastic,
1467
+ TabBarIndicatorSize .tab => TabIndicatorAnimation .linear,
1468
+ };
1469
+
1429
1470
_indicatorPainter = ! _controllerIsValid ? null : _IndicatorPainter (
1430
1471
controller: _controller! ,
1431
1472
indicator: _getIndicator (indicatorSize),
@@ -1439,6 +1480,7 @@ class _TabBarState extends State<TabBar> {
1439
1480
dividerHeight: widget.dividerHeight ?? tabBarTheme.dividerHeight ?? _defaults.dividerHeight,
1440
1481
showDivider: theme.useMaterial3 && ! widget.isScrollable,
1441
1482
devicePixelRatio: MediaQuery .devicePixelRatioOf (context),
1483
+ indicatorAnimation: widget.indicatorAnimation ?? tabBarTheme.indicatorAnimation ?? defaultTabIndicatorAnimation,
1442
1484
);
1443
1485
1444
1486
oldPainter? .dispose ();
@@ -1471,7 +1513,8 @@ class _TabBarState extends State<TabBar> {
1471
1513
widget.indicatorPadding != oldWidget.indicatorPadding ||
1472
1514
widget.indicator != oldWidget.indicator ||
1473
1515
widget.dividerColor != oldWidget.dividerColor ||
1474
- widget.dividerHeight != oldWidget.dividerHeight) {
1516
+ widget.dividerHeight != oldWidget.dividerHeight||
1517
+ widget.indicatorAnimation != oldWidget.indicatorAnimation) {
1475
1518
_initIndicatorPainter ();
1476
1519
}
1477
1520
0 commit comments