Reverts "Fix Tab linear and elastic animation blink (#162315)" (#162387)

<!-- start_original_pr_link -->
Reverts: flutter/flutter#162315
<!-- end_original_pr_link -->
<!-- start_initiating_author -->
Initiated by: TahaTesser
<!-- end_initiating_author -->
<!-- start_revert_reason -->
Reason for reverting: Red tree due to unapproved golden images. 
<!-- end_revert_reason -->
<!-- start_original_pr_author -->
Original PR Author: TahaTesser
<!-- end_original_pr_author -->

<!-- start_reviewers -->
Reviewed By: {justinmc}
<!-- end_reviewers -->

<!-- start_revert_body -->
This change reverts the following previous change:
Fixes
[https://github.com/flutter/flutter/issues/162098](https://github.com/flutter/flutter/issues/162098)

### Description 

This PR fixes `Tab` linear and elastic animation blinks/flickers when
skipping multiple tabs. Previous attempt to fix elastic animation didn't
cover linear animation tests and didn't have enough number of tab items
which this PR fixes.

- Fixed Linear and elastic animation blink issue.
- Added tests for linear and elastic animation with various tab sizes
(LTR and RTL)
- Added tests for linear and elastic animation when skipping tabs (LTR
and RTL)

### Code Sample

<details>
<summary>expand to view the code sample</summary> 

```dart
import 'package:flutter/material.dart';
// import 'package:flutter/scheduler.dart';

void main() {
  // timeDilation = 10;
  runApp(const TabBarDemo());
}

class TabBarDemo extends StatelessWidget {
  const TabBarDemo({super.key});

  @override
  Widget build(BuildContext context) {
    final List<Widget> tabs = <Widget>[
      const Tab(text: 'Short'),
      const Tab(text: 'A Bit Longer Text'),
      const Tab(text: 'An Extremely Long Tab Label That Overflows'),
      const Tab(text: 'Tiny'),
      const Tab(text: 'Moderate Length'),
      const Tab(text: 'Just Right'),
      const Tab(text: 'Supercalifragilisticexpialidocious'),
      const Tab(text: 'Longer Than Usual'),
    ];

    return MaterialApp(
      home: DefaultTabController(
        length: tabs.length,
        child: Scaffold(
          appBar: AppBar(
            bottom: TabBar(
              tabAlignment: TabAlignment.start,
              isScrollable: true,
              indicatorAnimation: TabIndicatorAnimation.elastic,
              tabs: tabs,
            ),
            title: const Text('Tabs Demo'),
          ),
          body: TabBarView(
            children: <Widget>[
              for (int i = 0; i < tabs.length; i++) const Icon(Icons.directions_car),
            ],
          ),
        ),
      ),
    );
  }
}

```

</details>

### Before


https://github.com/user-attachments/assets/5c271948-5a01-4520-90a3-921c20c79470

### After


https://github.com/user-attachments/assets/6af32d43-3588-488f-ba50-be59323ed692


### Linear animation before (left) and After (right) comparison.

<img width="1048" alt="Screenshot 2025-01-28 at 17 27 50"
src="https://github.com/user-attachments/assets/4ba587a5-24d0-40ce-817c-366d004abc05"
/>




## Pre-launch Checklist

- [x] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [x] I read the [Tree Hygiene] wiki page, which explains my
responsibilities.
- [x] I read and followed the [Flutter Style Guide], including [Features
we expect every widget to implement].
- [x] I signed the [CLA].
- [x] I listed at least one issue that this PR fixes in the description
above.
- [x] I updated/added relevant documentation (doc comments with `///`).
- [x] I added new tests to check the change I am making, or this PR is
[test-exempt].
- [ ] I followed the [breaking change policy] and added [Data Driven
Fixes] where supported.
- [x] All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel
on [Discord].

<!-- Links -->
[Contributor Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview
[Tree Hygiene]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md
[test-exempt]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests
[Flutter Style Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md
[Features we expect every widget to implement]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement
[CLA]: https://cla.developers.google.com/
[flutter/tests]: https://github.com/flutter/tests
[breaking change policy]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes
[Discord]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md
[Data Driven Fixes]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md

<!-- end_revert_body -->

Co-authored-by: auto-submit[bot] <flutter-engprod-team@google.com>
This commit is contained in:
auto-submit[bot] 2025-01-29 15:10:05 -05:00 committed by GitHub
parent b007899d3a
commit 2c1e4b1615
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 134 additions and 468 deletions

View File

@ -586,10 +586,27 @@ class _IndicatorPainter extends CustomPainter {
_painter ??= indicator.createBoxPainter(markNeedsPaint);
final double value = controller.animation!.value;
final int to =
controller.indexIsChanging
? controller.index
: switch (textDirection) {
TextDirection.ltr => value.ceil(),
TextDirection.rtl => value.floor(),
}.clamp(0, maxTabIndex);
final int from =
controller.indexIsChanging
? controller.previousIndex
: switch (textDirection) {
TextDirection.ltr => (to - 1),
TextDirection.rtl => (to + 1),
}.clamp(0, maxTabIndex);
final Rect toRect = indicatorRect(size, to);
final Rect fromRect = indicatorRect(size, from);
_currentRect = Rect.lerp(fromRect, toRect, (value - from).abs());
_currentRect = switch (indicatorAnimation) {
TabIndicatorAnimation.linear => _applyLinearEffect(size: size, value: value),
TabIndicatorAnimation.elastic => _applyElasticEffect(size: size, value: value),
TabIndicatorAnimation.linear => _currentRect,
TabIndicatorAnimation.elastic => _applyElasticEffect(fromRect, toRect, _currentRect!),
};
assert(_currentRect != null);
@ -611,17 +628,6 @@ class _IndicatorPainter extends CustomPainter {
_painter!.paint(canvas, _currentRect!.topLeft, configuration);
}
/// Applies the linear effect to the indicator.
Rect? _applyLinearEffect({required Size size, required double value}) {
final double index = controller.index.toDouble();
final bool ltr = index > value;
final int from = (ltr ? value.floor() : value.ceil()).clamp(0, maxTabIndex);
final int to = (ltr ? from + 1 : from - 1).clamp(0, maxTabIndex);
final Rect fromRect = indicatorRect(size, from);
final Rect toRect = indicatorRect(size, to);
return Rect.lerp(fromRect, toRect, (value - from).abs());
}
// Ease out sine (decelerating).
double decelerateInterpolation(double fraction) {
return math.sin((fraction * math.pi) / 2.0);
@ -633,38 +639,20 @@ class _IndicatorPainter extends CustomPainter {
}
/// Applies the elastic effect to the indicator.
Rect? _applyElasticEffect({required Size size, required double value}) {
final double index = controller.index.toDouble();
double progressLeft = (index - value).abs();
final int to =
progressLeft == 0.0 || !controller.indexIsChanging
? switch (textDirection) {
TextDirection.ltr => value.ceil(),
TextDirection.rtl => value.floor(),
}.clamp(0, maxTabIndex)
: controller.index;
final int from =
progressLeft == 0.0 || !controller.indexIsChanging
? switch (textDirection) {
TextDirection.ltr => (to - 1),
TextDirection.rtl => (to + 1),
}.clamp(0, maxTabIndex)
: controller.previousIndex;
final Rect toRect = indicatorRect(size, to);
final Rect fromRect = indicatorRect(size, from);
final Rect rect = Rect.lerp(fromRect, toRect, (value - from).abs())!;
Rect _applyElasticEffect(Rect fromRect, Rect toRect, Rect currentRect) {
// If the tab animation is completed, there is no need to stretch the indicator
// This only works for the tab change animation via tab index, not when
// dragging a [TabBarView], but it's still ok, to avoid unnecessary calculations.
if (controller.animation!.isCompleted) {
return rect;
return currentRect;
}
final double index = controller.index.toDouble();
final double value = controller.animation!.value;
final double tabChangeProgress;
if (controller.indexIsChanging) {
double progressLeft = (index - value).abs();
final int tabsDelta = (controller.index - controller.previousIndex).abs();
if (tabsDelta != 0) {
progressLeft /= tabsDelta;
@ -676,7 +664,7 @@ class _IndicatorPainter extends CustomPainter {
// If the animation has finished, there is no need to apply the stretch effect.
if (tabChangeProgress == 1.0) {
return rect;
return currentRect;
}
final double leftFraction;
@ -713,7 +701,7 @@ class _IndicatorPainter extends CustomPainter {
};
}
return Rect.fromLTRB(lerpRectLeft, rect.top, lerpRectRight, rect.bottom);
return Rect.fromLTRB(lerpRectLeft, currentRect.top, lerpRectRight, currentRect.bottom);
}
@override

View File

@ -3753,8 +3753,10 @@ void main() {
tabBarBox,
paints..line(
strokeWidth: indicatorWeight,
p1: const Offset(4951.0, indicatorY),
p2: const Offset(5049.0, indicatorY),
// In RTL, the elastic tab animation expands the width of the tab with a negative offset
// when jumping from the first tab to the last tab in a scrollable tab bar.
p1: const Offset(-480149, indicatorY),
p2: const Offset(-480051, indicatorY),
),
);
@ -7822,14 +7824,9 @@ void main() {
addTearDown(animationSheet.dispose);
final List<Widget> tabs = <Widget>[
const Tab(text: 'Short'),
const Tab(text: 'A Bit Longer Text'),
const Tab(text: 'An Extremely Long Tab Label That Overflows'),
const Tab(text: 'Tiny'),
const Tab(text: 'Moderate Length'),
const Tab(text: 'Just Right'),
const Tab(text: 'Supercalifragilisticexpialidocious'),
const Tab(text: 'Longer Than Usual'),
const Tab(text: 'Medium'),
const Tab(text: 'Extremely Very Long Label'),
const Tab(text: 'C'),
];
final TabController controller = createTabController(
@ -7837,7 +7834,7 @@ void main() {
length: tabs.length,
);
Widget buildTabBar() {
Widget target() {
return animationSheet.record(
boilerplate(
child: Container(
@ -7852,19 +7849,13 @@ void main() {
);
}
await tester.pumpFrames(buildTabBar(), const Duration(milliseconds: 50));
await tester.pumpFrames(target(), const Duration(milliseconds: 50));
await tester.tap(find.byType(Tab).at(2));
await tester.pumpFrames(buildTabBar(), const Duration(milliseconds: 500));
await tester.tap(find.text('Extremely Very Long Label'));
await tester.pumpFrames(target(), const Duration(milliseconds: 500));
await tester.tap(find.byType(Tab).at(3));
await tester.pumpFrames(buildTabBar(), const Duration(milliseconds: 500));
await tester.tap(find.byType(Tab).at(5));
await tester.pumpFrames(buildTabBar(), const Duration(milliseconds: 500));
await tester.tap(find.byType(Tab).at(4));
await tester.pumpFrames(buildTabBar(), const Duration(milliseconds: 500));
await tester.tap(find.text('C'));
await tester.pumpFrames(target(), const Duration(milliseconds: 500));
await expectLater(
animationSheet.collate(1),
@ -7872,179 +7863,6 @@ void main() {
);
}, skip: isBrowser); // https://github.com/flutter/flutter/issues/56001
testWidgets('Elastic Tab animation with various size tabs - RTL', (WidgetTester tester) async {
final AnimationSheetBuilder animationSheet = AnimationSheetBuilder(
frameSize: const Size(800, 100),
);
addTearDown(animationSheet.dispose);
final List<Widget> tabs = <Widget>[
const Tab(text: 'Short'),
const Tab(text: 'A Bit Longer Text'),
const Tab(text: 'An Extremely Long Tab Label That Overflows'),
const Tab(text: 'Tiny'),
const Tab(text: 'Moderate Length'),
const Tab(text: 'Just Right'),
const Tab(text: 'Supercalifragilisticexpialidocious'),
const Tab(text: 'Longer Than Usual'),
];
final TabController controller = createTabController(
vsync: const TestVSync(),
length: tabs.length,
);
Widget buildTabBar() {
return animationSheet.record(
boilerplate(
textDirection: TextDirection.rtl,
child: Container(
alignment: Alignment.topLeft,
child: TabBar(
indicatorAnimation: TabIndicatorAnimation.elastic,
controller: controller,
tabs: tabs,
),
),
),
);
}
await tester.pumpFrames(buildTabBar(), const Duration(milliseconds: 50));
await tester.tap(find.byType(Tab).at(2));
await tester.pumpFrames(buildTabBar(), const Duration(milliseconds: 500));
await tester.tap(find.byType(Tab).at(3));
await tester.pumpFrames(buildTabBar(), const Duration(milliseconds: 500));
await tester.tap(find.byType(Tab).at(5));
await tester.pumpFrames(buildTabBar(), const Duration(milliseconds: 500));
await tester.tap(find.byType(Tab).at(4));
await tester.pumpFrames(buildTabBar(), const Duration(milliseconds: 500));
await expectLater(
animationSheet.collate(1),
matchesGoldenFile('tab_indicator.elastic_animation.various_size_tabs.rtl.png'),
);
}, skip: isBrowser); // https://github.com/flutter/flutter/issues/56001
testWidgets('Linear Tab animation with various size tabs - LTR', (WidgetTester tester) async {
final AnimationSheetBuilder animationSheet = AnimationSheetBuilder(
frameSize: const Size(800, 100),
);
addTearDown(animationSheet.dispose);
final List<Widget> tabs = <Widget>[
const Tab(text: 'Short'),
const Tab(text: 'A Bit Longer Text'),
const Tab(text: 'An Extremely Long Tab Label That Overflows'),
const Tab(text: 'Tiny'),
const Tab(text: 'Moderate Length'),
const Tab(text: 'Just Right'),
const Tab(text: 'Supercalifragilisticexpialidocious'),
const Tab(text: 'Longer Than Usual'),
];
final TabController controller = createTabController(
vsync: const TestVSync(),
length: tabs.length,
);
Widget buildTabBar() {
return animationSheet.record(
boilerplate(
child: Container(
alignment: Alignment.topLeft,
child: TabBar(
indicatorAnimation: TabIndicatorAnimation.linear,
controller: controller,
tabs: tabs,
),
),
),
);
}
await tester.pumpFrames(buildTabBar(), const Duration(milliseconds: 50));
await tester.tap(find.byType(Tab).at(2));
await tester.pumpFrames(buildTabBar(), const Duration(milliseconds: 500));
await tester.tap(find.byType(Tab).at(3));
await tester.pumpFrames(buildTabBar(), const Duration(milliseconds: 500));
await tester.tap(find.byType(Tab).at(5));
await tester.pumpFrames(buildTabBar(), const Duration(milliseconds: 500));
await tester.tap(find.byType(Tab).at(4));
await tester.pumpFrames(buildTabBar(), const Duration(milliseconds: 500));
await expectLater(
animationSheet.collate(1),
matchesGoldenFile('tab_indicator.linear_animation.various_size_tabs.ltr.png'),
);
}, skip: isBrowser); // https://github.com/flutter/flutter/issues/56001
testWidgets('Linear Tab animation with various size tabs - RTL', (WidgetTester tester) async {
final AnimationSheetBuilder animationSheet = AnimationSheetBuilder(
frameSize: const Size(800, 100),
);
addTearDown(animationSheet.dispose);
final List<Widget> tabs = <Widget>[
const Tab(text: 'Short'),
const Tab(text: 'A Bit Longer Text'),
const Tab(text: 'An Extremely Long Tab Label That Overflows'),
const Tab(text: 'Tiny'),
const Tab(text: 'Moderate Length'),
const Tab(text: 'Just Right'),
const Tab(text: 'Supercalifragilisticexpialidocious'),
const Tab(text: 'Longer Than Usual'),
];
final TabController controller = createTabController(
vsync: const TestVSync(),
length: tabs.length,
);
Widget buildTabBar() {
return animationSheet.record(
boilerplate(
textDirection: TextDirection.rtl,
child: Container(
alignment: Alignment.topLeft,
child: TabBar(
indicatorAnimation: TabIndicatorAnimation.linear,
controller: controller,
tabs: tabs,
),
),
),
);
}
await tester.pumpFrames(buildTabBar(), const Duration(milliseconds: 50));
await tester.tap(find.byType(Tab).at(2));
await tester.pumpFrames(buildTabBar(), const Duration(milliseconds: 500));
await tester.tap(find.byType(Tab).at(3));
await tester.pumpFrames(buildTabBar(), const Duration(milliseconds: 500));
await tester.tap(find.byType(Tab).at(5));
await tester.pumpFrames(buildTabBar(), const Duration(milliseconds: 500));
await tester.tap(find.byType(Tab).at(4));
await tester.pumpFrames(buildTabBar(), const Duration(milliseconds: 500));
await expectLater(
animationSheet.collate(1),
matchesGoldenFile('tab_indicator.linear_animation.various_size_tabs.rtl.png'),
);
}, skip: isBrowser); // https://github.com/flutter/flutter/issues/56001
testWidgets('Elastic Tab animation with various size tabs in a scrollable tab bar - LTR', (
WidgetTester tester,
) async {
@ -8054,14 +7872,18 @@ void main() {
addTearDown(animationSheet.dispose);
final List<Widget> tabs = <Widget>[
const Tab(text: 'Short'),
const Tab(text: 'A Bit Longer Text'),
const Tab(text: 'An Extremely Long Tab Label That Overflows'),
const Tab(text: 'Tiny'),
const Tab(text: 'Moderate Length'),
const Tab(text: 'Just Right'),
const Tab(text: 'Supercalifragilisticexpialidocious'),
const Tab(text: 'Longer Than Usual'),
const Tab(text: 'Medium'),
const Tab(text: 'Extremely Very Long Label'),
const Tab(text: 'C'),
const Tab(text: 'Medium'),
const Tab(text: 'Extremely Very Long Label'),
const Tab(text: 'C'),
const Tab(text: 'Medium'),
const Tab(text: 'Extremely Very Long Label'),
const Tab(text: 'C'),
const Tab(text: 'Medium'),
const Tab(text: 'Extremely Very Long Label'),
const Tab(text: 'C'),
];
final TabController controller = createTabController(
@ -8069,13 +7891,60 @@ void main() {
length: tabs.length,
);
Widget buildTabBar() {
Widget target() {
return animationSheet.record(
boilerplate(
child: Container(
alignment: Alignment.topLeft,
child: TabBar(
indicatorAnimation: TabIndicatorAnimation.elastic,
isScrollable: true,
controller: controller,
tabs: tabs,
),
),
),
);
}
await tester.pumpFrames(target(), const Duration(milliseconds: 50));
controller.animateTo(tabs.length - 1);
await tester.pumpFrames(target(), const Duration(milliseconds: 500));
controller.animateTo(0);
await tester.pumpFrames(target(), const Duration(milliseconds: 500));
await expectLater(
animationSheet.collate(1),
matchesGoldenFile('tab_indicator.elastic_animation.various_size_tabs.scrollable.ltr.png'),
);
}, skip: isBrowser); // https://github.com/flutter/flutter/issues/56001
testWidgets('Elastic Tab animation with various size tabs - RTL', (WidgetTester tester) async {
final AnimationSheetBuilder animationSheet = AnimationSheetBuilder(
frameSize: const Size(800, 100),
);
addTearDown(animationSheet.dispose);
final List<Widget> tabs = <Widget>[
const Tab(text: 'Medium'),
const Tab(text: 'Extremely Very Long Label'),
const Tab(text: 'C'),
];
final TabController controller = createTabController(
vsync: const TestVSync(),
length: tabs.length,
);
Widget target() {
return animationSheet.record(
boilerplate(
textDirection: TextDirection.rtl,
child: Container(
alignment: Alignment.topLeft,
child: TabBar(
indicatorAnimation: TabIndicatorAnimation.elastic,
controller: controller,
tabs: tabs,
@ -8085,26 +7954,17 @@ void main() {
);
}
await tester.pumpFrames(buildTabBar(), const Duration(milliseconds: 50));
await tester.pumpFrames(target(), const Duration(milliseconds: 50));
await tester.tap(find.byType(Tab).at(2));
await tester.pumpFrames(buildTabBar(), const Duration(milliseconds: 500));
await tester.tap(find.text('Extremely Very Long Label'));
await tester.pumpFrames(target(), const Duration(milliseconds: 500));
await tester.tap(find.byType(Tab).at(1));
await tester.pumpFrames(buildTabBar(), const Duration(milliseconds: 500));
controller.animateTo(tabs.length - 1);
await tester.pumpFrames(buildTabBar(), const Duration(milliseconds: 500));
controller.animateTo(0);
await tester.pumpFrames(buildTabBar(), const Duration(milliseconds: 500));
await tester.tap(find.byType(Tab).at(1));
await tester.pumpFrames(buildTabBar(), const Duration(milliseconds: 500));
await tester.tap(find.text('C'));
await tester.pumpFrames(target(), const Duration(milliseconds: 500));
await expectLater(
animationSheet.collate(1),
matchesGoldenFile('tab_indicator.elastic_animation.various_size_tabs.scrollable.ltr.png'),
matchesGoldenFile('tab_indicator.elastic_animation.various_size_tabs.rtl.png'),
);
}, skip: isBrowser); // https://github.com/flutter/flutter/issues/56001
@ -8117,14 +7977,18 @@ void main() {
addTearDown(animationSheet.dispose);
final List<Widget> tabs = <Widget>[
const Tab(text: 'Short'),
const Tab(text: 'A Bit Longer Text'),
const Tab(text: 'An Extremely Long Tab Label That Overflows'),
const Tab(text: 'Tiny'),
const Tab(text: 'Moderate Length'),
const Tab(text: 'Just Right'),
const Tab(text: 'Supercalifragilisticexpialidocious'),
const Tab(text: 'Longer Than Usual'),
const Tab(text: 'Medium'),
const Tab(text: 'Extremely Very Long Label'),
const Tab(text: 'C'),
const Tab(text: 'Medium'),
const Tab(text: 'Extremely Very Long Label'),
const Tab(text: 'C'),
const Tab(text: 'Medium'),
const Tab(text: 'Extremely Very Long Label'),
const Tab(text: 'C'),
const Tab(text: 'Medium'),
const Tab(text: 'Extremely Very Long Label'),
const Tab(text: 'C'),
];
final TabController controller = createTabController(
@ -8132,15 +7996,15 @@ void main() {
length: tabs.length,
);
Widget buildTabBar() {
Widget target() {
return animationSheet.record(
boilerplate(
textDirection: TextDirection.rtl,
child: Container(
alignment: Alignment.topLeft,
child: TabBar(
isScrollable: true,
indicatorAnimation: TabIndicatorAnimation.elastic,
isScrollable: true,
controller: controller,
tabs: tabs,
),
@ -8149,22 +8013,13 @@ void main() {
);
}
await tester.pumpFrames(buildTabBar(), const Duration(milliseconds: 50));
await tester.tap(find.byType(Tab).at(2));
await tester.pumpFrames(buildTabBar(), const Duration(milliseconds: 500));
await tester.tap(find.byType(Tab).at(1));
await tester.pumpFrames(buildTabBar(), const Duration(milliseconds: 500));
await tester.pumpFrames(target(), const Duration(milliseconds: 50));
controller.animateTo(tabs.length - 1);
await tester.pumpFrames(buildTabBar(), const Duration(milliseconds: 500));
await tester.pumpFrames(target(), const Duration(milliseconds: 500));
controller.animateTo(0);
await tester.pumpFrames(buildTabBar(), const Duration(milliseconds: 500));
await tester.tap(find.byType(Tab).at(1));
await tester.pumpFrames(buildTabBar(), const Duration(milliseconds: 500));
await tester.pumpFrames(target(), const Duration(milliseconds: 500));
await expectLater(
animationSheet.collate(1),
@ -8172,133 +8027,6 @@ void main() {
);
}, skip: isBrowser); // https://github.com/flutter/flutter/issues/56001
testWidgets('Linear Tab animation with various size tabs in a scrollable tab bar - LTR', (
WidgetTester tester,
) async {
final AnimationSheetBuilder animationSheet = AnimationSheetBuilder(
frameSize: const Size(800, 100),
);
addTearDown(animationSheet.dispose);
final List<Widget> tabs = <Widget>[
const Tab(text: 'Short'),
const Tab(text: 'A Bit Longer Text'),
const Tab(text: 'An Extremely Long Tab Label That Overflows'),
const Tab(text: 'Tiny'),
const Tab(text: 'Moderate Length'),
const Tab(text: 'Just Right'),
const Tab(text: 'Supercalifragilisticexpialidocious'),
const Tab(text: 'Longer Than Usual'),
];
final TabController controller = createTabController(
vsync: const TestVSync(),
length: tabs.length,
);
Widget buildTabBar() {
return animationSheet.record(
boilerplate(
child: Container(
alignment: Alignment.topLeft,
child: TabBar(
isScrollable: true,
indicatorAnimation: TabIndicatorAnimation.linear,
controller: controller,
tabs: tabs,
),
),
),
);
}
await tester.pumpFrames(buildTabBar(), const Duration(milliseconds: 50));
await tester.tap(find.byType(Tab).at(2));
await tester.pumpFrames(buildTabBar(), const Duration(milliseconds: 500));
await tester.tap(find.byType(Tab).at(1));
await tester.pumpFrames(buildTabBar(), const Duration(milliseconds: 500));
controller.animateTo(tabs.length - 1);
await tester.pumpFrames(buildTabBar(), const Duration(milliseconds: 500));
controller.animateTo(0);
await tester.pumpFrames(buildTabBar(), const Duration(milliseconds: 500));
await tester.tap(find.byType(Tab).at(1));
await tester.pumpFrames(buildTabBar(), const Duration(milliseconds: 500));
await expectLater(
animationSheet.collate(1),
matchesGoldenFile('tab_indicator.linear_animation.various_size_tabs.scrollable.ltr.png'),
);
}, skip: isBrowser); // https://github.com/flutter/flutter/issues/56001
testWidgets('Linear Tab animation with various size tabs in a scrollable tab bar - RTL', (
WidgetTester tester,
) async {
final AnimationSheetBuilder animationSheet = AnimationSheetBuilder(
frameSize: const Size(800, 100),
);
addTearDown(animationSheet.dispose);
final List<Widget> tabs = <Widget>[
const Tab(text: 'Short'),
const Tab(text: 'A Bit Longer Text'),
const Tab(text: 'An Extremely Long Tab Label That Overflows'),
const Tab(text: 'Tiny'),
const Tab(text: 'Moderate Length'),
const Tab(text: 'Just Right'),
const Tab(text: 'Supercalifragilisticexpialidocious'),
const Tab(text: 'Longer Than Usual'),
];
final TabController controller = createTabController(
vsync: const TestVSync(),
length: tabs.length,
);
Widget buildTabBar() {
return animationSheet.record(
boilerplate(
textDirection: TextDirection.rtl,
child: Container(
alignment: Alignment.topLeft,
child: TabBar(
isScrollable: true,
indicatorAnimation: TabIndicatorAnimation.linear,
controller: controller,
tabs: tabs,
),
),
),
);
}
await tester.pumpFrames(buildTabBar(), const Duration(milliseconds: 50));
await tester.tap(find.byType(Tab).at(2));
await tester.pumpFrames(buildTabBar(), const Duration(milliseconds: 500));
await tester.tap(find.byType(Tab).at(1));
await tester.pumpFrames(buildTabBar(), const Duration(milliseconds: 500));
controller.animateTo(tabs.length - 1);
await tester.pumpFrames(buildTabBar(), const Duration(milliseconds: 500));
controller.animateTo(0);
await tester.pumpFrames(buildTabBar(), const Duration(milliseconds: 500));
await tester.tap(find.byType(Tab).at(1));
await tester.pumpFrames(buildTabBar(), const Duration(milliseconds: 500));
await expectLater(
animationSheet.collate(1),
matchesGoldenFile('tab_indicator.linear_animation.various_size_tabs.scrollable.rtl.png'),
);
}, skip: isBrowser); // https://github.com/flutter/flutter/issues/56001
// Regression test for https://github.com/flutter/flutter/issues/160631
testWidgets('Elastic Tab animation when skipping tabs', (WidgetTester tester) async {
final AnimationSheetBuilder animationSheet = AnimationSheetBuilder(
@ -8306,14 +8034,19 @@ void main() {
);
addTearDown(animationSheet.dispose);
final List<Widget> tabs = List<Widget>.generate(10, (int index) => Tab(text: 'Tab $index'));
final List<Widget> tabs = <Widget>[
const Tab(text: 'Medium'),
const Tab(text: 'Extremely Very Long Label'),
const Tab(text: 'C'),
const Tab(text: 'Short'),
];
final TabController controller = createTabController(
vsync: const TestVSync(),
length: tabs.length,
);
Widget buildTabBar() {
Widget target() {
return animationSheet.record(
boilerplate(
child: Container(
@ -8328,72 +8061,17 @@ void main() {
);
}
await tester.pumpFrames(buildTabBar(), const Duration(milliseconds: 50));
await tester.pumpFrames(target(), const Duration(milliseconds: 50));
await tester.tap(find.text('Tab 2'));
await tester.pumpFrames(buildTabBar(), const Duration(milliseconds: 500));
await tester.tap(find.text('C'));
await tester.pumpFrames(target(), const Duration(milliseconds: 500));
await tester.tap(find.text('Tab 1'));
await tester.pumpFrames(buildTabBar(), const Duration(milliseconds: 500));
await tester.tap(find.text('Tab 4'));
await tester.pumpFrames(buildTabBar(), const Duration(milliseconds: 500));
await tester.tap(find.text('Tab 5'));
await tester.pumpFrames(buildTabBar(), const Duration(milliseconds: 500));
await tester.tap(find.text('Medium'));
await tester.pumpFrames(target(), const Duration(milliseconds: 500));
await expectLater(
animationSheet.collate(1),
matchesGoldenFile('tab_indicator.elastic_animation.skipping_tabs.png'),
);
}, skip: isBrowser); // https://github.com/flutter/flutter/issues/56001
// Regression test for https://github.com/flutter/flutter/issues/162098
testWidgets('Linear Tab animation when skipping tab', (WidgetTester tester) async {
final AnimationSheetBuilder animationSheet = AnimationSheetBuilder(
frameSize: const Size(800, 100),
);
addTearDown(animationSheet.dispose);
final List<Widget> tabs = List<Widget>.generate(10, (int index) => Tab(text: 'Tab $index'));
final TabController controller = createTabController(
vsync: const TestVSync(),
length: tabs.length,
);
Widget buildTabBar() {
return animationSheet.record(
boilerplate(
child: Container(
alignment: Alignment.topLeft,
child: TabBar(
indicatorAnimation: TabIndicatorAnimation.linear,
controller: controller,
tabs: tabs,
),
),
),
);
}
await tester.pumpFrames(buildTabBar(), const Duration(milliseconds: 50));
await tester.tap(find.text('Tab 2'));
await tester.pumpFrames(buildTabBar(), const Duration(milliseconds: 500));
await tester.tap(find.text('Tab 1'));
await tester.pumpFrames(buildTabBar(), const Duration(milliseconds: 500));
await tester.tap(find.text('Tab 4'));
await tester.pumpFrames(buildTabBar(), const Duration(milliseconds: 500));
await tester.tap(find.text('Tab 5'));
await tester.pumpFrames(buildTabBar(), const Duration(milliseconds: 500));
await expectLater(
animationSheet.collate(1),
matchesGoldenFile('tab_indicator.linear_animation.skipping_tabs.png'),
);
}, skip: isBrowser); // https://github.com/flutter/flutter/issues/56001
}