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. Screenshot 2025-01-28 at 17 27 50 ## 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 }