diff --git a/packages/flutter/lib/src/animation/animation_controller.dart b/packages/flutter/lib/src/animation/animation_controller.dart index 44e6d11161e..c564ed62cad 100644 --- a/packages/flutter/lib/src/animation/animation_controller.dart +++ b/packages/flutter/lib/src/animation/animation_controller.dart @@ -487,6 +487,23 @@ class AnimationController extends Animation return _animateToInternal(target, duration: duration, curve: curve); } + /// Drives the animation from its current value to target. + /// + /// Returns a [TickerFuture] that completes when the animation is complete. + /// + /// The most recently returned [TickerFuture], if any, is marked as having been + /// canceled, meaning the future never completes and its [TickerFuture.orCancel] + /// derivative future completes with a [TickerCanceled] error. + /// + /// During the animation, [status] is reported as [AnimationStatus.reverse] + /// regardless of whether `target` < [value] or not. At the end of the + /// animation, when `target` is reached, [status] is reported as + /// [AnimationStatus.dismissed]. + TickerFuture animateBack(double target, { Duration duration, Curve curve = Curves.linear }) { + _direction = _AnimationDirection.reverse; + return _animateToInternal(target, duration: duration, curve: curve); + } + TickerFuture _animateToInternal(double target, { Duration duration, Curve curve = Curves.linear, AnimationBehavior animationBehavior }) { final AnimationBehavior behavior = animationBehavior ?? this.animationBehavior; double scale = 1.0; diff --git a/packages/flutter/lib/src/animation/curves.dart b/packages/flutter/lib/src/animation/curves.dart index c5c101b9e8e..237833b0422 100644 --- a/packages/flutter/lib/src/animation/curves.dart +++ b/packages/flutter/lib/src/animation/curves.dart @@ -316,7 +316,6 @@ class _DecelerateCurve extends Curve { } } - // BOUNCE CURVES double _bounce(double t) { @@ -536,6 +535,15 @@ class Curves { /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_decelerate.mp4} static const Curve decelerate = _DecelerateCurve._(); + /// A curve that is very steep and linear at the beginning, but quickly flattens out + /// and very slowly eases in. + /// + /// By default is the curve used to animate pages on iOS back to their original + /// position if a swipe gesture is ended midway through a swipe. + /// + /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/fast_linear_to_slow_ease_in.mp4} + static const Cubic fastLinearToSlowEaseIn = Cubic(0.18, 1.0, 0.04, 1.0); + /// A cubic animation curve that speeds up quickly and ends slowly. /// /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease.mp4} @@ -546,6 +554,13 @@ class Curves { /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in.mp4} static const Cubic easeIn = Cubic(0.42, 0.0, 1.0, 1.0); + /// A cubic animation curve that starts starts slowly and ends linearly. + /// + /// The symmetric animation to [linearToEaseOut]. + /// + /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_to_linear.mp4} + static const Cubic easeInToLinear = Cubic(0.67, 0.03, 0.65, 0.09); + /// A cubic animation curve that starts slowly and ends quickly. This is /// similar to [Curves.easeIn], but with sinusoidal easing for a slightly less /// abrupt beginning and end. Nonetheless, the result is quite gentle and is @@ -640,6 +655,13 @@ class Curves { /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_out.mp4} static const Cubic easeOut = Cubic(0.0, 0.0, 0.58, 1.0); + /// A cubic animation curve that starts linearly and ends slowly. + /// + /// A symmetric animation to [easeInToLinear]. + /// + /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/linear_to_ease_out.mp4} + static const Cubic linearToEaseOut = Cubic(0.35, 0.91, 0.33, 0.97); + /// A cubic animation curve that starts quickly and ends slowly. This is /// similar to [Curves.easeOut], but with sinusoidal easing for a slightly /// less abrupt beginning and end. Nonetheless, the result is quite gentle and diff --git a/packages/flutter/lib/src/cupertino/route.dart b/packages/flutter/lib/src/cupertino/route.dart index b3bd2fddd09..d388e037112 100644 --- a/packages/flutter/lib/src/cupertino/route.dart +++ b/packages/flutter/lib/src/cupertino/route.dart @@ -4,15 +4,25 @@ import 'dart:async'; import 'dart:math'; +import 'dart:ui' show lerpDouble; import 'package:flutter/foundation.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/widgets.dart'; +import 'package:flutter/animation.dart' show Curves; const double _kBackGestureWidth = 20.0; const double _kMinFlingVelocity = 1.0; // Screen widths per second. +// An eyeballed value for the maximum time it takes for a page to animate forward +// if the user releases a page mid swipe. +const int _kMaxDroppedSwipePageForwardAnimationTime = 800; // Milliseconds. + +// The maximum time for a page to get reset to it's original position if the +// user releases a page mid swipe. +const int _kMaxPageBackAnimationTime = 300; // Milliseconds. + // Barrier color for a Cupertino modal barrier. const Color _kModalBarrierColor = Color(0x6604040F); @@ -152,7 +162,8 @@ class CupertinoPageRoute extends PageRoute { final bool maintainState; @override - Duration get transitionDuration => const Duration(milliseconds: 350); + // A relatively rigorous eyeball estimation. + Duration get transitionDuration => const Duration(milliseconds: 400); @override Color get barrierColor => null; @@ -346,20 +357,26 @@ class CupertinoPageTransition extends StatelessWidget { @required bool linearTransition, }) : assert(linearTransition != null), _primaryPositionAnimation = (linearTransition ? primaryRouteAnimation : + // The curves below have been rigorously derived from plots of native + // iOS animation frames. Specifically, a video was taken of a page + // transition animation and the distance in each frame that the page + // moved was measured. A best fit bezier curve was the fitted to the + // point set, which is linearToEaseIn. Conversely, easeInToLinear is the + // reflection over the origin of linearToEaseIn. CurvedAnimation( parent: primaryRouteAnimation, - curve: Curves.easeOut, - reverseCurve: Curves.easeIn, + curve: Curves.linearToEaseOut, + reverseCurve: Curves.easeInToLinear, ) ).drive(_kRightMiddleTween), _secondaryPositionAnimation = CurvedAnimation( parent: secondaryRouteAnimation, - curve: Curves.easeOut, - reverseCurve: Curves.easeIn, + curve: Curves.linearToEaseOut, + reverseCurve: Curves.easeInToLinear, ).drive(_kMiddleLeftTween), _primaryShadowAnimation = CurvedAnimation( parent: primaryRouteAnimation, - curve: Curves.easeOut, + curve: Curves.linearToEaseOut, ).drive(_kGradientShadowTween), super(key: key); @@ -593,13 +610,32 @@ class _CupertinoBackGestureController { // Fling in the appropriate direction. // AnimationController.fling is guaranteed to // take at least one frame. - if (velocity.abs() >= _kMinFlingVelocity) { - controller.fling(velocity: -velocity); - } else if (controller.value <= 0.5) { - controller.fling(velocity: -1.0); + // + // This curve has been determined through rigorously eyeballing native iOS + // animations. + const Curve animationCurve = Curves.fastLinearToSlowEaseIn; + bool animateForward; + + // If the user releases the page before mid screen with sufficient velocity, + // or after mid screen, we should animate the page out. Otherwise, the page + // should be animated back in. + if (velocity.abs() >= _kMinFlingVelocity) + animateForward = velocity > 0 ? false : true; + else + animateForward = controller.value > 0.5 ? true : false; + + if (animateForward) { + // The closer the panel is to dismissing, the shorter the animation is. + // We want to cap the animation time, but we want to use a linear curve + // to determine it. + final int droppedPageForwardAnimationTime = min(lerpDouble(_kMaxDroppedSwipePageForwardAnimationTime, 0, controller.value).floor(), + _kMaxPageBackAnimationTime); + controller.animateTo(1.0, duration: Duration(milliseconds: droppedPageForwardAnimationTime), curve: animationCurve); } else { - controller.fling(velocity: 1.0); + final int droppedPageBackAnimationTime = lerpDouble(0, _kMaxDroppedSwipePageForwardAnimationTime, controller.value).floor(); + controller.animateBack(0.0, duration: Duration(milliseconds: droppedPageBackAnimationTime), curve: animationCurve); } + assert(controller.isAnimating); assert(controller.status != AnimationStatus.completed); assert(controller.status != AnimationStatus.dismissed); diff --git a/packages/flutter/test/cupertino/nav_bar_transition_test.dart b/packages/flutter/test/cupertino/nav_bar_transition_test.dart index c7b6889240e..246b4b88c90 100644 --- a/packages/flutter/test/cupertino/nav_bar_transition_test.dart +++ b/packages/flutter/test/cupertino/nav_bar_transition_test.dart @@ -139,11 +139,11 @@ void main() { // place. expect( tester.getTopLeft(flying(tester, find.text('Page 1')).first), - const Offset(332.0129337310791, 13.5), + const Offset(337.0234375, 13.5), ); expect( tester.getTopLeft(flying(tester, find.text('Page 1')).last), - const Offset(332.0129337310791, 13.5), + const Offset(337.0234375, 13.5), ); }); @@ -158,15 +158,14 @@ void main() { await tester.pump(const Duration(milliseconds: 50)); expect(flying(tester, find.text('Page 1')), findsNWidgets(2)); - // Same as LTR but more to the right now. expect( tester.getTopLeft(flying(tester, find.text('Page 1')).first), - const Offset(367.9870662689209, 13.5), + const Offset(362.9765625, 13.5), ); expect( tester.getTopLeft(flying(tester, find.text('Page 1')).last), - const Offset(367.9870662689209, 13.5), + const Offset(362.9765625, 13.5), ); }); @@ -180,19 +179,19 @@ void main() { // The transition's stack is ordered. The bottom middle is inserted first. final RenderParagraph bottomMiddle = tester.renderObject(flying(tester, find.text('Page 1')).first); - expect(bottomMiddle.text.style.color, const Color(0xFF00070F)); + expect(bottomMiddle.text.style.color, const Color(0xff00050a)); expect(bottomMiddle.text.style.fontWeight, FontWeight.w600); expect(bottomMiddle.text.style.fontFamily, '.SF Pro Text'); expect(bottomMiddle.text.style.letterSpacing, -0.41); checkOpacity( - tester, flying(tester, find.text('Page 1')).first, 0.8609542846679688); + tester, flying(tester, find.text('Page 1')).first, 0.9004602432250977); // The top back label is styled exactly the same way. But the opacity tweens // are flipped. final RenderParagraph topBackLabel = tester.renderObject(flying(tester, find.text('Page 1')).last); - expect(topBackLabel.text.style.color, const Color(0xFF00070F)); + expect(topBackLabel.text.style.color, const Color(0xff00050a)); expect(topBackLabel.text.style.fontWeight, FontWeight.w600); expect(topBackLabel.text.style.fontFamily, '.SF Pro Text'); expect(topBackLabel.text.style.letterSpacing, -0.41); @@ -201,20 +200,20 @@ void main() { // Move animation further a bit. await tester.pump(const Duration(milliseconds: 200)); - expect(bottomMiddle.text.style.color, const Color(0xFF0073F0)); + expect(bottomMiddle.text.style.color, const Color(0xff006de4)); expect(bottomMiddle.text.style.fontWeight, FontWeight.w400); expect(bottomMiddle.text.style.fontFamily, '.SF Pro Text'); expect(bottomMiddle.text.style.letterSpacing, -0.41); checkOpacity(tester, flying(tester, find.text('Page 1')).first, 0.0); - expect(topBackLabel.text.style.color, const Color(0xFF0073F0)); + expect(topBackLabel.text.style.color, const Color(0xff006de4)); expect(topBackLabel.text.style.fontWeight, FontWeight.w400); expect(topBackLabel.text.style.fontFamily, '.SF Pro Text'); expect(topBackLabel.text.style.letterSpacing, -0.41); checkOpacity( - tester, flying(tester, find.text('Page 1')).last, 0.8733493089675903); + tester, flying(tester, find.text('Page 1')).last, 0.7630139589309692); }); testWidgets('Font transitions respect themes', @@ -231,18 +230,18 @@ void main() { // The transition's stack is ordered. The bottom middle is inserted first. final RenderParagraph bottomMiddle = tester.renderObject(flying(tester, find.text('Page 1')).first); - expect(bottomMiddle.text.style.color, const Color(0xFFFFF8EF)); + expect(bottomMiddle.text.style.color, const Color(0xfffffaf4)); expect(bottomMiddle.text.style.fontWeight, FontWeight.w600); expect(bottomMiddle.text.style.fontFamily, '.SF Pro Text'); expect(bottomMiddle.text.style.letterSpacing, -0.41); - checkOpacity(tester, flying(tester, find.text('Page 1')).first, 0.8609542846679688); + checkOpacity(tester, flying(tester, find.text('Page 1')).first, 0.9004602432250977); // The top back label is styled exactly the same way. But the opacity tweens // are flipped. final RenderParagraph topBackLabel = tester.renderObject(flying(tester, find.text('Page 1')).last); - expect(topBackLabel.text.style.color, const Color(0xFFFFF8EF)); + expect(topBackLabel.text.style.color, const Color(0xfffffaf4)); expect(topBackLabel.text.style.fontWeight, FontWeight.w600); expect(topBackLabel.text.style.fontFamily, '.SF Pro Text'); expect(topBackLabel.text.style.letterSpacing, -0.41); @@ -251,19 +250,19 @@ void main() { // Move animation further a bit. await tester.pump(const Duration(milliseconds: 200)); - expect(bottomMiddle.text.style.color, const Color(0xFFFF9A0E)); + expect(bottomMiddle.text.style.color, const Color(0xffffa01a)); expect(bottomMiddle.text.style.fontWeight, FontWeight.w400); expect(bottomMiddle.text.style.fontFamily, '.SF Pro Text'); expect(bottomMiddle.text.style.letterSpacing, -0.41); checkOpacity(tester, flying(tester, find.text('Page 1')).first, 0.0); - expect(topBackLabel.text.style.color, const Color(0xFFFF9A0E)); + expect(topBackLabel.text.style.color, const Color(0xffffa01a)); expect(topBackLabel.text.style.fontWeight, FontWeight.w400); expect(topBackLabel.text.style.fontFamily, '.SF Pro Text'); expect(topBackLabel.text.style.letterSpacing, -0.41); - checkOpacity(tester, flying(tester, find.text('Page 1')).last, 0.8733493089675903); + checkOpacity(tester, flying(tester, find.text('Page 1')).last, 0.7630139589309692); }); testWidgets('Fullscreen dialogs do not create heroes', @@ -336,20 +335,20 @@ void main() { // The transition's stack is ordered. The bottom middle is inserted first. final RenderParagraph bottomMiddle = tester.renderObject(flying(tester, find.text('Page 1')).first); - expect(bottomMiddle.text.style.color, const Color(0xFF00070F)); + expect(bottomMiddle.text.style.color, const Color(0xff00050a)); expect( tester.getTopLeft(flying(tester, find.text('Page 1')).first), - const Offset(332.0129337310791, 13.5), + const Offset(337.0234375, 13.5), ); // The top back label is styled exactly the same way. But the opacity tweens // are flipped. final RenderParagraph topBackLabel = tester.renderObject(flying(tester, find.text('Page 1')).last); - expect(topBackLabel.text.style.color, const Color(0xFF00070F)); + expect(topBackLabel.text.style.color, const Color(0xff00050a)); expect( tester.getTopLeft(flying(tester, find.text('Page 1')).last), - const Offset(332.0129337310791, 13.5), + const Offset(337.0234375, 13.5), ); } @@ -382,20 +381,20 @@ void main() { // The transition's stack is ordered. The bottom middle is inserted first. final RenderParagraph bottomMiddle = tester.renderObject(flying(tester, find.text('Page 1')).first); - expect(bottomMiddle.text.style.color, const Color(0xFF00070F)); + expect(bottomMiddle.text.style.color, const Color(0xff00050a)); expect( tester.getTopLeft(flying(tester, find.text('Page 1')).first), - const Offset(367.9870662689209, 13.5), + const Offset(362.9765625, 13.5), ); // The top back label is styled exactly the same way. But the opacity tweens // are flipped. final RenderParagraph topBackLabel = tester.renderObject(flying(tester, find.text('Page 1')).last); - expect(topBackLabel.text.style.color, const Color(0xFF00070F)); + expect(topBackLabel.text.style.color, const Color(0xff00050a)); expect( tester.getTopLeft(flying(tester, find.text('Page 1')).last), - const Offset(367.9870662689209, 13.5), + const Offset(362.9765625, 13.5), ); } @@ -515,19 +514,19 @@ void main() { ); await tester.pump(const Duration(milliseconds: 50)); - checkBackgroundBoxHeight(tester, 47.097110748291016); + checkBackgroundBoxHeight(tester, 46.234375); await tester.pump(const Duration(milliseconds: 50)); - checkBackgroundBoxHeight(tester, 61.0267448425293); + checkBackgroundBoxHeight(tester, 56.3232741355896); await tester.pump(const Duration(milliseconds: 50)); - checkBackgroundBoxHeight(tester, 78.68475294113159); + checkBackgroundBoxHeight(tester, 73.04067611694336); await tester.pump(const Duration(milliseconds: 50)); - checkBackgroundBoxHeight(tester, 88.32722091674805); + checkBackgroundBoxHeight(tester, 84.33018499612808); await tester.pump(const Duration(milliseconds: 50)); - checkBackgroundBoxHeight(tester, 93.13018447160721); + checkBackgroundBoxHeight(tester, 90.53337162733078); }); testWidgets('Large transition box shrinks to standard nav bar size', @@ -540,19 +539,19 @@ void main() { ); await tester.pump(const Duration(milliseconds: 50)); - checkBackgroundBoxHeight(tester, 92.90288925170898); + checkBackgroundBoxHeight(tester, 93.765625); await tester.pump(const Duration(milliseconds: 50)); - checkBackgroundBoxHeight(tester, 78.9732551574707); + checkBackgroundBoxHeight(tester, 83.6767258644104); await tester.pump(const Duration(milliseconds: 50)); - checkBackgroundBoxHeight(tester, 61.31524705886841); + checkBackgroundBoxHeight(tester, 66.95932388305664); await tester.pump(const Duration(milliseconds: 50)); - checkBackgroundBoxHeight(tester, 51.67277908325195); + checkBackgroundBoxHeight(tester, 55.66981500387192); await tester.pump(const Duration(milliseconds: 50)); - checkBackgroundBoxHeight(tester, 46.86981552839279); + checkBackgroundBoxHeight(tester, 49.46662837266922); }); testWidgets('Hero flight removed at the end of page transition', @@ -614,16 +613,15 @@ void main() { // Only one exists from the top page. The bottom page has no back chevron. findsOneWidget, ); - // Come in from the right and fade in. checkOpacity(tester, backChevron, 0.0); expect( - tester.getTopLeft(backChevron), const Offset(71.94993209838867, 5.0)); + tester.getTopLeft(backChevron), const Offset(73.078125, 5.0)); await tester.pump(const Duration(milliseconds: 150)); - checkOpacity(tester, backChevron, 0.32467134296894073); + checkOpacity(tester, backChevron, 0.09497911669313908); expect( - tester.getTopLeft(backChevron), const Offset(18.033634185791016, 5.0)); + tester.getTopLeft(backChevron), const Offset(23.260527312755585, 5.0)); }); testWidgets('First appearance of back chevron fades in from the left in RTL', @@ -663,14 +661,14 @@ void main() { checkOpacity(tester, backChevron, 0.0); expect( tester.getTopRight(backChevron), - const Offset(694.0500679016113, 5.0), + const Offset(692.921875, 5.0), ); await tester.pump(const Duration(milliseconds: 150)); - checkOpacity(tester, backChevron, 0.32467134296894073); + checkOpacity(tester, backChevron, 0.09497911669313908); expect( tester.getTopRight(backChevron), - const Offset(747.966365814209, 5.0), + const Offset(742.7394726872444, 5.0), ); }); @@ -688,7 +686,7 @@ void main() { findsNWidgets(2), ); - checkOpacity(tester, backChevrons.first, 0.8393326997756958); + checkOpacity(tester, backChevrons.first, 0.8833301812410355); checkOpacity(tester, backChevrons.last, 0.0); // Both overlap at the same place. expect(tester.getTopLeft(backChevrons.first), const Offset(8.0, 5.0)); @@ -696,7 +694,7 @@ void main() { await tester.pump(const Duration(milliseconds: 150)); checkOpacity(tester, backChevrons.first, 0.0); - checkOpacity(tester, backChevrons.last, 0.6276369094848633); + checkOpacity(tester, backChevrons.last, 0.4604858811944723); // Still in the same place. expect(tester.getTopLeft(backChevrons.first), const Offset(8.0, 5.0)); expect(tester.getTopLeft(backChevrons.last), const Offset(8.0, 5.0)); @@ -718,7 +716,7 @@ void main() { // There's just 1 in flight because there's no back label on the top page. expect(flying(tester, find.text('Page 1')), findsOneWidget); - checkOpacity(tester, flying(tester, find.text('Page 1')), 0.8609542846679688); + checkOpacity(tester, flying(tester, find.text('Page 1')), 0.9004602432250977); // The middle widget doesn't move. expect( @@ -746,7 +744,7 @@ void main() { expect(flying(tester, find.text('custom')), findsOneWidget); - checkOpacity(tester, flying(tester, find.text('custom')), 0.7655444294214249); + checkOpacity(tester, flying(tester, find.text('custom')), 0.828093871474266); expect( tester.getTopLeft(flying(tester, find.text('custom'))), const Offset(16.0, 0.0), @@ -772,7 +770,7 @@ void main() { expect(flying(tester, find.text('custom')), findsOneWidget); - checkOpacity(tester, flying(tester, find.text('custom')), 0.8393326997756958); + checkOpacity(tester, flying(tester, find.text('custom')), 0.8833301812410355); expect( tester.getTopLeft(flying(tester, find.text('custom'))), const Offset(684.0, 13.5), @@ -809,17 +807,17 @@ void main() { expect(flying(tester, find.text('Page 1')), findsOneWidget); // Back label fades out faster. - checkOpacity(tester, flying(tester, find.text('Page 1')), 0.5584745407104492); + checkOpacity(tester, flying(tester, find.text('Page 1')), 0.6697911769151688); expect( tester.getTopLeft(flying(tester, find.text('Page 1'))), - const Offset(24.176071166992188, 13.5), + const Offset(30.8125, 13.5), ); await tester.pump(const Duration(milliseconds: 150)); checkOpacity(tester, flying(tester, find.text('Page 1')), 0.0); expect( tester.getTopLeft(flying(tester, find.text('Page 1'))), - const Offset(-292.97862243652344, 13.5), + const Offset(-262.2321922779083, 13.5), ); }); @@ -847,10 +845,10 @@ void main() { expect(flying(tester, find.text('Page 1')), findsOneWidget); // Back label fades out faster. - checkOpacity(tester, flying(tester, find.text('Page 1')), 0.5584745407104492); + checkOpacity(tester, flying(tester, find.text('Page 1')), 0.6697911769151688); expect( tester.getTopRight(flying(tester, find.text('Page 1'))), - const Offset(775.8239288330078, 13.5), + const Offset(769.1875, 13.5), ); await tester.pump(const Duration(milliseconds: 150)); @@ -858,7 +856,7 @@ void main() { expect( tester.getTopRight(flying(tester, find.text('Page 1'))), // >1000. It's now off the screen. - const Offset(1092.9786224365234, 13.5), + const Offset(1062.2321922779083, 13.5), ); }); @@ -877,27 +875,27 @@ void main() { // bottom back label fading in. expect(flying(tester, find.text('Page 1')), findsNWidgets(2)); - checkOpacity(tester, flying(tester, find.text('Page 1')).first, 0.8393326997756958); + checkOpacity(tester, flying(tester, find.text('Page 1')).first, 0.8833301812410355); checkOpacity(tester, flying(tester, find.text('Page 1')).last, 0.0); expect( tester.getTopLeft(flying(tester, find.text('Page 1')).first), - const Offset(17.905914306640625, 51.58156871795654), + const Offset(17.375, 52.39453125), ); expect( tester.getTopLeft(flying(tester, find.text('Page 1')).last), - const Offset(17.905914306640625, 51.58156871795654), + const Offset(17.375, 52.39453125), ); await tester.pump(const Duration(milliseconds: 150)); checkOpacity(tester, flying(tester, find.text('Page 1')).first, 0.0); - checkOpacity(tester, flying(tester, find.text('Page 1')).last, 0.6276369094848633); + checkOpacity(tester, flying(tester, find.text('Page 1')).last, 0.4604858811944723); expect( tester.getTopLeft(flying(tester, find.text('Page 1')).first), - const Offset(43.278289794921875, 19.23011875152588), + const Offset(40.818575382232666, 22.49655644595623), ); expect( tester.getTopLeft(flying(tester, find.text('Page 1')).last), - const Offset(43.278289794921875, 19.23011875152588), + const Offset(40.818575382232666, 22.49655644595623), ); }); @@ -918,27 +916,27 @@ void main() { expect(flying(tester, find.text('Back')), findsOneWidget); checkOpacity(tester, flying(tester, find.text('A title too long to fit')), - 0.8393326997756958); + 0.8833301812410355); checkOpacity(tester, flying(tester, find.text('Back')), 0.0); expect( tester.getTopLeft(flying(tester, find.text('A title too long to fit'))), - const Offset(17.905914306640625, 51.58156871795654), + const Offset(17.375, 52.39453125), ); expect( tester.getTopLeft(flying(tester, find.text('Back'))), - const Offset(17.905914306640625, 51.58156871795654), + const Offset(17.375, 52.39453125), ); await tester.pump(const Duration(milliseconds: 150)); checkOpacity(tester, flying(tester, find.text('A title too long to fit')), 0.0); - checkOpacity(tester, flying(tester, find.text('Back')), 0.6276369094848633); + checkOpacity(tester, flying(tester, find.text('Back')), 0.4604858811944723); expect( tester.getTopLeft(flying(tester, find.text('A title too long to fit'))), - const Offset(43.278289794921875, 19.23011875152588), + const Offset(40.818575382232666, 22.49655644595623), ); expect( tester.getTopLeft(flying(tester, find.text('Back'))), - const Offset(43.278289794921875, 19.23011875152588), + const Offset(40.818575382232666, 22.49655644595623), ); }); @@ -956,30 +954,30 @@ void main() { // The transition's stack is ordered. The bottom large title is inserted first. final RenderParagraph bottomLargeTitle = tester.renderObject(flying(tester, find.text('Page 1')).first); - expect(bottomLargeTitle.text.style.color, const Color(0xFF00070F)); + expect(bottomLargeTitle.text.style.color, const Color(0xff00050a)); expect(bottomLargeTitle.text.style.fontWeight, FontWeight.w700); expect(bottomLargeTitle.text.style.fontFamily, '.SF Pro Display'); - expect(bottomLargeTitle.text.style.letterSpacing, 0.36116094589233394); + expect(bottomLargeTitle.text.style.letterSpacing, 0.374765625); // The top back label is styled exactly the same way. final RenderParagraph topBackLabel = tester.renderObject(flying(tester, find.text('Page 1')).last); - expect(topBackLabel.text.style.color, const Color(0xFF00070F)); + expect(topBackLabel.text.style.color, const Color(0xff00050a)); expect(topBackLabel.text.style.fontWeight, FontWeight.w700); expect(topBackLabel.text.style.fontFamily, '.SF Pro Display'); - expect(topBackLabel.text.style.letterSpacing, 0.36116094589233394); + expect(topBackLabel.text.style.letterSpacing, 0.374765625); // Move animation further a bit. await tester.pump(const Duration(milliseconds: 200)); - expect(bottomLargeTitle.text.style.color, const Color(0xFF0073F0)); + expect(bottomLargeTitle.text.style.color, const Color(0xff006de4)); expect(bottomLargeTitle.text.style.fontWeight, FontWeight.w400); expect(bottomLargeTitle.text.style.fontFamily, '.SF Pro Text'); - expect(bottomLargeTitle.text.style.letterSpacing, -0.3647452166676521); + expect(bottomLargeTitle.text.style.letterSpacing, -0.32379547566175454); - expect(topBackLabel.text.style.color, const Color(0xFF0073F0)); + expect(topBackLabel.text.style.color, const Color(0xff006de4)); expect(topBackLabel.text.style.fontWeight, FontWeight.w400); expect(topBackLabel.text.style.fontFamily, '.SF Pro Text'); - expect(topBackLabel.text.style.letterSpacing, -0.3647452166676521); + expect(topBackLabel.text.style.letterSpacing, -0.32379547566175454); }); testWidgets('Top middle fades in and slides in from the right', @@ -996,15 +994,15 @@ void main() { checkOpacity(tester, flying(tester, find.text('Page 2')), 0.0); expect( tester.getTopLeft(flying(tester, find.text('Page 2'))), - const Offset(726.1760711669922, 13.5), + const Offset(732.8125, 13.5), ); await tester.pump(const Duration(milliseconds: 150)); - checkOpacity(tester, flying(tester, find.text('Page 2')), 0.6972532719373703); + checkOpacity(tester, flying(tester, find.text('Page 2')), 0.5555618554353714); expect( tester.getTopLeft(flying(tester, find.text('Page 2'))), - const Offset(409.02137756347656, 13.5), + const Offset(439.7678077220917, 13.5), ); }); @@ -1023,15 +1021,15 @@ void main() { checkOpacity(tester, flying(tester, find.text('Page 2')), 0.0); expect( tester.getTopRight(flying(tester, find.text('Page 2'))), - const Offset(73.82392883300781, 13.5), + const Offset(67.1875, 13.5), ); await tester.pump(const Duration(milliseconds: 150)); - checkOpacity(tester, flying(tester, find.text('Page 2')), 0.6972532719373703); + checkOpacity(tester, flying(tester, find.text('Page 2')), 0.5555618554353714); expect( tester.getTopRight(flying(tester, find.text('Page 2'))), - const Offset(390.97862243652344, 13.5), + const Offset(360.2321922779083, 13.5), ); }); @@ -1050,15 +1048,15 @@ void main() { checkOpacity(tester, flying(tester, find.text('Page 2')), 0.0); expect( tester.getTopLeft(flying(tester, find.text('Page 2'))), - const Offset(768.3521423339844, 54.0), + const Offset(781.625, 54.0), ); await tester.pump(const Duration(milliseconds: 150)); - checkOpacity(tester, flying(tester, find.text('Page 2')), 0.6753286570310593); + checkOpacity(tester, flying(tester, find.text('Page 2')), 0.5292819738388062); expect( tester.getTopLeft(flying(tester, find.text('Page 2'))), - const Offset(134.04275512695312, 54.0), + const Offset(195.53561544418335, 54.0), ); }); @@ -1078,15 +1076,15 @@ void main() { checkOpacity(tester, flying(tester, find.text('Page 2')), 0.0); expect( tester.getTopRight(flying(tester, find.text('Page 2'))), - const Offset(31.647857666015625, 54.0), + const Offset(18.375, 54.0), ); await tester.pump(const Duration(milliseconds: 150)); - checkOpacity(tester, flying(tester, find.text('Page 2')), 0.6753286570310593); + checkOpacity(tester, flying(tester, find.text('Page 2')), 0.5292819738388062); expect( tester.getTopRight(flying(tester, find.text('Page 2'))), - const Offset(665.9572448730469, 54.0), + const Offset(604.4643845558167, 54.0), ); }); @@ -1171,7 +1169,6 @@ void main() { await gesture.up(); await tester.pump(); - // Transition continues. expect( tester.getTopLeft(flying(tester, find.text('Page 2'))), @@ -1180,7 +1177,7 @@ void main() { await tester.pump(const Duration(milliseconds: 50)); expect( tester.getTopLeft(flying(tester, find.text('Page 2'))), - const Offset(721.8727767467499, 13.5), + const Offset(749.6335566043854, 13.5), ); await tester.pump(const Duration(milliseconds: 500)); @@ -1229,7 +1226,7 @@ void main() { await tester.pump(const Duration(milliseconds: 50)); expect( tester.getTopLeft(flying(tester, find.text('Page 2'))), - const Offset(351.00985169410706, 13.5), + const Offset(350.0011436641216, 13.5), ); // Finish the snap back animation. diff --git a/packages/flutter/test/cupertino/page_test.dart b/packages/flutter/test/cupertino/page_test.dart index b9970392ba3..e0bc7a715a3 100644 --- a/packages/flutter/test/cupertino/page_test.dart +++ b/packages/flutter/test/cupertino/page_test.dart @@ -40,6 +40,9 @@ void main() { // Page 2 is coming in from the right. expect(widget2TopLeft.dx, greaterThan(widget1InitialTopLeft.dx)); + // Will need to be changed if the animation curve or duration changes. + expect(widget1TransientTopLeft.dx, closeTo(130, 1.0)); + await tester.pumpAndSettle(); // Page 2 covers page 1. @@ -62,6 +65,9 @@ void main() { // Page 2 is leaving towards the right. expect(widget2TopLeft.dx, greaterThan(widget1InitialTopLeft.dx)); + // Will need to be changed if the animation curve or duration changes. + expect(widget1TransientTopLeft.dx, closeTo(249, 1.0)); + await tester.pumpAndSettle(); expect(find.text('Page 1'), isOnstage); @@ -222,7 +228,7 @@ void main() { tester.state(find.byType(Navigator)).pushNamed('/next'); await tester.pump(); - await tester.pump(const Duration(milliseconds: 400)); + await tester.pump(const Duration(seconds: 1)); // Page 2 covers page 1. expect(find.text('Page 1'), findsNothing); @@ -285,7 +291,7 @@ void main() { ); await tester.pump(); - await tester.pump(const Duration(milliseconds: 400)); + await tester.pump(const Duration(seconds: 2)); tester.state(find.byType(Navigator)).push( CupertinoPageRoute( @@ -293,7 +299,7 @@ void main() { ), ); await tester.pump(); - await tester.pump(const Duration(milliseconds: 400)); + await tester.pumpAndSettle(); expect(find.text('Page 1'), findsNothing); expect(find.text('Page 2'), isOnstage); @@ -332,7 +338,7 @@ void main() { ); await tester.pump(); - await tester.pump(const Duration(milliseconds: 400)); + await tester.pumpAndSettle(); tester.state(find.byType(Navigator)).push( CupertinoPageRoute( @@ -341,7 +347,7 @@ void main() { ); await tester.pump(); - await tester.pump(const Duration(milliseconds: 400)); + await tester.pumpAndSettle(); expect(find.text('Page 1'), findsNothing); expect(find.text('Page 2'), isOnstage); @@ -379,7 +385,7 @@ void main() { tester.state(find.byType(Navigator)).pushNamed('/next'); await tester.pump(); - await tester.pump(const Duration(milliseconds: 400)); + await tester.pumpAndSettle(); // Page 2 covers page 1. expect(find.text('Page 1'), findsNothing); diff --git a/packages/flutter_test/test/widget_tester_test.dart b/packages/flutter_test/test/widget_tester_test.dart index e3cc6b1a5b6..ecab29a7638 100644 --- a/packages/flutter_test/test/widget_tester_test.dart +++ b/packages/flutter_test/test/widget_tester_test.dart @@ -411,7 +411,7 @@ void main() { await tester.pageBack(); await tester.pump(); - await tester.pump(const Duration(milliseconds: 400)); + await tester.pumpAndSettle(); expect(find.text('Next'), findsOneWidget); expect(find.text('Page 2'), findsNothing);