@@ -481,6 +481,7 @@ class _IndicatorPainter extends CustomPainter {
481
481
required this .showDivider,
482
482
this .devicePixelRatio,
483
483
required this .indicatorAnimation,
484
+ required this .textDirection,
484
485
}) : super (repaint: controller.animation) {
485
486
// TODO(polina-c): stop duplicating code across disposables
486
487
// https://github.com/flutter/flutter/issues/137435
@@ -507,6 +508,7 @@ class _IndicatorPainter extends CustomPainter {
507
508
final bool showDivider;
508
509
final double ? devicePixelRatio;
509
510
final TabIndicatorAnimation indicatorAnimation;
511
+ final TextDirection textDirection;
510
512
511
513
// _currentTabOffsets and _currentTextDirection are set each time TabBar
512
514
// layout is completed. These values can be null when TabBar contains no
@@ -583,18 +585,28 @@ class _IndicatorPainter extends CustomPainter {
583
585
_needsPaint = false ;
584
586
_painter ?? = indicator.createBoxPainter (markNeedsPaint);
585
587
586
- final double index = controller.index.toDouble ();
587
588
final double value = controller.animation! .value;
588
- final bool ltr = index > value;
589
- final int from = (ltr ? value.floor () : value.ceil ()).clamp (0 , maxTabIndex);
590
- final int to = (ltr ? from + 1 : from - 1 ).clamp (0 , maxTabIndex);
591
- final Rect fromRect = indicatorRect (size, from);
589
+ final int to =
590
+ controller.indexIsChanging
591
+ ? controller.index
592
+ : switch (textDirection) {
593
+ TextDirection .ltr => value.ceil (),
594
+ TextDirection .rtl => value.floor (),
595
+ }.clamp (0 , maxTabIndex);
596
+ final int from =
597
+ controller.indexIsChanging
598
+ ? controller.previousIndex
599
+ : switch (textDirection) {
600
+ TextDirection .ltr => (to - 1 ),
601
+ TextDirection .rtl => (to + 1 ),
602
+ }.clamp (0 , maxTabIndex);
592
603
final Rect toRect = indicatorRect (size, to);
604
+ final Rect fromRect = indicatorRect (size, from);
593
605
_currentRect = Rect .lerp (fromRect, toRect, (value - from).abs ());
594
606
595
607
_currentRect = switch (indicatorAnimation) {
596
608
TabIndicatorAnimation .linear => _currentRect,
597
- TabIndicatorAnimation .elastic => _applyElasticEffect (_currentRect ! , fromRect ),
609
+ TabIndicatorAnimation .elastic => _applyElasticEffect (fromRect, toRect, _currentRect ! ),
598
610
};
599
611
600
612
assert (_currentRect != null );
@@ -627,40 +639,69 @@ class _IndicatorPainter extends CustomPainter {
627
639
}
628
640
629
641
/// Applies the elastic effect to the indicator.
630
- Rect _applyElasticEffect (Rect rect , Rect targetRect ) {
642
+ Rect _applyElasticEffect (Rect fromRect , Rect toRect, Rect currentRect ) {
631
643
// If the tab animation is completed, there is no need to stretch the indicator
632
644
// This only works for the tab change animation via tab index, not when
633
645
// dragging a [TabBarView], but it's still ok, to avoid unnecessary calculations.
634
646
if (controller.animation! .isCompleted) {
635
- return rect ;
647
+ return currentRect ;
636
648
}
637
649
638
650
final double index = controller.index.toDouble ();
639
651
final double value = controller.animation! .value;
640
- final double tabChangeProgress = (index - value).abs ();
652
+ final double tabChangeProgress;
653
+
654
+ if (controller.indexIsChanging) {
655
+ double progressLeft = (index - value).abs ();
656
+ final int tabsDelta = (controller.index - controller.previousIndex).abs ();
657
+ if (tabsDelta != 0 ) {
658
+ progressLeft /= tabsDelta;
659
+ }
660
+ tabChangeProgress = 1 - clampDouble (progressLeft, 0.0 , 1.0 );
661
+ } else {
662
+ tabChangeProgress = (index - value).abs ();
663
+ }
641
664
642
665
// If the animation has finished, there is no need to apply the stretch effect.
643
666
if (tabChangeProgress == 1.0 ) {
644
- return rect ;
667
+ return currentRect ;
645
668
}
646
669
647
- final double fraction = switch (rect.left < targetRect.left) {
648
- true => accelerateInterpolation (tabChangeProgress),
649
- false => decelerateInterpolation (tabChangeProgress),
670
+ final double leftFraction;
671
+ final double rightFraction;
672
+ final bool isMovingRight = switch (textDirection) {
673
+ TextDirection .ltr => controller.indexIsChanging ? index > value : value > index,
674
+ TextDirection .rtl => controller.indexIsChanging ? value > index : index > value,
650
675
};
676
+ if (isMovingRight) {
677
+ leftFraction = accelerateInterpolation (tabChangeProgress);
678
+ rightFraction = decelerateInterpolation (tabChangeProgress);
679
+ } else {
680
+ leftFraction = decelerateInterpolation (tabChangeProgress);
681
+ rightFraction = accelerateInterpolation (tabChangeProgress);
682
+ }
651
683
652
- final Rect stretchedRect = _inflateRectHorizontally (rect, targetRect, fraction);
653
- return stretchedRect;
654
- }
684
+ final double lerpRectLeft;
685
+ final double lerpRectRight;
655
686
656
- /// Same as [Rect.inflate] , but only inflates in the horizontal direction.
657
- Rect _inflateRectHorizontally (Rect rect, Rect targetRect, double fraction) {
658
- return Rect .fromLTRB (
659
- lerpDouble (rect.left, targetRect.left, fraction)! ,
660
- rect.top,
661
- lerpDouble (rect.right, targetRect.right, fraction)! ,
662
- rect.bottom,
663
- );
687
+ // The controller.indexIsChanging is true when the Tab is pressed, instead of swipe to change tabs.
688
+ // If the tab is pressed then only lerp between fromRect and toRect.
689
+ if (controller.indexIsChanging) {
690
+ lerpRectLeft = lerpDouble (fromRect.left, toRect.left, leftFraction)! ;
691
+ lerpRectRight = lerpDouble (fromRect.right, toRect.right, rightFraction)! ;
692
+ } else {
693
+ // Switch the Rect left and right lerp order based on swipe direction.
694
+ lerpRectLeft = switch (isMovingRight) {
695
+ true => lerpDouble (fromRect.left, toRect.left, leftFraction)! ,
696
+ false => lerpDouble (toRect.left, fromRect.left, leftFraction)! ,
697
+ };
698
+ lerpRectRight = switch (isMovingRight) {
699
+ true => lerpDouble (fromRect.right, toRect.right, rightFraction)! ,
700
+ false => lerpDouble (toRect.right, fromRect.right, rightFraction)! ,
701
+ };
702
+ }
703
+
704
+ return Rect .fromLTRB (lerpRectLeft, currentRect.top, lerpRectRight, currentRect.bottom);
664
705
}
665
706
666
707
@override
@@ -1517,6 +1558,7 @@ class _TabBarState extends State<TabBar> {
1517
1558
widget.indicatorAnimation ??
1518
1559
tabBarTheme.indicatorAnimation ??
1519
1560
defaultTabIndicatorAnimation,
1561
+ textDirection: Directionality .of (context),
1520
1562
);
1521
1563
1522
1564
oldPainter? .dispose ();
0 commit comments