Skip to content

Commit 10998cd

Browse files
[CP-stable][Reland] Fix Tab linear and elastic animation blink (flutter#162315) (flutter#163830)
This pull request is created by [automatic cherry pick workflow](https://github.com/flutter/flutter/blob/main/docs/releases/Flutter-Cherrypick-Process.md#automatically-creates-a-cherry-pick-request) Please fill in the form below, and a flutter domain expert will evaluate this cherry pick request. ### Issue Link: Cherry picked PR: flutter#162450 Issue: flutter#162098 < Replace with issue link here > ### Changelog Description: Explain this cherry pick in one line that is accessible to most Flutter developers. See [best practices](https://github.com/flutter/flutter/blob/main/docs/releases/Hotfix-Documentation-Best-Practices.md) for examples < Replace with changelog description here > ### Impact Description: What is the impact (ex. visual jank on Samsung phones, app crash, cannot ship an iOS app)? Does it impact development (ex. flutter doctor crashes when Android Studio is installed), or the shipping production app (the app crashes on launch) Anyone using the `TabBar` widget. High impact. ### Workaround: Is there a workaround for this issue? None. ### Risk: What is the risk level of this cherry-pick? ### Test Coverage: Are you confident that your fix is well-tested by automated tests? ### Validation Steps: What are the steps to validate that this fix works? Run a `TabBar` with linear tab animation and tap third or fourth or use elastic animation observe tab indictor highlight.
1 parent ccf215b commit 10998cd

File tree

2 files changed

+988
-194
lines changed

2 files changed

+988
-194
lines changed

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

Lines changed: 38 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -586,27 +586,10 @@ class _IndicatorPainter extends CustomPainter {
586586
_painter ??= indicator.createBoxPainter(markNeedsPaint);
587587

588588
final double value = controller.animation!.value;
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);
603-
final Rect toRect = indicatorRect(size, to);
604-
final Rect fromRect = indicatorRect(size, from);
605-
_currentRect = Rect.lerp(fromRect, toRect, (value - from).abs());
606589

607590
_currentRect = switch (indicatorAnimation) {
608-
TabIndicatorAnimation.linear => _currentRect,
609-
TabIndicatorAnimation.elastic => _applyElasticEffect(fromRect, toRect, _currentRect!),
591+
TabIndicatorAnimation.linear => _applyLinearEffect(size: size, value: value),
592+
TabIndicatorAnimation.elastic => _applyElasticEffect(size: size, value: value),
610593
};
611594

612595
assert(_currentRect != null);
@@ -628,6 +611,17 @@ class _IndicatorPainter extends CustomPainter {
628611
_painter!.paint(canvas, _currentRect!.topLeft, configuration);
629612
}
630613

614+
/// Applies the linear effect to the indicator.
615+
Rect? _applyLinearEffect({required Size size, required double value}) {
616+
final double index = controller.index.toDouble();
617+
final bool ltr = index > value;
618+
final int from = (ltr ? value.floor() : value.ceil()).clamp(0, maxTabIndex);
619+
final int to = (ltr ? from + 1 : from - 1).clamp(0, maxTabIndex);
620+
final Rect fromRect = indicatorRect(size, from);
621+
final Rect toRect = indicatorRect(size, to);
622+
return Rect.lerp(fromRect, toRect, (value - from).abs());
623+
}
624+
631625
// Ease out sine (decelerating).
632626
double decelerateInterpolation(double fraction) {
633627
return math.sin((fraction * math.pi) / 2.0);
@@ -639,20 +633,38 @@ class _IndicatorPainter extends CustomPainter {
639633
}
640634

641635
/// Applies the elastic effect to the indicator.
642-
Rect _applyElasticEffect(Rect fromRect, Rect toRect, Rect currentRect) {
636+
Rect? _applyElasticEffect({required Size size, required double value}) {
637+
final double index = controller.index.toDouble();
638+
double progressLeft = (index - value).abs();
639+
640+
final int to =
641+
progressLeft == 0.0 || !controller.indexIsChanging
642+
? switch (textDirection) {
643+
TextDirection.ltr => value.ceil(),
644+
TextDirection.rtl => value.floor(),
645+
}.clamp(0, maxTabIndex)
646+
: controller.index;
647+
final int from =
648+
progressLeft == 0.0 || !controller.indexIsChanging
649+
? switch (textDirection) {
650+
TextDirection.ltr => (to - 1),
651+
TextDirection.rtl => (to + 1),
652+
}.clamp(0, maxTabIndex)
653+
: controller.previousIndex;
654+
final Rect toRect = indicatorRect(size, to);
655+
final Rect fromRect = indicatorRect(size, from);
656+
final Rect rect = Rect.lerp(fromRect, toRect, (value - from).abs())!;
657+
643658
// If the tab animation is completed, there is no need to stretch the indicator
644659
// This only works for the tab change animation via tab index, not when
645660
// dragging a [TabBarView], but it's still ok, to avoid unnecessary calculations.
646661
if (controller.animation!.isCompleted) {
647-
return currentRect;
662+
return rect;
648663
}
649664

650-
final double index = controller.index.toDouble();
651-
final double value = controller.animation!.value;
652665
final double tabChangeProgress;
653666

654667
if (controller.indexIsChanging) {
655-
double progressLeft = (index - value).abs();
656668
final int tabsDelta = (controller.index - controller.previousIndex).abs();
657669
if (tabsDelta != 0) {
658670
progressLeft /= tabsDelta;
@@ -664,7 +676,7 @@ class _IndicatorPainter extends CustomPainter {
664676

665677
// If the animation has finished, there is no need to apply the stretch effect.
666678
if (tabChangeProgress == 1.0) {
667-
return currentRect;
679+
return rect;
668680
}
669681

670682
final double leftFraction;
@@ -701,7 +713,7 @@ class _IndicatorPainter extends CustomPainter {
701713
};
702714
}
703715

704-
return Rect.fromLTRB(lerpRectLeft, currentRect.top, lerpRectRight, currentRect.bottom);
716+
return Rect.fromLTRB(lerpRectLeft, rect.top, lerpRectRight, rect.bottom);
705717
}
706718

707719
@override

0 commit comments

Comments
 (0)