From 2c1e4b16151d33a6ef290fb57e52747110f68dcb Mon Sep 17 00:00:00 2001
From: "auto-submit[bot]" <98614782+auto-submit[bot]@users.noreply.github.com>
Date: Wed, 29 Jan 2025 15:10:05 -0500
Subject: [PATCH] Reverts "Fix `Tab` linear and elastic animation blink
(#162315)" (#162387)
Reverts: flutter/flutter#162315
Initiated by: TahaTesser
Reason for reverting: Red tree due to unapproved golden images.
Original PR Author: TahaTesser
Reviewed By: {justinmc}
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
expand to view the code sample
```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 tabs = [
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: [
for (int i = 0; i < tabs.length; i++) const Icon(Icons.directions_car),
],
),
),
),
);
}
}
```
### 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.
## 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].
[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
Co-authored-by: auto-submit[bot]
---
packages/flutter/lib/src/material/tabs.dart | 64 +--
packages/flutter/test/material/tabs_test.dart | 538 ++++--------------
2 files changed, 134 insertions(+), 468 deletions(-)
diff --git a/packages/flutter/lib/src/material/tabs.dart b/packages/flutter/lib/src/material/tabs.dart
index 5b26b64445..e537af3eab 100644
--- a/packages/flutter/lib/src/material/tabs.dart
+++ b/packages/flutter/lib/src/material/tabs.dart
@@ -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
diff --git a/packages/flutter/test/material/tabs_test.dart b/packages/flutter/test/material/tabs_test.dart
index 23feb4303c..e49bb6471c 100644
--- a/packages/flutter/test/material/tabs_test.dart
+++ b/packages/flutter/test/material/tabs_test.dart
@@ -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 tabs = [
- 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 tabs = [
- 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 tabs = [
- 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 tabs = [
- 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 tabs = [
- 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 tabs = [
+ 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 tabs = [
- 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 tabs = [
- 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 tabs = [
- 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 tabs = List.generate(10, (int index) => Tab(text: 'Tab $index'));
+ final List tabs = [
+ 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 tabs = List.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
}