From daad63848c763e660870d7773b20c90579da647d Mon Sep 17 00:00:00 2001 From: Shi Hao Hong Date: Mon, 30 Dec 2019 14:12:57 -0800 Subject: [PATCH 01/37] WIP - Shared Z Axis Page Transition Builder --- packages/animations/lib/animations.dart | 1 + .../lib/src/shared_z_axis_transition.dart | 108 ++++++++++++++++++ packages/animations/lib/src/utils/curves.dart | 39 +++++++ 3 files changed, 148 insertions(+) create mode 100644 packages/animations/lib/src/shared_z_axis_transition.dart create mode 100644 packages/animations/lib/src/utils/curves.dart diff --git a/packages/animations/lib/animations.dart b/packages/animations/lib/animations.dart index c1cddf0b7d0b..ce0beba545c5 100644 --- a/packages/animations/lib/animations.dart +++ b/packages/animations/lib/animations.dart @@ -5,3 +5,4 @@ export 'src/fade_through_transition.dart'; export 'src/open_container.dart'; export 'src/page_transition_switcher.dart'; +export 'src/shared_z_axis_transition.dart'; diff --git a/packages/animations/lib/src/shared_z_axis_transition.dart b/packages/animations/lib/src/shared_z_axis_transition.dart new file mode 100644 index 000000000000..113d0581a546 --- /dev/null +++ b/packages/animations/lib/src/shared_z_axis_transition.dart @@ -0,0 +1,108 @@ +// Copyright 2019 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/animation.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; + +import 'utils/curves.dart'; + +/// TODO: documentation +class SharedZAxisPageTransitionBuilder extends PageTransitionsBuilder { + /// Construct a [SharedZAxisPageTransitionBuilder]. + const SharedZAxisPageTransitionBuilder(); + + @override + Widget buildTransitions( + PageRoute route, + BuildContext context, + Animation animation, + Animation secondaryAnimation, + Widget child, + ) { + return _SharedZAxisPageTransition( + animation: animation, + secondaryAnimation: secondaryAnimation, + child: child, + ); + } +} + +class _SharedZAxisPageTransition extends StatefulWidget { + const _SharedZAxisPageTransition({ + Key key, + this.animation, + this.secondaryAnimation, + this.child, + }) : super(key: key); + + final Animation animation; + final Animation secondaryAnimation; + final Widget child; + + @override + __SharedZAxisPageTransitionState createState() => __SharedZAxisPageTransitionState(); +} + +class __SharedZAxisPageTransitionState extends State<_SharedZAxisPageTransition> { + static final Tween _flippedTween = Tween( + begin: 1.0, + end: 0.0, + ); + + static Animation _flip(Animation animation) { + return _flippedTween.animate(animation); + } + + @override + Widget build(BuildContext context) { + // Scale Transitions + final Animation _forwardEndScreenScaleTransition = widget.animation.drive( + Tween(begin: 0.80, end: 1.00) + .chain(standardEasing)); + + final Animation _forwardStartScreenScaleTransition = widget.secondaryAnimation.drive( + Tween(begin: 1.00, end: 1.10) + .chain(standardEasing)); + + // Fade Transitions + final Animation _forwardStartScreenFadeTransition = _flip(widget.secondaryAnimation).drive( + accelerateEasing + .chain(CurveTween(curve: const Interval(0.0, 0.3)))); + + final Animation _forwardEndScreenFadeTransition = widget.animation.drive( + decelerateEasing + .chain(CurveTween(curve: const Interval(0.3, 1.0)))); + + return AnimatedBuilder( + animation: widget.animation, + builder: (BuildContext context, Widget child) { + return FadeTransition( + opacity: _forwardEndScreenFadeTransition, + child: ScaleTransition( + scale: _forwardEndScreenScaleTransition, + child: child, + ), + ); + }, + child: AnimatedBuilder( + animation: widget.secondaryAnimation, + builder: (BuildContext context, Widget child) { + return Container( + color: Theme.of(context).canvasColor, + child: FadeTransition( + opacity: _forwardStartScreenFadeTransition, + child: ScaleTransition( + scale: _forwardStartScreenScaleTransition, + child: child, + ), + ), + ); + }, + child: widget.child, + ), + ); + } +} \ No newline at end of file diff --git a/packages/animations/lib/src/utils/curves.dart b/packages/animations/lib/src/utils/curves.dart new file mode 100644 index 000000000000..c54d9f8a19b4 --- /dev/null +++ b/packages/animations/lib/src/utils/curves.dart @@ -0,0 +1,39 @@ +import 'package:flutter/animation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; + +// The easing curves of the Material Library +/// The standard easing curve in the Material specification. +/// +/// Elements that begin and end at rest use standard easing. +/// They speed up quickly and slow down gradually, in order +/// to emphasize the end of the transition. +/// +/// See also: +/// * +final CurveTween standardEasing = CurveTween( + curve: const Cubic(0.4, 0.0, 0.2, 1), +); + +/// The accelerate easing curve in the Material specification. +/// +/// Elements exiting a screen use acceleration easing, +/// where they start at rest and end at peak velocity. +/// +/// See also: +/// * +final CurveTween accelerateEasing = CurveTween( + curve: const Cubic(0.4, 0.0, 1.0, 1.0), +); + +/// The decelerate easing curve in the Material specification. +/// +/// Incoming elements are animated using deceleration easing, +/// which starts a transition at peak velocity (the fastest +/// point of an element’s movement) and ends at rest. +/// +/// See also: +/// * +final CurveTween decelerateEasing = CurveTween( + curve: const Cubic(0.0, 0.0, 0.2, 1.0), +); From 177dab66d075446007d86c45983fd7828d479ba0 Mon Sep 17 00:00:00 2001 From: Shi Hao Hong Date: Mon, 30 Dec 2019 14:17:56 -0800 Subject: [PATCH 02/37] Update utils --- .../animations/lib/src/fade_through_transition.dart | 2 +- .../animations/lib/src/shared_z_axis_transition.dart | 10 +--------- packages/animations/lib/src/utils/curves.dart | 11 +++++++++++ 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/packages/animations/lib/src/fade_through_transition.dart b/packages/animations/lib/src/fade_through_transition.dart index 51c17a7fcf86..b5084ca12b4c 100644 --- a/packages/animations/lib/src/fade_through_transition.dart +++ b/packages/animations/lib/src/fade_through_transition.dart @@ -323,7 +323,7 @@ class _FadeThroughTransitionState extends State { case AnimationStatus.reverse: case AnimationStatus.completed: return _ZoomedFadeIn( - animation: _flip(widget.secondaryAnimation), + animation: flipTween(widget.secondaryAnimation), child: child, ); } diff --git a/packages/animations/lib/src/shared_z_axis_transition.dart b/packages/animations/lib/src/shared_z_axis_transition.dart index 113d0581a546..2eb99ca0d331 100644 --- a/packages/animations/lib/src/shared_z_axis_transition.dart +++ b/packages/animations/lib/src/shared_z_axis_transition.dart @@ -47,14 +47,6 @@ class _SharedZAxisPageTransition extends StatefulWidget { } class __SharedZAxisPageTransitionState extends State<_SharedZAxisPageTransition> { - static final Tween _flippedTween = Tween( - begin: 1.0, - end: 0.0, - ); - - static Animation _flip(Animation animation) { - return _flippedTween.animate(animation); - } @override Widget build(BuildContext context) { @@ -68,7 +60,7 @@ class __SharedZAxisPageTransitionState extends State<_SharedZAxisPageTransition> .chain(standardEasing)); // Fade Transitions - final Animation _forwardStartScreenFadeTransition = _flip(widget.secondaryAnimation).drive( + final Animation _forwardStartScreenFadeTransition = flipTween(widget.secondaryAnimation).drive( accelerateEasing .chain(CurveTween(curve: const Interval(0.0, 0.3)))); diff --git a/packages/animations/lib/src/utils/curves.dart b/packages/animations/lib/src/utils/curves.dart index c54d9f8a19b4..6a9435e06ca9 100644 --- a/packages/animations/lib/src/utils/curves.dart +++ b/packages/animations/lib/src/utils/curves.dart @@ -37,3 +37,14 @@ final CurveTween accelerateEasing = CurveTween( final CurveTween decelerateEasing = CurveTween( curve: const Cubic(0.0, 0.0, 0.2, 1.0), ); + +// A tween that starts from 1.0 and ends at 0.0. +final Tween _flippedTween = Tween( + begin: 1.0, + end: 0.0, +); + +/// Flips the incoming passed in [Animation] to start from 1.0 and end at 0.0. +Animation flipTween(Animation animation) { + return _flippedTween.animate(animation); +} \ No newline at end of file From 6b380edf2feb384562f6ae559026257f438c6ab7 Mon Sep 17 00:00:00 2001 From: Shi Hao Hong Date: Mon, 30 Dec 2019 14:18:28 -0800 Subject: [PATCH 03/37] Revert change to fade_through_transition.dart --- packages/animations/lib/src/fade_through_transition.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/animations/lib/src/fade_through_transition.dart b/packages/animations/lib/src/fade_through_transition.dart index b5084ca12b4c..51c17a7fcf86 100644 --- a/packages/animations/lib/src/fade_through_transition.dart +++ b/packages/animations/lib/src/fade_through_transition.dart @@ -323,7 +323,7 @@ class _FadeThroughTransitionState extends State { case AnimationStatus.reverse: case AnimationStatus.completed: return _ZoomedFadeIn( - animation: flipTween(widget.secondaryAnimation), + animation: _flip(widget.secondaryAnimation), child: child, ); } From 869133d3da5ff134f88f305f9e050af6678fd358 Mon Sep 17 00:00:00 2001 From: Shi Hao Hong Date: Mon, 30 Dec 2019 14:40:40 -0800 Subject: [PATCH 04/37] Add API documentation --- .../lib/src/shared_z_axis_transition.dart | 170 ++++++++++++++++-- 1 file changed, 158 insertions(+), 12 deletions(-) diff --git a/packages/animations/lib/src/shared_z_axis_transition.dart b/packages/animations/lib/src/shared_z_axis_transition.dart index 2eb99ca0d331..ecb96d98589c 100644 --- a/packages/animations/lib/src/shared_z_axis_transition.dart +++ b/packages/animations/lib/src/shared_z_axis_transition.dart @@ -9,10 +9,61 @@ import 'package:flutter/widgets.dart'; import 'utils/curves.dart'; -/// TODO: documentation -class SharedZAxisPageTransitionBuilder extends PageTransitionsBuilder { - /// Construct a [SharedZAxisPageTransitionBuilder]. - const SharedZAxisPageTransitionBuilder(); +/// Used by [PageTransitionsTheme] to define a page route transition animation +/// in which outgoing and incoming elements share a scale transition. +/// +/// The shared axis pattern provides the transition animation between UI elements +/// that have a spatial or navigational relationship. For example, +/// transitioning from one page of a sign up page to the next one. +/// +/// +/// The following example shows how the SharedZAxisPageTransitionsBuilder can +/// be used in a [PageTransitionsTheme] to change the default transitions +/// of [MaterialPageRoute]s. +/// +/// ```dart +/// MaterialApp( +/// theme: ThemeData( +/// pageTransitionsTheme: PageTransitionsTheme( +/// builders: { +/// TargetPlatform.android: SharedZAxisPageTransitionsBuilder(), +/// TargetPlatform.iOS: SharedZAxisPageTransitionsBuilder(), +/// }, +/// ), +/// ), +/// routes: { +/// '/': (BuildContext context) { +/// return Container( +/// color: Colors.red, +/// child: Center( +/// child: MaterialButton( +/// child: Text('Push route'), +/// onPressed: () { +/// Navigator.of(context).pushNamed('/a'); +/// }, +/// ), +/// ), +/// ); +/// }, +/// '/a' : (BuildContext context) { +/// return Container( +/// color: Colors.blue, +/// child: Center( +/// child: MaterialButton( +/// child: Text('Pop route'), +/// onPressed: () { +/// Navigator.of(context).pop(); +/// }, +/// ), +/// ), +/// ); +/// }, +/// }, +/// ); +/// ``` +class SharedZAxisPageTransitionsBuilder extends PageTransitionsBuilder { + /// Construct a [SharedZAxisPageTransitionsBuilder]. + const SharedZAxisPageTransitionsBuilder(); @override Widget buildTransitions( @@ -22,7 +73,7 @@ class SharedZAxisPageTransitionBuilder extends PageTransitionsBuilder { Animation secondaryAnimation, Widget child, ) { - return _SharedZAxisPageTransition( + return SharedZAxisPageTransition( animation: animation, secondaryAnimation: secondaryAnimation, child: child, @@ -30,24 +81,119 @@ class SharedZAxisPageTransitionBuilder extends PageTransitionsBuilder { } } -class _SharedZAxisPageTransition extends StatefulWidget { - const _SharedZAxisPageTransition({ +/// Defines a transition in which outgoing and incoming elements share a scale +/// transition. +/// +/// The shared axis pattern provides the transition animation between UI elements +/// that have a spatial or navigational relationship. For example, +/// transitioning from one page of a sign up page to the next one. +/// +/// Consider using [SharedZAxisPageTransition] within a +/// [PageTransitionsTheme] if you want to apply this kind of transition to +/// [MaterialPageRoute] transitions within a Navigator (see +/// [SharedZAxisPageTransitionsBuilder] for some example code). +/// +/// This transition can also be used directly in a +/// [PageTransitionSwitcher.transitionBuilder] to transition +/// from one widget to another as seen in the following example: +/// +/// ```dart +/// int _selectedIndex = 0; +/// +/// final List _colors = [Colors.white, Colors.red, Colors.yellow]; +/// +/// @override +/// Widget build(BuildContext context) { +/// return Scaffold( +/// appBar: AppBar( +/// title: const Text('Page Transition Example'), +/// ), +/// body: PageTransitionSwitcher( +/// reverse: true, +/// transitionBuilder: ( +/// Widget child, +/// Animation primaryAnimation, +/// Animation secondaryAnimation, +/// ) { +/// return SharedZAxisPageTransition( +/// animation: primaryAnimation, +/// secondaryAnimation: secondaryAnimation, +/// child: child, +/// ); +/// }, +/// child: Container( +/// key: ValueKey(_selectedIndex), +/// color: _colors[_selectedIndex], +/// child: Center( +/// child: FlutterLogo(size: 300), +/// ) +/// ), +/// ), +/// bottomNavigationBar: BottomNavigationBar( +/// items: const [ +/// BottomNavigationBarItem( +/// icon: Icon(Icons.home), +/// title: Text('White'), +/// ), +/// BottomNavigationBarItem( +/// icon: Icon(Icons.business), +/// title: Text('Red'), +/// ), +/// BottomNavigationBarItem( +/// icon: Icon(Icons.school), +/// title: Text('Yellow'), +/// ), +/// ], +/// currentIndex: _selectedIndex, +/// onTap: (int index) { +/// setState(() { +/// _selectedIndex = index; +/// }); +/// }, +/// ), +/// ); +/// } +/// ``` +class SharedZAxisPageTransition extends StatefulWidget { + /// Creates a [SharedZAxisPageTransition]. + /// + /// The [animation] and [secondaryAnimation] argument are required and must + /// not be null. + const SharedZAxisPageTransition({ Key key, - this.animation, - this.secondaryAnimation, + @required this.animation, + @required this.secondaryAnimation, this.child, }) : super(key: key); + /// The animation that drives the [child]'s entrance and exit. + /// + /// See also: + /// + /// * [TransitionRoute.animate], which is the value given to this property + /// when it is used as a page transition. final Animation animation; + + /// The animation that transitions [child] when new content is pushed on top + /// of it. + /// + /// See also: + /// + /// * [TransitionRoute.secondaryAnimation], which is the value given to this + /// property when the it is used as a page transition. final Animation secondaryAnimation; + + /// The widget below this widget in the tree. + /// + /// This widget will transition in and out as driven by [animation] and + /// [secondaryAnimation]. final Widget child; @override - __SharedZAxisPageTransitionState createState() => __SharedZAxisPageTransitionState(); + _SharedZAxisPageTransitionState createState() => _SharedZAxisPageTransitionState(); } -class __SharedZAxisPageTransitionState extends State<_SharedZAxisPageTransition> { - +class _SharedZAxisPageTransitionState extends State { @override Widget build(BuildContext context) { // Scale Transitions From 4d6ce6ac5e3c4e4e2ef07601029d86b0d992aab7 Mon Sep 17 00:00:00 2001 From: Shi Hao Hong Date: Mon, 30 Dec 2019 14:46:35 -0800 Subject: [PATCH 05/37] Remove reverse line from API doc sample --- packages/animations/lib/src/shared_z_axis_transition.dart | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/animations/lib/src/shared_z_axis_transition.dart b/packages/animations/lib/src/shared_z_axis_transition.dart index ecb96d98589c..8ea3e7f3579f 100644 --- a/packages/animations/lib/src/shared_z_axis_transition.dart +++ b/packages/animations/lib/src/shared_z_axis_transition.dart @@ -96,7 +96,6 @@ class SharedZAxisPageTransitionsBuilder extends PageTransitionsBuilder { /// This transition can also be used directly in a /// [PageTransitionSwitcher.transitionBuilder] to transition /// from one widget to another as seen in the following example: -/// /// ```dart /// int _selectedIndex = 0; /// @@ -109,7 +108,6 @@ class SharedZAxisPageTransitionsBuilder extends PageTransitionsBuilder { /// title: const Text('Page Transition Example'), /// ), /// body: PageTransitionSwitcher( -/// reverse: true, /// transitionBuilder: ( /// Widget child, /// Animation primaryAnimation, From 82e8a4d4c672e6a30927a1b4b1f52240ea941b66 Mon Sep 17 00:00:00 2001 From: Shi Hao Hong Date: Mon, 30 Dec 2019 14:47:53 -0800 Subject: [PATCH 06/37] Leave comment in API doc for reverse transition --- packages/animations/lib/src/shared_z_axis_transition.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/animations/lib/src/shared_z_axis_transition.dart b/packages/animations/lib/src/shared_z_axis_transition.dart index 8ea3e7f3579f..917c29fcc98e 100644 --- a/packages/animations/lib/src/shared_z_axis_transition.dart +++ b/packages/animations/lib/src/shared_z_axis_transition.dart @@ -108,6 +108,7 @@ class SharedZAxisPageTransitionsBuilder extends PageTransitionsBuilder { /// title: const Text('Page Transition Example'), /// ), /// body: PageTransitionSwitcher( +/// // reverse: true, // uncomment to see transition in reverse /// transitionBuilder: ( /// Widget child, /// Animation primaryAnimation, From cc0b5d97261dab74ac13162c30adf6a14f37b70c Mon Sep 17 00:00:00 2001 From: Shi Hao Hong Date: Mon, 30 Dec 2019 14:52:03 -0800 Subject: [PATCH 07/37] Add simple test --- .../lib/src/shared_z_axis_transition.dart | 16 ++++----- .../test/shared_z_axis_transition_test.dart | 35 +++++++++++++++++++ 2 files changed, 43 insertions(+), 8 deletions(-) create mode 100644 packages/animations/test/shared_z_axis_transition_test.dart diff --git a/packages/animations/lib/src/shared_z_axis_transition.dart b/packages/animations/lib/src/shared_z_axis_transition.dart index 917c29fcc98e..f47ce196110f 100644 --- a/packages/animations/lib/src/shared_z_axis_transition.dart +++ b/packages/animations/lib/src/shared_z_axis_transition.dart @@ -73,7 +73,7 @@ class SharedZAxisPageTransitionsBuilder extends PageTransitionsBuilder { Animation secondaryAnimation, Widget child, ) { - return SharedZAxisPageTransition( + return SharedZAxisTransition( animation: animation, secondaryAnimation: secondaryAnimation, child: child, @@ -88,7 +88,7 @@ class SharedZAxisPageTransitionsBuilder extends PageTransitionsBuilder { /// that have a spatial or navigational relationship. For example, /// transitioning from one page of a sign up page to the next one. /// -/// Consider using [SharedZAxisPageTransition] within a +/// Consider using [SharedZAxisTransition] within a /// [PageTransitionsTheme] if you want to apply this kind of transition to /// [MaterialPageRoute] transitions within a Navigator (see /// [SharedZAxisPageTransitionsBuilder] for some example code). @@ -114,7 +114,7 @@ class SharedZAxisPageTransitionsBuilder extends PageTransitionsBuilder { /// Animation primaryAnimation, /// Animation secondaryAnimation, /// ) { -/// return SharedZAxisPageTransition( +/// return SharedZAxisTransition( /// animation: primaryAnimation, /// secondaryAnimation: secondaryAnimation, /// child: child, @@ -153,12 +153,12 @@ class SharedZAxisPageTransitionsBuilder extends PageTransitionsBuilder { /// ); /// } /// ``` -class SharedZAxisPageTransition extends StatefulWidget { - /// Creates a [SharedZAxisPageTransition]. +class SharedZAxisTransition extends StatefulWidget { + /// Creates a [SharedZAxisTransition]. /// /// The [animation] and [secondaryAnimation] argument are required and must /// not be null. - const SharedZAxisPageTransition({ + const SharedZAxisTransition({ Key key, @required this.animation, @required this.secondaryAnimation, @@ -189,10 +189,10 @@ class SharedZAxisPageTransition extends StatefulWidget { final Widget child; @override - _SharedZAxisPageTransitionState createState() => _SharedZAxisPageTransitionState(); + _SharedZAxisTransitionState createState() => _SharedZAxisTransitionState(); } -class _SharedZAxisPageTransitionState extends State { +class _SharedZAxisTransitionState extends State { @override Widget build(BuildContext context) { // Scale Transitions diff --git a/packages/animations/test/shared_z_axis_transition_test.dart b/packages/animations/test/shared_z_axis_transition_test.dart new file mode 100644 index 000000000000..7b551182fded --- /dev/null +++ b/packages/animations/test/shared_z_axis_transition_test.dart @@ -0,0 +1,35 @@ +// Copyright 2019 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:animations/src/shared_z_axis_transition.dart'; +import 'package:flutter/animation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:flutter/widgets.dart'; + +void main() { + testWidgets( + 'FadeThroughPageTransitionsBuilder builds a FadeThroughTransition', + (WidgetTester tester) async { + final AnimationController animation = AnimationController( + vsync: const TestVSync(), + ); + final AnimationController secondaryAnimation = AnimationController( + vsync: const TestVSync(), + ); + + await tester.pumpWidget( + const SharedZAxisPageTransitionsBuilder().buildTransitions( + null, + null, + animation, + secondaryAnimation, + const Placeholder(), + ), + ); + + expect(find.byType(SharedZAxisTransition), findsOneWidget); + }, + ); +} \ No newline at end of file From a5ef638ac3f48f81d3ade9b0144d6a77b37e6494 Mon Sep 17 00:00:00 2001 From: Shi Hao Hong Date: Mon, 30 Dec 2019 15:15:10 -0800 Subject: [PATCH 08/37] Add forwards animation test --- .../lib/src/shared_z_axis_transition.dart | 2 +- .../test/shared_z_axis_transition_test.dart | 154 +++++++++++++++++- 2 files changed, 154 insertions(+), 2 deletions(-) diff --git a/packages/animations/lib/src/shared_z_axis_transition.dart b/packages/animations/lib/src/shared_z_axis_transition.dart index f47ce196110f..8db4ef734d16 100644 --- a/packages/animations/lib/src/shared_z_axis_transition.dart +++ b/packages/animations/lib/src/shared_z_axis_transition.dart @@ -207,7 +207,7 @@ class _SharedZAxisTransitionState extends State { // Fade Transitions final Animation _forwardStartScreenFadeTransition = flipTween(widget.secondaryAnimation).drive( accelerateEasing - .chain(CurveTween(curve: const Interval(0.0, 0.3)))); + .chain(CurveTween(curve: const Interval(0.7, 1.0)))); final Animation _forwardEndScreenFadeTransition = widget.animation.drive( decelerateEasing diff --git a/packages/animations/test/shared_z_axis_transition_test.dart b/packages/animations/test/shared_z_axis_transition_test.dart index 7b551182fded..b6e1ec843caa 100644 --- a/packages/animations/test/shared_z_axis_transition_test.dart +++ b/packages/animations/test/shared_z_axis_transition_test.dart @@ -32,4 +32,156 @@ void main() { expect(find.byType(SharedZAxisTransition), findsOneWidget); }, ); -} \ No newline at end of file + + testWidgets( + 'SharedZAxisTransition runs forward', + (WidgetTester tester) async { + final GlobalKey navigator = GlobalKey(); + const String bottomRoute = '/'; + const String topRoute = '/a'; + + await tester.pumpWidget( + _TestWidget(navigatorKey: navigator), + ); + + expect(find.text(bottomRoute), findsOneWidget); + expect(_getScale(bottomRoute, tester), 1.0); + expect(_getOpacity(bottomRoute, tester), 1.0); + expect(find.text(topRoute), findsNothing); + + navigator.currentState.pushNamed(topRoute); + await tester.pump(); + await tester.pump(); + + // Bottom route is full size and fully visible. + expect(find.text(bottomRoute), findsOneWidget); + expect(_getScale(bottomRoute, tester), 1.0); + expect(_getOpacity(bottomRoute, tester), 1.0); + // Top route is at 80% of full size and not visible yet. + expect(find.text(topRoute), findsOneWidget); + expect(_getScale(topRoute, tester), 0.8); + expect(_getOpacity(topRoute, tester), 0.0); + + // Jump 3/10ths of the way through the transition, bottom route + // should be be completely faded out while the top route + // is also completely faded out. + // Transition time: 300ms, 3/10 * 300ms = 90ms + await tester.pump(const Duration(milliseconds: 90)); + + // Bottom route is now invisible + expect(find.text(bottomRoute), findsOneWidget); + expect(_getOpacity(bottomRoute, tester), 0.0); + // Top route is still invisible, but scaling up. + expect(find.text(topRoute), findsOneWidget); + expect(_getOpacity(topRoute, tester), 0.0); + double topScale = _getScale(topRoute, tester); + expect(topScale, greaterThan(0.8)); + expect(topScale, lessThan(1.0)); + + // Jump to the middle of fading in + await tester.pump(const Duration(milliseconds: 90)); + // Bottom route is still invisible + expect(find.text(bottomRoute), findsOneWidget); + expect(_getOpacity(bottomRoute, tester), 0.0); + // Top route is fading in + expect(find.text(topRoute), findsOneWidget); + expect(_getOpacity(topRoute, tester), greaterThan(0)); + expect(_getOpacity(topRoute, tester), lessThan(1.0)); + topScale = _getScale(topRoute, tester); + expect(topScale, greaterThan(0.8)); + expect(topScale, lessThan(1.0)); + + // Jump to the end of the transition + await tester.pump(const Duration(milliseconds: 120)); + // Bottom route is not visible. + expect(find.text(bottomRoute), findsOneWidget); + expect(_getScale(bottomRoute, tester), 1.1); + expect(_getOpacity(bottomRoute, tester), 0.0); + // Top route fully scaled in and visible. + expect(find.text(topRoute), findsOneWidget); + expect(_getScale(topRoute, tester), 1.0); + expect(_getOpacity(topRoute, tester), 1.0); + + await tester.pump(const Duration(milliseconds: 1)); + expect(find.text(bottomRoute), findsNothing); + expect(find.text(topRoute), findsOneWidget); + }, + ); +} + +double _getOpacity(String key, WidgetTester tester) { + final Finder finder = find.ancestor( + of: find.byKey(ValueKey(key)), + matching: find.byType(FadeTransition), + ); + return tester.widgetList(finder).fold(1.0, (double a, Widget widget) { + final FadeTransition transition = widget; + return a * transition.opacity.value; + }); +} + +double _getScale(String key, WidgetTester tester) { + final Finder finder = find.ancestor( + of: find.byKey(ValueKey(key)), + matching: find.byType(ScaleTransition), + ); + return tester.widgetList(finder).fold(1.0, (double a, Widget widget) { + final ScaleTransition transition = widget; + return a * transition.scale.value; + }); +} + +class _TestWidget extends StatelessWidget { + const _TestWidget({this.navigatorKey, this.contentBuilder}); + + final Key navigatorKey; + final _ContentBuilder contentBuilder; + + @override + Widget build(BuildContext context) { + return MaterialApp( + navigatorKey: navigatorKey, + theme: ThemeData( + platform: TargetPlatform.android, + pageTransitionsTheme: const PageTransitionsTheme( + builders: { + TargetPlatform.android: SharedZAxisPageTransitionsBuilder(), + }, + ), + ), + onGenerateRoute: (RouteSettings settings) { + return MaterialPageRoute( + settings: settings, + builder: (BuildContext context) { + return contentBuilder != null + ? contentBuilder(settings) + : Container( + child: Center( + key: ValueKey(settings.name), + child: Text(settings.name), + ), + ); + }, + ); + }, + ); + } +} + +class _StatefulTestWidget extends StatefulWidget { + const _StatefulTestWidget({Key key, this.name}) : super(key: key); + + final String name; + + @override + State<_StatefulTestWidget> createState() => _StatefulTestWidgetState(); +} + +class _StatefulTestWidgetState extends State<_StatefulTestWidget> { + @override + Widget build(BuildContext context) { + return Text(widget.name); + } +} + +typedef _ContentBuilder = Widget Function(RouteSettings settings); From d45385855023f2139c5ef2cdc8c1d2a81eb3ed8e Mon Sep 17 00:00:00 2001 From: Shi Hao Hong Date: Mon, 30 Dec 2019 17:07:13 -0800 Subject: [PATCH 09/37] Fix broken curves --- .../lib/src/shared_z_axis_transition.dart | 20 ++++++++-------- packages/animations/lib/src/utils/curves.dart | 23 +++++++++++-------- 2 files changed, 24 insertions(+), 19 deletions(-) diff --git a/packages/animations/lib/src/shared_z_axis_transition.dart b/packages/animations/lib/src/shared_z_axis_transition.dart index 8db4ef734d16..96cc2ba77266 100644 --- a/packages/animations/lib/src/shared_z_axis_transition.dart +++ b/packages/animations/lib/src/shared_z_axis_transition.dart @@ -198,19 +198,19 @@ class _SharedZAxisTransitionState extends State { // Scale Transitions final Animation _forwardEndScreenScaleTransition = widget.animation.drive( Tween(begin: 0.80, end: 1.00) - .chain(standardEasing)); + .chain(CurveTween(curve: standardEasing))); final Animation _forwardStartScreenScaleTransition = widget.secondaryAnimation.drive( Tween(begin: 1.00, end: 1.10) - .chain(standardEasing)); + .chain(CurveTween(curve: standardEasing))); // Fade Transitions - final Animation _forwardStartScreenFadeTransition = flipTween(widget.secondaryAnimation).drive( - accelerateEasing - .chain(CurveTween(curve: const Interval(0.7, 1.0)))); + final Animation _forwardStartScreenFadeTransition = widget.secondaryAnimation.drive( + FlippedCurveTween(curve: accelerateEasing) + .chain(CurveTween(curve: const Interval(0.0, 0.3)))); final Animation _forwardEndScreenFadeTransition = widget.animation.drive( - decelerateEasing + CurveTween(curve: decelerateEasing) .chain(CurveTween(curve: const Interval(0.3, 1.0)))); return AnimatedBuilder( @@ -227,10 +227,10 @@ class _SharedZAxisTransitionState extends State { child: AnimatedBuilder( animation: widget.secondaryAnimation, builder: (BuildContext context, Widget child) { - return Container( - color: Theme.of(context).canvasColor, - child: FadeTransition( - opacity: _forwardStartScreenFadeTransition, + return FadeTransition( + opacity: _forwardStartScreenFadeTransition, + child: Container( + color: Theme.of(context).canvasColor, child: ScaleTransition( scale: _forwardStartScreenScaleTransition, child: child, diff --git a/packages/animations/lib/src/utils/curves.dart b/packages/animations/lib/src/utils/curves.dart index 6a9435e06ca9..f9f65a625034 100644 --- a/packages/animations/lib/src/utils/curves.dart +++ b/packages/animations/lib/src/utils/curves.dart @@ -11,9 +11,7 @@ import 'package:flutter/widgets.dart'; /// /// See also: /// * -final CurveTween standardEasing = CurveTween( - curve: const Cubic(0.4, 0.0, 0.2, 1), -); +const Curve standardEasing = Cubic(0.4, 0.0, 0.2, 1); /// The accelerate easing curve in the Material specification. /// @@ -22,9 +20,7 @@ final CurveTween standardEasing = CurveTween( /// /// See also: /// * -final CurveTween accelerateEasing = CurveTween( - curve: const Cubic(0.4, 0.0, 1.0, 1.0), -); +const Curve accelerateEasing = Cubic(0.4, 0.0, 1.0, 1.0); /// The decelerate easing curve in the Material specification. /// @@ -34,9 +30,7 @@ final CurveTween accelerateEasing = CurveTween( /// /// See also: /// * -final CurveTween decelerateEasing = CurveTween( - curve: const Cubic(0.0, 0.0, 0.2, 1.0), -); +const Curve decelerateEasing = Cubic(0.0, 0.0, 0.2, 1.0); // A tween that starts from 1.0 and ends at 0.0. final Tween _flippedTween = Tween( @@ -44,6 +38,17 @@ final Tween _flippedTween = Tween( end: 0.0, ); +class FlippedCurveTween extends CurveTween { + FlippedCurveTween({Curve curve}) + : assert(curve != null), + super(curve: curve); + + @override + double transform(double t) { + return 1.0 - super.transform(t); + } +} + /// Flips the incoming passed in [Animation] to start from 1.0 and end at 0.0. Animation flipTween(Animation animation) { return _flippedTween.animate(animation); From 17c82721c6850f2fee3bea83e0e54718817a078a Mon Sep 17 00:00:00 2001 From: Shi Hao Hong Date: Mon, 30 Dec 2019 17:09:55 -0800 Subject: [PATCH 10/37] Add FlippedCurveTween doc --- packages/animations/lib/src/utils/curves.dart | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/packages/animations/lib/src/utils/curves.dart b/packages/animations/lib/src/utils/curves.dart index f9f65a625034..1998ce0f8d5b 100644 --- a/packages/animations/lib/src/utils/curves.dart +++ b/packages/animations/lib/src/utils/curves.dart @@ -38,6 +38,13 @@ final Tween _flippedTween = Tween( end: 0.0, ); +/// Enables creating a flipped [CurveTween]. +/// +/// This creates a [CurveTween] that evaluates to a result that flips the +/// tween vertically. +/// +/// This tween sequence assumes that the evaluated result has to be a double +/// between 0.0 and 1.0. class FlippedCurveTween extends CurveTween { FlippedCurveTween({Curve curve}) : assert(curve != null), From 6da0b563e068dcef5a7318423a19559103b598c3 Mon Sep 17 00:00:00 2001 From: Shi Hao Hong Date: Mon, 30 Dec 2019 20:30:13 -0800 Subject: [PATCH 11/37] Refactor --- .../lib/src/shared_z_axis_transition.dart | 96 ++++++++++++------- 1 file changed, 63 insertions(+), 33 deletions(-) diff --git a/packages/animations/lib/src/shared_z_axis_transition.dart b/packages/animations/lib/src/shared_z_axis_transition.dart index 96cc2ba77266..e56774dabf31 100644 --- a/packages/animations/lib/src/shared_z_axis_transition.dart +++ b/packages/animations/lib/src/shared_z_axis_transition.dart @@ -195,51 +195,81 @@ class SharedZAxisTransition extends StatefulWidget { class _SharedZAxisTransitionState extends State { @override Widget build(BuildContext context) { - // Scale Transitions - final Animation _forwardEndScreenScaleTransition = widget.animation.drive( - Tween(begin: 0.80, end: 1.00) - .chain(CurveTween(curve: standardEasing))); - - final Animation _forwardStartScreenScaleTransition = widget.secondaryAnimation.drive( - Tween(begin: 1.00, end: 1.10) - .chain(CurveTween(curve: standardEasing))); - - // Fade Transitions - final Animation _forwardStartScreenFadeTransition = widget.secondaryAnimation.drive( - FlippedCurveTween(curve: accelerateEasing) - .chain(CurveTween(curve: const Interval(0.0, 0.3)))); - - final Animation _forwardEndScreenFadeTransition = widget.animation.drive( - CurveTween(curve: decelerateEasing) - .chain(CurveTween(curve: const Interval(0.3, 1.0)))); - return AnimatedBuilder( animation: widget.animation, builder: (BuildContext context, Widget child) { - return FadeTransition( - opacity: _forwardEndScreenFadeTransition, - child: ScaleTransition( - scale: _forwardEndScreenScaleTransition, - child: child, - ), + return _EnterTransition( + animation: widget.animation, + child: child, ); }, child: AnimatedBuilder( animation: widget.secondaryAnimation, builder: (BuildContext context, Widget child) { - return FadeTransition( - opacity: _forwardStartScreenFadeTransition, - child: Container( - color: Theme.of(context).canvasColor, - child: ScaleTransition( - scale: _forwardStartScreenScaleTransition, - child: child, - ), - ), + return _ExitTransition( + animation: widget.secondaryAnimation, + child: child, ); }, child: widget.child, ), ); } +} + +class _EnterTransition extends StatelessWidget { + _EnterTransition({ + this.animation, + this.child, + }); + + Animation animation; + Widget child; + + static Animatable fadeInTransition = CurveTween(curve: decelerateEasing) + .chain(CurveTween(curve: const Interval(0.3, 1.0))); + + static Animatable scaleInTransition = Tween(begin: 0.80, end: 1.00) + .chain(CurveTween(curve: standardEasing)); + + @override + Widget build(BuildContext context) { + return FadeTransition( + opacity: fadeInTransition.animate(animation), + child: ScaleTransition( + scale: scaleInTransition.animate(animation), + child: child, + ), + ); + } +} + +class _ExitTransition extends StatelessWidget { + _ExitTransition({ + this.animation, + this.child, + }); + + Animation animation; + Widget child; + + static Animatable fadeOutTransition = FlippedCurveTween(curve: accelerateEasing) + .chain(CurveTween(curve: const Interval(0.0, 0.3))); + + static Animatable scaleOutTransition = Tween(begin: 1.00, end: 1.10) + .chain(CurveTween(curve: standardEasing)); + + @override + Widget build(BuildContext context) { + return FadeTransition( + opacity: fadeOutTransition.animate(animation), + child: Container( + color: Theme.of(context).canvasColor, + child: ScaleTransition( + scale: scaleOutTransition.animate(animation), + child: child, + ), + ), + ); + } } \ No newline at end of file From ece7a36b71a5c10faf7589e62b9523f959d80e9c Mon Sep 17 00:00:00 2001 From: Shi Hao Hong Date: Mon, 30 Dec 2019 20:33:28 -0800 Subject: [PATCH 12/37] Implement reverse transitions --- .../lib/src/shared_z_axis_transition.dart | 135 ++++++++++++++++-- 1 file changed, 127 insertions(+), 8 deletions(-) diff --git a/packages/animations/lib/src/shared_z_axis_transition.dart b/packages/animations/lib/src/shared_z_axis_transition.dart index e56774dabf31..2cc2ee0f58c5 100644 --- a/packages/animations/lib/src/shared_z_axis_transition.dart +++ b/packages/animations/lib/src/shared_z_axis_transition.dart @@ -193,23 +193,142 @@ class SharedZAxisTransition extends StatefulWidget { } class _SharedZAxisTransitionState extends State { + AnimationStatus _effectiveAnimationStatus; + AnimationStatus _effectiveSecondaryAnimationStatus; + + @override + void initState() { + super.initState(); + _effectiveAnimationStatus = widget.animation.status; + _effectiveSecondaryAnimationStatus = widget.secondaryAnimation.status; + widget.animation.addStatusListener(_animationListener); + widget.secondaryAnimation.addStatusListener(_secondaryAnimationListener); + } + + void _animationListener(AnimationStatus animationStatus) { + _effectiveAnimationStatus = _calculateEffectiveAnimationStatus( + lastEffective: _effectiveAnimationStatus, + current: animationStatus, + ); + } + + void _secondaryAnimationListener(AnimationStatus animationStatus) { + _effectiveSecondaryAnimationStatus = _calculateEffectiveAnimationStatus( + lastEffective: _effectiveSecondaryAnimationStatus, + current: animationStatus, + ); + } + + // When a transition is interrupted midway we just want to play the ongoing + // animation in reverse. Switching to the actual reverse transition would + // yield a disjoint experience since the forward and reverse transitions are + // very different. + AnimationStatus _calculateEffectiveAnimationStatus({ + @required AnimationStatus lastEffective, + @required AnimationStatus current, + }) { + assert(current != null); + assert(lastEffective != null); + switch (current) { + case AnimationStatus.dismissed: + case AnimationStatus.completed: + return current; + case AnimationStatus.forward: + switch (lastEffective) { + case AnimationStatus.dismissed: + case AnimationStatus.completed: + case AnimationStatus.forward: + return current; + case AnimationStatus.reverse: + return lastEffective; + } + break; + case AnimationStatus.reverse: + switch (lastEffective) { + case AnimationStatus.dismissed: + case AnimationStatus.completed: + case AnimationStatus.reverse: + return current; + case AnimationStatus.forward: + return lastEffective; + } + break; + } + return null; // unreachable + } + + @override + void didUpdateWidget(SharedZAxisTransition oldWidget) { + super.didUpdateWidget(oldWidget); + if (oldWidget.animation != widget.animation) { + oldWidget.animation.removeStatusListener(_animationListener); + widget.animation.addStatusListener(_animationListener); + _animationListener(widget.animation.status); + } + if (oldWidget.secondaryAnimation != widget.secondaryAnimation) { + oldWidget.secondaryAnimation + .removeStatusListener(_secondaryAnimationListener); + widget.secondaryAnimation.addStatusListener(_secondaryAnimationListener); + _secondaryAnimationListener(widget.secondaryAnimation.status); + } + } + + @override + void dispose() { + widget.animation.removeStatusListener(_animationListener); + widget.secondaryAnimation.removeStatusListener(_secondaryAnimationListener); + super.dispose(); + } + + static final Tween _flippedTween = Tween( + begin: 1.0, + end: 0.0, + ); + static Animation _flip(Animation animation) { + return _flippedTween.animate(animation); + } + @override Widget build(BuildContext context) { return AnimatedBuilder( animation: widget.animation, builder: (BuildContext context, Widget child) { - return _EnterTransition( - animation: widget.animation, - child: child, - ); + assert(_effectiveAnimationStatus != null); + switch (_effectiveAnimationStatus) { + case AnimationStatus.forward: + return _EnterTransition( + animation: widget.animation, + child: child, + ); + case AnimationStatus.dismissed: + case AnimationStatus.reverse: + case AnimationStatus.completed: + return _ExitTransition( + animation: _flip(widget.animation), + child: child, + ); + } + return null; // unreachable }, child: AnimatedBuilder( animation: widget.secondaryAnimation, builder: (BuildContext context, Widget child) { - return _ExitTransition( - animation: widget.secondaryAnimation, - child: child, - ); + assert(_effectiveSecondaryAnimationStatus != null); + switch (_effectiveSecondaryAnimationStatus) { + case AnimationStatus.forward: + return _ExitTransition( + animation: widget.secondaryAnimation, + child: child, + ); + case AnimationStatus.dismissed: + case AnimationStatus.reverse: + case AnimationStatus.completed: + return _EnterTransition( + animation: _flip(widget.secondaryAnimation), + child: child, + ); + } + return null; // unreachable }, child: widget.child, ), From 03abd8dc428863cbe02194be31eb17581a1d4a6e Mon Sep 17 00:00:00 2001 From: Shi Hao Hong Date: Mon, 30 Dec 2019 20:43:19 -0800 Subject: [PATCH 13/37] Add reverse test --- .../lib/src/shared_z_axis_transition.dart | 12 +-- .../test/shared_z_axis_transition_test.dart | 77 +++++++++++++++++++ 2 files changed, 83 insertions(+), 6 deletions(-) diff --git a/packages/animations/lib/src/shared_z_axis_transition.dart b/packages/animations/lib/src/shared_z_axis_transition.dart index 2cc2ee0f58c5..b5f1ddb4b980 100644 --- a/packages/animations/lib/src/shared_z_axis_transition.dart +++ b/packages/animations/lib/src/shared_z_axis_transition.dart @@ -337,13 +337,13 @@ class _SharedZAxisTransitionState extends State { } class _EnterTransition extends StatelessWidget { - _EnterTransition({ + const _EnterTransition({ this.animation, this.child, }); - Animation animation; - Widget child; + final Animation animation; + final Widget child; static Animatable fadeInTransition = CurveTween(curve: decelerateEasing) .chain(CurveTween(curve: const Interval(0.3, 1.0))); @@ -364,13 +364,13 @@ class _EnterTransition extends StatelessWidget { } class _ExitTransition extends StatelessWidget { - _ExitTransition({ + const _ExitTransition({ this.animation, this.child, }); - Animation animation; - Widget child; + final Animation animation; + final Widget child; static Animatable fadeOutTransition = FlippedCurveTween(curve: accelerateEasing) .chain(CurveTween(curve: const Interval(0.0, 0.3))); diff --git a/packages/animations/test/shared_z_axis_transition_test.dart b/packages/animations/test/shared_z_axis_transition_test.dart index b6e1ec843caa..f3cbd8fe439a 100644 --- a/packages/animations/test/shared_z_axis_transition_test.dart +++ b/packages/animations/test/shared_z_axis_transition_test.dart @@ -107,6 +107,83 @@ void main() { expect(find.text(topRoute), findsOneWidget); }, ); + + testWidgets( + 'SharedZAxisTransition runs in reverse', + (WidgetTester tester) async { + final GlobalKey navigator = GlobalKey(); + const String bottomRoute = '/'; + const String topRoute = '/a'; + + await tester.pumpWidget( + _TestWidget(navigatorKey: navigator), + ); + + navigator.currentState.pushNamed('/a'); + await tester.pumpAndSettle(); + + expect(find.text(topRoute), findsOneWidget); + expect(_getScale(topRoute, tester), 1.0); + expect(_getOpacity(topRoute, tester), 1.0); + expect(find.text(bottomRoute), findsNothing); + + navigator.currentState.pop(); + await tester.pump(); + + // Top route is full size and fully visible. + expect(find.text(topRoute), findsOneWidget); + expect(_getScale(topRoute, tester), 1.0); + expect(_getOpacity(topRoute, tester), 1.0); + // Bottom route is at 80% of full size and not visible yet. + expect(find.text(bottomRoute), findsOneWidget); + expect(_getScale(bottomRoute, tester), 0.8); + expect(_getOpacity(bottomRoute, tester), 0.0); + + // Jump 3/10ths of the way through the transition, bottom route + // should be be completely faded out while the top route + // is also completely faded out. + // Transition time: 300ms, 3/10 * 300ms = 90ms + await tester.pump(const Duration(milliseconds: 90)); + + // Bottom route is now invisible + expect(find.text(topRoute), findsOneWidget); + expect(_getOpacity(topRoute, tester), 0.0); + // Top route is still invisible, but scaling up. + expect(find.text(bottomRoute), findsOneWidget); + expect(_getOpacity(bottomRoute, tester), moreOrLessEquals(0, epsilon: 0.005)); + double bottomScale = _getScale(bottomRoute, tester); + expect(bottomScale, greaterThan(0.8)); + expect(bottomScale, lessThan(1.0)); + + // Jump to the middle of fading in + await tester.pump(const Duration(milliseconds: 90)); + // Top route is still invisible + expect(find.text(topRoute), findsOneWidget); + expect(_getOpacity(topRoute, tester), 0.0); + // Bottom route is fading in + expect(find.text(bottomRoute), findsOneWidget); + expect(_getOpacity(bottomRoute, tester), greaterThan(0)); + expect(_getOpacity(bottomRoute, tester), lessThan(1.0)); + bottomScale = _getScale(bottomRoute, tester); + expect(bottomScale, greaterThan(0.8)); + expect(bottomScale, lessThan(1.0)); + + // Jump to the end of the transition + await tester.pump(const Duration(milliseconds: 120)); + // Top route is not visible. + expect(find.text(topRoute), findsOneWidget); + expect(_getScale(topRoute, tester), 1.1); + expect(_getOpacity(topRoute, tester), 0.0); + // Bottom route fully scaled in and visible. + expect(find.text(bottomRoute), findsOneWidget); + expect(_getScale(bottomRoute, tester), 1.0); + expect(_getOpacity(bottomRoute, tester), 1.0); + + await tester.pump(const Duration(milliseconds: 1)); + expect(find.text(topRoute), findsNothing); + expect(find.text(bottomRoute), findsOneWidget); + }, + ); } double _getOpacity(String key, WidgetTester tester) { From fdf87e807065932e6fa8223a60a5210931025ae7 Mon Sep 17 00:00:00 2001 From: Shi Hao Hong Date: Mon, 30 Dec 2019 20:59:35 -0800 Subject: [PATCH 14/37] Interrupt test --- .../test/shared_z_axis_transition_test.dart | 73 +++++++++++++++++++ 1 file changed, 73 insertions(+) diff --git a/packages/animations/test/shared_z_axis_transition_test.dart b/packages/animations/test/shared_z_axis_transition_test.dart index f3cbd8fe439a..9d43d1c31fdc 100644 --- a/packages/animations/test/shared_z_axis_transition_test.dart +++ b/packages/animations/test/shared_z_axis_transition_test.dart @@ -184,6 +184,79 @@ void main() { expect(find.text(bottomRoute), findsOneWidget); }, ); + + testWidgets( + 'FadeThroughTransition does not jump when interrupted', + (WidgetTester tester) async { + final GlobalKey navigator = GlobalKey(); + const String bottomRoute = '/'; + const String topRoute = '/a'; + + await tester.pumpWidget( + _TestWidget( + navigatorKey: navigator, + ), + ); + expect(find.text(bottomRoute), findsOneWidget); + expect(find.text(topRoute), findsNothing); + + navigator.currentState.pushNamed(topRoute); + await tester.pump(); + + // Jump to halfway point of transition. + await tester.pump(const Duration(milliseconds: 150)); + // Bottom route is fully faded out. + expect(find.text(bottomRoute), findsOneWidget); + expect(_getOpacity(bottomRoute, tester), 0.0); + double halfwayBottomScale = _getScale(bottomRoute, tester); + expect(halfwayBottomScale, greaterThan(1.0)); + expect(halfwayBottomScale, lessThan(1.1)); + + // Top route is fading/scaling in. + expect(find.text(topRoute), findsOneWidget); + double halfwayTopScale = _getScale(topRoute, tester); + double halfwayTopOpacity = _getOpacity(topRoute, tester); + expect(halfwayTopScale, greaterThan(0.8)); + expect(halfwayTopScale, lessThan(1.0)); + expect(halfwayTopOpacity, greaterThan(0.0)); + expect(halfwayTopOpacity, lessThan(1.0)); + + // Interrupt the transition with a pop. + navigator.currentState.pop(); + await tester.pump(); + + // Nothing should change. + expect(find.text(bottomRoute), findsOneWidget); + expect(_getScale(bottomRoute, tester), halfwayBottomScale); + expect(_getOpacity(bottomRoute, tester), 0.0); + expect(find.text(topRoute), findsOneWidget); + expect(_getScale(topRoute, tester), halfwayTopScale); + expect(_getOpacity(topRoute, tester), halfwayTopOpacity); + + // Jump to the 1/4 (75 ms) point of transition + await tester.pump(const Duration(milliseconds: 75)); + expect(find.text(bottomRoute), findsOneWidget); + expect(_getScale(bottomRoute, tester), greaterThan(1.0)); + expect(_getScale(bottomRoute, tester), lessThan(1.1)); + expect(_getScale(bottomRoute, tester), lessThan(halfwayBottomScale)); + expect(_getOpacity(bottomRoute, tester), greaterThan(0.0)); + expect(_getOpacity(bottomRoute, tester), lessThan(1.0)); + + + // Jump to the end. + await tester.pump(const Duration(milliseconds: 75)); + expect(find.text(bottomRoute), findsOneWidget); + expect(_getScale(bottomRoute, tester), 1.0); + expect(_getOpacity(bottomRoute, tester), 1.0); + expect(find.text(topRoute), findsOneWidget); + expect(_getScale(topRoute, tester), 0.80); + expect(_getOpacity(topRoute, tester), 0.0); + + await tester.pump(const Duration(milliseconds: 1)); + expect(find.text(topRoute), findsNothing); + expect(find.text(bottomRoute), findsOneWidget); + }, + ); } double _getOpacity(String key, WidgetTester tester) { From 1e918b11e966070b8e5371cd75fe3a5837adb686 Mon Sep 17 00:00:00 2001 From: Shi Hao Hong Date: Mon, 30 Dec 2019 21:00:08 -0800 Subject: [PATCH 15/37] Add final keyword --- packages/animations/test/shared_z_axis_transition_test.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/animations/test/shared_z_axis_transition_test.dart b/packages/animations/test/shared_z_axis_transition_test.dart index 9d43d1c31fdc..910b36dcb0c1 100644 --- a/packages/animations/test/shared_z_axis_transition_test.dart +++ b/packages/animations/test/shared_z_axis_transition_test.dart @@ -208,14 +208,14 @@ void main() { // Bottom route is fully faded out. expect(find.text(bottomRoute), findsOneWidget); expect(_getOpacity(bottomRoute, tester), 0.0); - double halfwayBottomScale = _getScale(bottomRoute, tester); + final double halfwayBottomScale = _getScale(bottomRoute, tester); expect(halfwayBottomScale, greaterThan(1.0)); expect(halfwayBottomScale, lessThan(1.1)); // Top route is fading/scaling in. expect(find.text(topRoute), findsOneWidget); - double halfwayTopScale = _getScale(topRoute, tester); - double halfwayTopOpacity = _getOpacity(topRoute, tester); + final double halfwayTopScale = _getScale(topRoute, tester); + final double halfwayTopOpacity = _getOpacity(topRoute, tester); expect(halfwayTopScale, greaterThan(0.8)); expect(halfwayTopScale, lessThan(1.0)); expect(halfwayTopOpacity, greaterThan(0.0)); From da1204e4f300ab74962480392549cbda569b5de3 Mon Sep 17 00:00:00 2001 From: Shi Hao Hong Date: Mon, 30 Dec 2019 21:02:26 -0800 Subject: [PATCH 16/37] Add state test --- .../test/shared_z_axis_transition_test.dart | 91 +++++++++++++++++++ 1 file changed, 91 insertions(+) diff --git a/packages/animations/test/shared_z_axis_transition_test.dart b/packages/animations/test/shared_z_axis_transition_test.dart index 910b36dcb0c1..db4de3f0dc5f 100644 --- a/packages/animations/test/shared_z_axis_transition_test.dart +++ b/packages/animations/test/shared_z_axis_transition_test.dart @@ -257,6 +257,97 @@ void main() { expect(find.text(bottomRoute), findsOneWidget); }, ); + + testWidgets( + 'State is not lost when transitioning', + (WidgetTester tester) async { + final GlobalKey navigator = GlobalKey(); + const String bottomRoute = '/'; + const String topRoute = '/a'; + + await tester.pumpWidget( + _TestWidget( + navigatorKey: navigator, + contentBuilder: (RouteSettings settings) { + return _StatefulTestWidget( + key: ValueKey(settings.name), + name: settings.name, + ); + }, + ), + ); + + final _StatefulTestWidgetState bottomState = tester.state( + find.byKey(const ValueKey(bottomRoute)), + ); + expect(bottomState.widget.name, bottomRoute); + + navigator.currentState.pushNamed(topRoute); + await tester.pump(); + await tester.pump(); + + expect( + tester.state(find.byKey(const ValueKey(bottomRoute))), + bottomState, + ); + final _StatefulTestWidgetState topState = tester.state( + find.byKey(const ValueKey(topRoute)), + ); + expect(topState.widget.name, topRoute); + + await tester.pump(const Duration(milliseconds: 150)); + expect( + tester.state(find.byKey(const ValueKey(bottomRoute))), + bottomState, + ); + expect( + tester.state(find.byKey(const ValueKey(topRoute))), + topState, + ); + + await tester.pumpAndSettle(); + expect( + tester.state(find.byKey( + const ValueKey(bottomRoute), + skipOffstage: false, + )), + bottomState, + ); + expect( + tester.state(find.byKey(const ValueKey(topRoute))), + topState, + ); + + navigator.currentState.pop(); + await tester.pump(); + + expect( + tester.state(find.byKey(const ValueKey(bottomRoute))), + bottomState, + ); + expect( + tester.state(find.byKey(const ValueKey(topRoute))), + topState, + ); + + await tester.pump(const Duration(milliseconds: 150)); + expect( + tester.state(find.byKey(const ValueKey(bottomRoute))), + bottomState, + ); + expect( + tester.state(find.byKey(const ValueKey(topRoute))), + topState, + ); + + await tester.pumpAndSettle(); + expect( + tester.state(find.byKey(const ValueKey(bottomRoute))), + bottomState, + ); + expect(find.byKey(const ValueKey(topRoute)), findsNothing); + }, + ); } double _getOpacity(String key, WidgetTester tester) { From 2cdb95622f37f4c1257795e82ec08508ad0c917c Mon Sep 17 00:00:00 2001 From: Shi Hao Hong Date: Mon, 30 Dec 2019 21:07:04 -0800 Subject: [PATCH 17/37] Newline at end of file --- packages/animations/lib/src/utils/curves.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/animations/lib/src/utils/curves.dart b/packages/animations/lib/src/utils/curves.dart index 1998ce0f8d5b..e1495b77c0b1 100644 --- a/packages/animations/lib/src/utils/curves.dart +++ b/packages/animations/lib/src/utils/curves.dart @@ -59,4 +59,4 @@ class FlippedCurveTween extends CurveTween { /// Flips the incoming passed in [Animation] to start from 1.0 and end at 0.0. Animation flipTween(Animation animation) { return _flippedTween.animate(animation); -} \ No newline at end of file +} From 722254c5a47c1f42127b9e31b6e60667088ad9ca Mon Sep 17 00:00:00 2001 From: Shi Hao Hong Date: Thu, 2 Jan 2020 10:33:17 -0800 Subject: [PATCH 18/37] Add documentation --- packages/animations/lib/src/utils/curves.dart | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/animations/lib/src/utils/curves.dart b/packages/animations/lib/src/utils/curves.dart index e1495b77c0b1..9760f5eb5134 100644 --- a/packages/animations/lib/src/utils/curves.dart +++ b/packages/animations/lib/src/utils/curves.dart @@ -46,14 +46,14 @@ final Tween _flippedTween = Tween( /// This tween sequence assumes that the evaluated result has to be a double /// between 0.0 and 1.0. class FlippedCurveTween extends CurveTween { - FlippedCurveTween({Curve curve}) - : assert(curve != null), - super(curve: curve); + /// Creates a vertically flipped [CurveTween]. + FlippedCurveTween({ + @required Curve curve, + }) : assert(curve != null), + super(curve: curve); @override - double transform(double t) { - return 1.0 - super.transform(t); - } + double transform(double t) => 1.0 - super.transform(t); } /// Flips the incoming passed in [Animation] to start from 1.0 and end at 0.0. From e26da20de7f7ff8c475f0ab9ebb0204fad2b8dfc Mon Sep 17 00:00:00 2001 From: Shi Hao Hong Date: Thu, 2 Jan 2020 10:58:43 -0800 Subject: [PATCH 19/37] Implement shared x axis transition --- packages/animations/lib/animations.dart | 1 + .../lib/src/shared_x_axis_transition.dart | 399 ++++++++++++++++++ 2 files changed, 400 insertions(+) create mode 100644 packages/animations/lib/src/shared_x_axis_transition.dart diff --git a/packages/animations/lib/animations.dart b/packages/animations/lib/animations.dart index ce0beba545c5..7b6fb951c1d5 100644 --- a/packages/animations/lib/animations.dart +++ b/packages/animations/lib/animations.dart @@ -5,4 +5,5 @@ export 'src/fade_through_transition.dart'; export 'src/open_container.dart'; export 'src/page_transition_switcher.dart'; +export 'src/shared_x_axis_transition.dart'; export 'src/shared_z_axis_transition.dart'; diff --git a/packages/animations/lib/src/shared_x_axis_transition.dart b/packages/animations/lib/src/shared_x_axis_transition.dart new file mode 100644 index 000000000000..ecdbd33c06c6 --- /dev/null +++ b/packages/animations/lib/src/shared_x_axis_transition.dart @@ -0,0 +1,399 @@ +// Copyright 2019 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/animation.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; + +import 'utils/curves.dart'; + +/// Used by [PageTransitionsTheme] to define a page route transition animation +/// in which outgoing and incoming elements share a horizontal fade transition. +/// +/// The shared axis pattern provides the transition animation between UI elements +/// that have a spatial or navigational relationship. For example, +/// transitioning from one page of a sign up page to the next one. +/// +/// +/// The following example shows how the SharedXAxisPageTransitionsBuilder can +/// be used in a [PageTransitionsTheme] to change the default transitions +/// of [MaterialPageRoute]s. +/// +/// ```dart +/// MaterialApp( +/// theme: ThemeData( +/// pageTransitionsTheme: PageTransitionsTheme( +/// builders: { +/// TargetPlatform.android: SharedXAxisPageTransitionsBuilder(), +/// TargetPlatform.iOS: SharedXAxisPageTransitionsBuilder(), +/// }, +/// ), +/// ), +/// routes: { +/// '/': (BuildContext context) { +/// return Container( +/// color: Colors.red, +/// child: Center( +/// child: MaterialButton( +/// child: Text('Push route'), +/// onPressed: () { +/// Navigator.of(context).pushNamed('/a'); +/// }, +/// ), +/// ), +/// ); +/// }, +/// '/a' : (BuildContext context) { +/// return Container( +/// color: Colors.blue, +/// child: Center( +/// child: MaterialButton( +/// child: Text('Pop route'), +/// onPressed: () { +/// Navigator.of(context).pop(); +/// }, +/// ), +/// ), +/// ); +/// }, +/// }, +/// ); +/// ``` +class SharedXAxisPageTransitionsBuilder extends PageTransitionsBuilder { + /// Construct a [SharedXAxisPageTransitionsBuilder]. + const SharedXAxisPageTransitionsBuilder(); + + @override + Widget buildTransitions( + PageRoute route, + BuildContext context, + Animation animation, + Animation secondaryAnimation, + Widget child, + ) { + return SharedXAxisTransition( + animation: animation, + secondaryAnimation: secondaryAnimation, + child: child, + ); + } +} + +/// Defines a transition in which outgoing and incoming elements share a horizontal +/// transition. +/// +/// The shared axis pattern provides the transition animation between UI elements +/// that have a spatial or navigational relationship. For example, +/// transitioning from one page of a sign up page to the next one. +/// +/// Consider using [SharedXAxisTransition] within a +/// [PageTransitionsTheme] if you want to apply this kind of transition to +/// [MaterialPageRoute] transitions within a Navigator (see +/// [SharedXAxisPageTransitionsBuilder] for example code). +/// +/// This transition can also be used directly in a +/// [PageTransitionSwitcher.transitionBuilder] to transition +/// from one widget to another as seen in the following example: +/// ```dart +/// int _selectedIndex = 0; +/// +/// final List _colors = [Colors.white, Colors.red, Colors.yellow]; +/// +/// @override +/// Widget build(BuildContext context) { +/// return Scaffold( +/// appBar: AppBar( +/// title: const Text('Page Transition Example'), +/// ), +/// body: PageTransitionSwitcher( +/// // reverse: true, // uncomment to see transition in reverse +/// transitionBuilder: ( +/// Widget child, +/// Animation primaryAnimation, +/// Animation secondaryAnimation, +/// ) { +/// return SharedXAxisTransition( +/// animation: primaryAnimation, +/// secondaryAnimation: secondaryAnimation, +/// child: child, +/// ); +/// }, +/// child: Container( +/// key: ValueKey(_selectedIndex), +/// color: _colors[_selectedIndex], +/// child: Center( +/// child: FlutterLogo(size: 300), +/// ) +/// ), +/// ), +/// bottomNavigationBar: BottomNavigationBar( +/// items: const [ +/// BottomNavigationBarItem( +/// icon: Icon(Icons.home), +/// title: Text('White'), +/// ), +/// BottomNavigationBarItem( +/// icon: Icon(Icons.business), +/// title: Text('Red'), +/// ), +/// BottomNavigationBarItem( +/// icon: Icon(Icons.school), +/// title: Text('Yellow'), +/// ), +/// ], +/// currentIndex: _selectedIndex, +/// onTap: (int index) { +/// setState(() { +/// _selectedIndex = index; +/// }); +/// }, +/// ), +/// ); +/// } +/// ``` +class SharedXAxisTransition extends StatefulWidget { + /// Creates a [SharedXAxisTransition]. + /// + /// The [animation] and [secondaryAnimation] argument are required and must + /// not be null. + const SharedXAxisTransition({ + Key key, + @required this.animation, + @required this.secondaryAnimation, + this.child, + }) : super(key: key); + + /// The animation that drives the [child]'s entrance and exit. + /// + /// See also: + /// + /// * [TransitionRoute.animate], which is the value given to this property + /// when it is used as a page transition. + final Animation animation; + + /// The animation that transitions [child] when new content is pushed on top + /// of it. + /// + /// See also: + /// + /// * [TransitionRoute.secondaryAnimation], which is the value given to this + /// property when the it is used as a page transition. + final Animation secondaryAnimation; + + /// The widget below this widget in the tree. + /// + /// This widget will transition in and out as driven by [animation] and + /// [secondaryAnimation]. + final Widget child; + + @override + _SharedXAxisTransitionState createState() => _SharedXAxisTransitionState(); +} + +class _SharedXAxisTransitionState extends State { + AnimationStatus _effectiveAnimationStatus; + AnimationStatus _effectiveSecondaryAnimationStatus; + + @override + void initState() { + super.initState(); + _effectiveAnimationStatus = widget.animation.status; + _effectiveSecondaryAnimationStatus = widget.secondaryAnimation.status; + widget.animation.addStatusListener(_animationListener); + widget.secondaryAnimation.addStatusListener(_secondaryAnimationListener); + } + + void _animationListener(AnimationStatus animationStatus) { + _effectiveAnimationStatus = _calculateEffectiveAnimationStatus( + lastEffective: _effectiveAnimationStatus, + current: animationStatus, + ); + } + + void _secondaryAnimationListener(AnimationStatus animationStatus) { + _effectiveSecondaryAnimationStatus = _calculateEffectiveAnimationStatus( + lastEffective: _effectiveSecondaryAnimationStatus, + current: animationStatus, + ); + } + + // When a transition is interrupted midway we just want to play the ongoing + // animation in reverse. Switching to the actual reverse transition would + // yield a disjoint experience since the forward and reverse transitions are + // very different. + AnimationStatus _calculateEffectiveAnimationStatus({ + @required AnimationStatus lastEffective, + @required AnimationStatus current, + }) { + assert(current != null); + assert(lastEffective != null); + switch (current) { + case AnimationStatus.dismissed: + case AnimationStatus.completed: + return current; + case AnimationStatus.forward: + switch (lastEffective) { + case AnimationStatus.dismissed: + case AnimationStatus.completed: + case AnimationStatus.forward: + return current; + case AnimationStatus.reverse: + return lastEffective; + } + break; + case AnimationStatus.reverse: + switch (lastEffective) { + case AnimationStatus.dismissed: + case AnimationStatus.completed: + case AnimationStatus.reverse: + return current; + case AnimationStatus.forward: + return lastEffective; + } + break; + } + return null; // unreachable + } + + @override + void didUpdateWidget(SharedXAxisTransition oldWidget) { + super.didUpdateWidget(oldWidget); + if (oldWidget.animation != widget.animation) { + oldWidget.animation.removeStatusListener(_animationListener); + widget.animation.addStatusListener(_animationListener); + _animationListener(widget.animation.status); + } + if (oldWidget.secondaryAnimation != widget.secondaryAnimation) { + oldWidget.secondaryAnimation + .removeStatusListener(_secondaryAnimationListener); + widget.secondaryAnimation.addStatusListener(_secondaryAnimationListener); + _secondaryAnimationListener(widget.secondaryAnimation.status); + } + } + + @override + void dispose() { + widget.animation.removeStatusListener(_animationListener); + widget.secondaryAnimation.removeStatusListener(_secondaryAnimationListener); + super.dispose(); + } + + static final Tween _flippedTween = Tween( + begin: 1.0, + end: 0.0, + ); + static Animation _flip(Animation animation) { + return _flippedTween.animate(animation); + } + + @override + Widget build(BuildContext context) { + return AnimatedBuilder( + animation: widget.animation, + builder: (BuildContext context, Widget child) { + assert(_effectiveAnimationStatus != null); + switch (_effectiveAnimationStatus) { + case AnimationStatus.forward: + return _EnterTransition( + animation: widget.animation, + child: child, + ); + case AnimationStatus.dismissed: + case AnimationStatus.reverse: + case AnimationStatus.completed: + return _ExitTransition( + animation: _flip(widget.animation), + child: child, + ); + } + return null; // unreachable + }, + child: AnimatedBuilder( + animation: widget.secondaryAnimation, + builder: (BuildContext context, Widget child) { + assert(_effectiveSecondaryAnimationStatus != null); + switch (_effectiveSecondaryAnimationStatus) { + case AnimationStatus.forward: + return _ExitTransition( + animation: widget.secondaryAnimation, + child: child, + ); + case AnimationStatus.dismissed: + case AnimationStatus.reverse: + case AnimationStatus.completed: + return _EnterTransition( + animation: _flip(widget.secondaryAnimation), + child: child, + ); + } + return null; // unreachable + }, + child: widget.child, + ), + ); + } +} + +class _EnterTransition extends StatelessWidget { + const _EnterTransition({ + this.animation, + this.child, + }); + + final Animation animation; + final Widget child; + + static Animatable fadeInTransition = CurveTween(curve: decelerateEasing) + .chain(CurveTween(curve: const Interval(0.3, 1.0))); + + static Animatable slideInTransition = Tween( + begin: const Offset(30, 0.0), + end: Offset.zero, + ).chain(CurveTween(curve: standardEasing)); + + @override + Widget build(BuildContext context) { + return FadeTransition( + opacity: fadeInTransition.animate(animation), + child: Transform.translate( + offset: slideInTransition.evaluate(animation), + child: child, + ), + ); + } +} + +class _ExitTransition extends StatelessWidget { + const _ExitTransition({ + this.animation, + this.child, + }); + + final Animation animation; + final Widget child; + + static Animatable fadeOutTransition = FlippedCurveTween(curve: accelerateEasing) + .chain(CurveTween(curve: const Interval(0.0, 0.3))); + + static Animatable slideOutTransition = Tween( + begin: Offset.zero, + end: const Offset(30, 0.0), + ).chain(CurveTween(curve: standardEasing)); + + @override + Widget build(BuildContext context) { + print(slideOutTransition.animate(animation).value); + return FadeTransition( + opacity: fadeOutTransition.animate(animation), + child: Container( + color: Theme.of(context).canvasColor, + child: Transform.translate( + offset: slideOutTransition.evaluate(animation), + child: child, + ), + ), + ); + } +} From faa5e124643a94623c918b3a86ca78fd7435f738 Mon Sep 17 00:00:00 2001 From: Shi Hao Hong Date: Thu, 2 Jan 2020 11:00:08 -0800 Subject: [PATCH 20/37] remove print statement --- packages/animations/lib/src/shared_x_axis_transition.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/animations/lib/src/shared_x_axis_transition.dart b/packages/animations/lib/src/shared_x_axis_transition.dart index ecdbd33c06c6..7b5490432087 100644 --- a/packages/animations/lib/src/shared_x_axis_transition.dart +++ b/packages/animations/lib/src/shared_x_axis_transition.dart @@ -384,7 +384,6 @@ class _ExitTransition extends StatelessWidget { @override Widget build(BuildContext context) { - print(slideOutTransition.animate(animation).value); return FadeTransition( opacity: fadeOutTransition.animate(animation), child: Container( From db8ae881d20eaa117bb61d22aa904381e8dad322 Mon Sep 17 00:00:00 2001 From: Shi Hao Hong Date: Thu, 2 Jan 2020 11:04:00 -0800 Subject: [PATCH 21/37] Create shared-x-axis transition tests w/o translation testing --- .../test/shared_x_axis_transition_test.dart | 428 ++++++++++++++++++ 1 file changed, 428 insertions(+) create mode 100644 packages/animations/test/shared_x_axis_transition_test.dart diff --git a/packages/animations/test/shared_x_axis_transition_test.dart b/packages/animations/test/shared_x_axis_transition_test.dart new file mode 100644 index 000000000000..8b54c87978a8 --- /dev/null +++ b/packages/animations/test/shared_x_axis_transition_test.dart @@ -0,0 +1,428 @@ +// Copyright 2019 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:animations/src/shared_x_axis_transition.dart'; +import 'package:flutter/animation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:flutter/widgets.dart'; + +void main() { + testWidgets( + 'FadeThroughPageTransitionsBuilder builds a FadeThroughTransition', + (WidgetTester tester) async { + final AnimationController animation = AnimationController( + vsync: const TestVSync(), + ); + final AnimationController secondaryAnimation = AnimationController( + vsync: const TestVSync(), + ); + + await tester.pumpWidget( + const SharedXAxisPageTransitionsBuilder().buildTransitions( + null, + null, + animation, + secondaryAnimation, + const Placeholder(), + ), + ); + + expect(find.byType(SharedXAxisTransition), findsOneWidget); + }, + ); + + testWidgets( + 'SharedXAxisTransition runs forward', + (WidgetTester tester) async { + final GlobalKey navigator = GlobalKey(); + const String bottomRoute = '/'; + const String topRoute = '/a'; + + await tester.pumpWidget( + _TestWidget(navigatorKey: navigator), + ); + + expect(find.text(bottomRoute), findsOneWidget); + // expect(_getScale(bottomRoute, tester), 1.0); + expect(_getOpacity(bottomRoute, tester), 1.0); + expect(find.text(topRoute), findsNothing); + + navigator.currentState.pushNamed(topRoute); + await tester.pump(); + await tester.pump(); + + // Bottom route is full size and fully visible. + expect(find.text(bottomRoute), findsOneWidget); + // expect(_getScale(bottomRoute, tester), 1.0); + expect(_getOpacity(bottomRoute, tester), 1.0); + // Top route is at 80% of full size and not visible yet. + expect(find.text(topRoute), findsOneWidget); + // expect(_getScale(topRoute, tester), 0.8); + expect(_getOpacity(topRoute, tester), 0.0); + + // Jump 3/10ths of the way through the transition, bottom route + // should be be completely faded out while the top route + // is also completely faded out. + // Transition time: 300ms, 3/10 * 300ms = 90ms + await tester.pump(const Duration(milliseconds: 90)); + + // Bottom route is now invisible + expect(find.text(bottomRoute), findsOneWidget); + expect(_getOpacity(bottomRoute, tester), 0.0); + // Top route is still invisible, but scaling up. + expect(find.text(topRoute), findsOneWidget); + expect(_getOpacity(topRoute, tester), 0.0); + // double topScale = _getScale(topRoute, tester); + // expect(topScale, greaterThan(0.8)); + // expect(topScale, lessThan(1.0)); + + // Jump to the middle of fading in + await tester.pump(const Duration(milliseconds: 90)); + // Bottom route is still invisible + expect(find.text(bottomRoute), findsOneWidget); + expect(_getOpacity(bottomRoute, tester), 0.0); + // Top route is fading in + expect(find.text(topRoute), findsOneWidget); + expect(_getOpacity(topRoute, tester), greaterThan(0)); + expect(_getOpacity(topRoute, tester), lessThan(1.0)); + // topScale = _getScale(topRoute, tester); + // expect(topScale, greaterThan(0.8)); + // expect(topScale, lessThan(1.0)); + + // Jump to the end of the transition + await tester.pump(const Duration(milliseconds: 120)); + // Bottom route is not visible. + expect(find.text(bottomRoute), findsOneWidget); + // expect(_getScale(bottomRoute, tester), 1.1); + expect(_getOpacity(bottomRoute, tester), 0.0); + // Top route fully scaled in and visible. + expect(find.text(topRoute), findsOneWidget); + // expect(_getScale(topRoute, tester), 1.0); + expect(_getOpacity(topRoute, tester), 1.0); + + await tester.pump(const Duration(milliseconds: 1)); + expect(find.text(bottomRoute), findsNothing); + expect(find.text(topRoute), findsOneWidget); + }, + ); + + testWidgets( + 'SharedXAxisTransition runs in reverse', + (WidgetTester tester) async { + final GlobalKey navigator = GlobalKey(); + const String bottomRoute = '/'; + const String topRoute = '/a'; + + await tester.pumpWidget( + _TestWidget(navigatorKey: navigator), + ); + + navigator.currentState.pushNamed('/a'); + await tester.pumpAndSettle(); + + expect(find.text(topRoute), findsOneWidget); + // expect(_getScale(topRoute, tester), 1.0); + expect(_getOpacity(topRoute, tester), 1.0); + expect(find.text(bottomRoute), findsNothing); + + navigator.currentState.pop(); + await tester.pump(); + + // Top route is full size and fully visible. + expect(find.text(topRoute), findsOneWidget); + // expect(_getScale(topRoute, tester), 1.0); + expect(_getOpacity(topRoute, tester), 1.0); + // Bottom route is at 80% of full size and not visible yet. + expect(find.text(bottomRoute), findsOneWidget); + // expect(_getScale(bottomRoute, tester), 0.8); + expect(_getOpacity(bottomRoute, tester), 0.0); + + // Jump 3/10ths of the way through the transition, bottom route + // should be be completely faded out while the top route + // is also completely faded out. + // Transition time: 300ms, 3/10 * 300ms = 90ms + await tester.pump(const Duration(milliseconds: 90)); + + // Bottom route is now invisible + expect(find.text(topRoute), findsOneWidget); + expect(_getOpacity(topRoute, tester), 0.0); + // Top route is still invisible, but scaling up. + expect(find.text(bottomRoute), findsOneWidget); + expect(_getOpacity(bottomRoute, tester), moreOrLessEquals(0, epsilon: 0.005)); + // double bottomScale = _getScale(bottomRoute, tester); + // expect(bottomScale, greaterThan(0.8)); + // expect(bottomScale, lessThan(1.0)); + + // Jump to the middle of fading in + await tester.pump(const Duration(milliseconds: 90)); + // Top route is still invisible + expect(find.text(topRoute), findsOneWidget); + expect(_getOpacity(topRoute, tester), 0.0); + // Bottom route is fading in + expect(find.text(bottomRoute), findsOneWidget); + expect(_getOpacity(bottomRoute, tester), greaterThan(0)); + expect(_getOpacity(bottomRoute, tester), lessThan(1.0)); + // bottomScale = _getScale(bottomRoute, tester); + // expect(bottomScale, greaterThan(0.8)); + // expect(bottomScale, lessThan(1.0)); + + // Jump to the end of the transition + await tester.pump(const Duration(milliseconds: 120)); + // Top route is not visible. + expect(find.text(topRoute), findsOneWidget); + // expect(_getScale(topRoute, tester), 1.1); + expect(_getOpacity(topRoute, tester), 0.0); + // Bottom route fully scaled in and visible. + expect(find.text(bottomRoute), findsOneWidget); + // expect(_getScale(bottomRoute, tester), 1.0); + expect(_getOpacity(bottomRoute, tester), 1.0); + + await tester.pump(const Duration(milliseconds: 1)); + expect(find.text(topRoute), findsNothing); + expect(find.text(bottomRoute), findsOneWidget); + }, + ); + + testWidgets( + 'FadeThroughTransition does not jump when interrupted', + (WidgetTester tester) async { + final GlobalKey navigator = GlobalKey(); + const String bottomRoute = '/'; + const String topRoute = '/a'; + + await tester.pumpWidget( + _TestWidget( + navigatorKey: navigator, + ), + ); + expect(find.text(bottomRoute), findsOneWidget); + expect(find.text(topRoute), findsNothing); + + navigator.currentState.pushNamed(topRoute); + await tester.pump(); + + // Jump to halfway point of transition. + await tester.pump(const Duration(milliseconds: 150)); + // Bottom route is fully faded out. + expect(find.text(bottomRoute), findsOneWidget); + expect(_getOpacity(bottomRoute, tester), 0.0); + // final double halfwayBottomScale = _getScale(bottomRoute, tester); + // expect(halfwayBottomScale, greaterThan(1.0)); + // expect(halfwayBottomScale, lessThan(1.1)); + + // Top route is fading/scaling in. + expect(find.text(topRoute), findsOneWidget); + // final double halfwayTopScale = _getScale(topRoute, tester); + final double halfwayTopOpacity = _getOpacity(topRoute, tester); + // expect(halfwayTopScale, greaterThan(0.8)); + // expect(halfwayTopScale, lessThan(1.0)); + expect(halfwayTopOpacity, greaterThan(0.0)); + expect(halfwayTopOpacity, lessThan(1.0)); + + // Interrupt the transition with a pop. + navigator.currentState.pop(); + await tester.pump(); + + // Nothing should change. + expect(find.text(bottomRoute), findsOneWidget); + // expect(_getScale(bottomRoute, tester), halfwayBottomScale); + expect(_getOpacity(bottomRoute, tester), 0.0); + expect(find.text(topRoute), findsOneWidget); + // expect(_getScale(topRoute, tester), halfwayTopScale); + expect(_getOpacity(topRoute, tester), halfwayTopOpacity); + + // Jump to the 1/4 (75 ms) point of transition + await tester.pump(const Duration(milliseconds: 75)); + expect(find.text(bottomRoute), findsOneWidget); + // expect(_getScale(bottomRoute, tester), greaterThan(1.0)); + // expect(_getScale(bottomRoute, tester), lessThan(1.1)); + // expect(_getScale(bottomRoute, tester), lessThan(halfwayBottomScale)); + expect(_getOpacity(bottomRoute, tester), greaterThan(0.0)); + expect(_getOpacity(bottomRoute, tester), lessThan(1.0)); + + + // Jump to the end. + await tester.pump(const Duration(milliseconds: 75)); + expect(find.text(bottomRoute), findsOneWidget); + // expect(_getScale(bottomRoute, tester), 1.0); + expect(_getOpacity(bottomRoute, tester), 1.0); + expect(find.text(topRoute), findsOneWidget); + // expect(_getScale(topRoute, tester), 0.80); + expect(_getOpacity(topRoute, tester), 0.0); + + await tester.pump(const Duration(milliseconds: 1)); + expect(find.text(topRoute), findsNothing); + expect(find.text(bottomRoute), findsOneWidget); + }, + ); + + testWidgets( + 'State is not lost when transitioning', + (WidgetTester tester) async { + final GlobalKey navigator = GlobalKey(); + const String bottomRoute = '/'; + const String topRoute = '/a'; + + await tester.pumpWidget( + _TestWidget( + navigatorKey: navigator, + contentBuilder: (RouteSettings settings) { + return _StatefulTestWidget( + key: ValueKey(settings.name), + name: settings.name, + ); + }, + ), + ); + + final _StatefulTestWidgetState bottomState = tester.state( + find.byKey(const ValueKey(bottomRoute)), + ); + expect(bottomState.widget.name, bottomRoute); + + navigator.currentState.pushNamed(topRoute); + await tester.pump(); + await tester.pump(); + + expect( + tester.state(find.byKey(const ValueKey(bottomRoute))), + bottomState, + ); + final _StatefulTestWidgetState topState = tester.state( + find.byKey(const ValueKey(topRoute)), + ); + expect(topState.widget.name, topRoute); + + await tester.pump(const Duration(milliseconds: 150)); + expect( + tester.state(find.byKey(const ValueKey(bottomRoute))), + bottomState, + ); + expect( + tester.state(find.byKey(const ValueKey(topRoute))), + topState, + ); + + await tester.pumpAndSettle(); + expect( + tester.state(find.byKey( + const ValueKey(bottomRoute), + skipOffstage: false, + )), + bottomState, + ); + expect( + tester.state(find.byKey(const ValueKey(topRoute))), + topState, + ); + + navigator.currentState.pop(); + await tester.pump(); + + expect( + tester.state(find.byKey(const ValueKey(bottomRoute))), + bottomState, + ); + expect( + tester.state(find.byKey(const ValueKey(topRoute))), + topState, + ); + + await tester.pump(const Duration(milliseconds: 150)); + expect( + tester.state(find.byKey(const ValueKey(bottomRoute))), + bottomState, + ); + expect( + tester.state(find.byKey(const ValueKey(topRoute))), + topState, + ); + + await tester.pumpAndSettle(); + expect( + tester.state(find.byKey(const ValueKey(bottomRoute))), + bottomState, + ); + expect(find.byKey(const ValueKey(topRoute)), findsNothing); + }, + ); +} + +double _getOpacity(String key, WidgetTester tester) { + final Finder finder = find.ancestor( + of: find.byKey(ValueKey(key)), + matching: find.byType(FadeTransition), + ); + return tester.widgetList(finder).fold(1.0, (double a, Widget widget) { + final FadeTransition transition = widget; + return a * transition.opacity.value; + }); +} + +// double _getScale(String key, WidgetTester tester) { +// final Finder finder = find.ancestor( +// of: find.byKey(ValueKey(key)), +// matching: find.byType(ScaleTransition), +// ); +// return tester.widgetList(finder).fold(1.0, (double a, Widget widget) { +// final ScaleTransition transition = widget; +// return a * transition.scale.value; +// }); +// } + +class _TestWidget extends StatelessWidget { + const _TestWidget({this.navigatorKey, this.contentBuilder}); + + final Key navigatorKey; + final _ContentBuilder contentBuilder; + + @override + Widget build(BuildContext context) { + return MaterialApp( + navigatorKey: navigatorKey, + theme: ThemeData( + platform: TargetPlatform.android, + pageTransitionsTheme: const PageTransitionsTheme( + builders: { + TargetPlatform.android: SharedXAxisPageTransitionsBuilder(), + }, + ), + ), + onGenerateRoute: (RouteSettings settings) { + return MaterialPageRoute( + settings: settings, + builder: (BuildContext context) { + return contentBuilder != null + ? contentBuilder(settings) + : Container( + child: Center( + key: ValueKey(settings.name), + child: Text(settings.name), + ), + ); + }, + ); + }, + ); + } +} + +class _StatefulTestWidget extends StatefulWidget { + const _StatefulTestWidget({Key key, this.name}) : super(key: key); + + final String name; + + @override + State<_StatefulTestWidget> createState() => _StatefulTestWidgetState(); +} + +class _StatefulTestWidgetState extends State<_StatefulTestWidget> { + @override + Widget build(BuildContext context) { + return Text(widget.name); + } +} + +typedef _ContentBuilder = Widget Function(RouteSettings settings); From 2051c55d91fb8167404b95e281b84be04000cf61 Mon Sep 17 00:00:00 2001 From: Shi Hao Hong Date: Thu, 2 Jan 2020 11:25:34 -0800 Subject: [PATCH 22/37] Implement forward testing --- .../test/shared_x_axis_transition_test.dart | 45 ++++++++++++------- 1 file changed, 30 insertions(+), 15 deletions(-) diff --git a/packages/animations/test/shared_x_axis_transition_test.dart b/packages/animations/test/shared_x_axis_transition_test.dart index 8b54c87978a8..ec38b239c4d5 100644 --- a/packages/animations/test/shared_x_axis_transition_test.dart +++ b/packages/animations/test/shared_x_axis_transition_test.dart @@ -8,6 +8,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:flutter/widgets.dart'; +import 'package:vector_math/vector_math_64.dart'; + void main() { testWidgets( 'FadeThroughPageTransitionsBuilder builds a FadeThroughTransition', @@ -45,7 +47,7 @@ void main() { ); expect(find.text(bottomRoute), findsOneWidget); - // expect(_getScale(bottomRoute, tester), 1.0); + expect(_getTranslationOffset(bottomRoute, tester), 0.0); expect(_getOpacity(bottomRoute, tester), 1.0); expect(find.text(topRoute), findsNothing); @@ -53,13 +55,14 @@ void main() { await tester.pump(); await tester.pump(); - // Bottom route is full size and fully visible. + // Bottom route is not offset and fully visible. expect(find.text(bottomRoute), findsOneWidget); - // expect(_getScale(bottomRoute, tester), 1.0); + expect(_getTranslationOffset(bottomRoute, tester), 0.0); expect(_getOpacity(bottomRoute, tester), 1.0); - // Top route is at 80% of full size and not visible yet. + // Top route is offset to the right by 30.0 pixels + // and not visible yet. expect(find.text(topRoute), findsOneWidget); - // expect(_getScale(topRoute, tester), 0.8); + expect(_getTranslationOffset(topRoute, tester), 30.0); expect(_getOpacity(topRoute, tester), 0.0); // Jump 3/10ths of the way through the transition, bottom route @@ -71,12 +74,12 @@ void main() { // Bottom route is now invisible expect(find.text(bottomRoute), findsOneWidget); expect(_getOpacity(bottomRoute, tester), 0.0); - // Top route is still invisible, but scaling up. + // Top route is still invisible, but translating towards the left. expect(find.text(topRoute), findsOneWidget); expect(_getOpacity(topRoute, tester), 0.0); - // double topScale = _getScale(topRoute, tester); - // expect(topScale, greaterThan(0.8)); - // expect(topScale, lessThan(1.0)); + double topTranslation = _getTranslationOffset(topRoute, tester); + expect(topTranslation, lessThan(30.0)); + expect(topTranslation, greaterThan(0.0)); // Jump to the middle of fading in await tester.pump(const Duration(milliseconds: 90)); @@ -87,19 +90,20 @@ void main() { expect(find.text(topRoute), findsOneWidget); expect(_getOpacity(topRoute, tester), greaterThan(0)); expect(_getOpacity(topRoute, tester), lessThan(1.0)); - // topScale = _getScale(topRoute, tester); - // expect(topScale, greaterThan(0.8)); - // expect(topScale, lessThan(1.0)); + topTranslation = _getTranslationOffset(topRoute, tester); + expect(topTranslation, lessThan(30.0)); + expect(topTranslation, greaterThan(0.0)); // Jump to the end of the transition await tester.pump(const Duration(milliseconds: 120)); // Bottom route is not visible. expect(find.text(bottomRoute), findsOneWidget); - // expect(_getScale(bottomRoute, tester), 1.1); + + expect(_getTranslationOffset(bottomRoute, tester), 30.0); expect(_getOpacity(bottomRoute, tester), 0.0); - // Top route fully scaled in and visible. + // Top route has no translation offset and is visible. expect(find.text(topRoute), findsOneWidget); - // expect(_getScale(topRoute, tester), 1.0); + expect(_getTranslationOffset(topRoute, tester), 0.0); expect(_getOpacity(topRoute, tester), 1.0); await tester.pump(const Duration(milliseconds: 1)); @@ -361,6 +365,17 @@ double _getOpacity(String key, WidgetTester tester) { }); } +double _getTranslationOffset(String key, WidgetTester tester) { + final Finder finder = find.ancestor( + of: find.byKey(ValueKey(key)), + matching: find.byType(Transform), + ); + + return tester.widgetList(finder).fold(0.0, (double a, Widget widget) { + final Transform transition = widget; + return a + transition.transform.getTranslation().x; + }); +} // double _getScale(String key, WidgetTester tester) { // final Finder finder = find.ancestor( // of: find.byKey(ValueKey(key)), From 4a9e2ff6fec5d9bcdd03de8e7942c407b181105a Mon Sep 17 00:00:00 2001 From: Shi Hao Hong Date: Thu, 2 Jan 2020 11:32:43 -0800 Subject: [PATCH 23/37] Implement the rest of the tests --- .../test/shared_x_axis_transition_test.dart | 88 ++++++++----------- 1 file changed, 39 insertions(+), 49 deletions(-) diff --git a/packages/animations/test/shared_x_axis_transition_test.dart b/packages/animations/test/shared_x_axis_transition_test.dart index ec38b239c4d5..bf452b0f02db 100644 --- a/packages/animations/test/shared_x_axis_transition_test.dart +++ b/packages/animations/test/shared_x_axis_transition_test.dart @@ -74,12 +74,12 @@ void main() { // Bottom route is now invisible expect(find.text(bottomRoute), findsOneWidget); expect(_getOpacity(bottomRoute, tester), 0.0); - // Top route is still invisible, but translating towards the left. + // Top route is still invisible, but moving towards the left. expect(find.text(topRoute), findsOneWidget); expect(_getOpacity(topRoute, tester), 0.0); - double topTranslation = _getTranslationOffset(topRoute, tester); - expect(topTranslation, lessThan(30.0)); - expect(topTranslation, greaterThan(0.0)); + double topOffset = _getTranslationOffset(topRoute, tester); + expect(topOffset, lessThan(30.0)); + expect(topOffset, greaterThan(0.0)); // Jump to the middle of fading in await tester.pump(const Duration(milliseconds: 90)); @@ -90,9 +90,9 @@ void main() { expect(find.text(topRoute), findsOneWidget); expect(_getOpacity(topRoute, tester), greaterThan(0)); expect(_getOpacity(topRoute, tester), lessThan(1.0)); - topTranslation = _getTranslationOffset(topRoute, tester); - expect(topTranslation, lessThan(30.0)); - expect(topTranslation, greaterThan(0.0)); + topOffset = _getTranslationOffset(topRoute, tester); + expect(topOffset, greaterThan(0.0)); + expect(topOffset, lessThan(30.0)); // Jump to the end of the transition await tester.pump(const Duration(milliseconds: 120)); @@ -101,7 +101,7 @@ void main() { expect(_getTranslationOffset(bottomRoute, tester), 30.0); expect(_getOpacity(bottomRoute, tester), 0.0); - // Top route has no translation offset and is visible. + // Top route has no offset and is visible. expect(find.text(topRoute), findsOneWidget); expect(_getTranslationOffset(topRoute, tester), 0.0); expect(_getOpacity(topRoute, tester), 1.0); @@ -127,20 +127,20 @@ void main() { await tester.pumpAndSettle(); expect(find.text(topRoute), findsOneWidget); - // expect(_getScale(topRoute, tester), 1.0); + expect(_getTranslationOffset(topRoute, tester), 0.0); expect(_getOpacity(topRoute, tester), 1.0); expect(find.text(bottomRoute), findsNothing); navigator.currentState.pop(); await tester.pump(); - // Top route is full size and fully visible. + // Top route is is not offset and fully visible. expect(find.text(topRoute), findsOneWidget); - // expect(_getScale(topRoute, tester), 1.0); + expect(_getTranslationOffset(topRoute, tester), 0.0); expect(_getOpacity(topRoute, tester), 1.0); - // Bottom route is at 80% of full size and not visible yet. + // Bottom route is offset to the right and is not visible yet. expect(find.text(bottomRoute), findsOneWidget); - // expect(_getScale(bottomRoute, tester), 0.8); + expect(_getTranslationOffset(bottomRoute, tester), 30.0); expect(_getOpacity(bottomRoute, tester), 0.0); // Jump 3/10ths of the way through the transition, bottom route @@ -149,15 +149,15 @@ void main() { // Transition time: 300ms, 3/10 * 300ms = 90ms await tester.pump(const Duration(milliseconds: 90)); - // Bottom route is now invisible + // Top route is now invisible expect(find.text(topRoute), findsOneWidget); expect(_getOpacity(topRoute, tester), 0.0); - // Top route is still invisible, but scaling up. + // Bottom route is still invisible, but moving towards the left. expect(find.text(bottomRoute), findsOneWidget); expect(_getOpacity(bottomRoute, tester), moreOrLessEquals(0, epsilon: 0.005)); - // double bottomScale = _getScale(bottomRoute, tester); - // expect(bottomScale, greaterThan(0.8)); - // expect(bottomScale, lessThan(1.0)); + double bottomOffset = _getTranslationOffset(bottomRoute, tester); + expect(bottomOffset, greaterThan(0.0)); + expect(bottomOffset, lessThan(30.0)); // Jump to the middle of fading in await tester.pump(const Duration(milliseconds: 90)); @@ -168,19 +168,19 @@ void main() { expect(find.text(bottomRoute), findsOneWidget); expect(_getOpacity(bottomRoute, tester), greaterThan(0)); expect(_getOpacity(bottomRoute, tester), lessThan(1.0)); - // bottomScale = _getScale(bottomRoute, tester); - // expect(bottomScale, greaterThan(0.8)); - // expect(bottomScale, lessThan(1.0)); + bottomOffset = _getTranslationOffset(bottomRoute, tester); + expect(bottomOffset, greaterThan(0.0)); + expect(bottomOffset, lessThan(30.0)); // Jump to the end of the transition await tester.pump(const Duration(milliseconds: 120)); - // Top route is not visible. + // Top route is not visible and is offset to the right. expect(find.text(topRoute), findsOneWidget); - // expect(_getScale(topRoute, tester), 1.1); + expect(_getTranslationOffset(topRoute, tester), 30.0); expect(_getOpacity(topRoute, tester), 0.0); - // Bottom route fully scaled in and visible. + // Bottom route is not offset and is visible. expect(find.text(bottomRoute), findsOneWidget); - // expect(_getScale(bottomRoute, tester), 1.0); + expect(_getTranslationOffset(bottomRoute, tester), 0.0); expect(_getOpacity(bottomRoute, tester), 1.0); await tester.pump(const Duration(milliseconds: 1)); @@ -212,16 +212,16 @@ void main() { // Bottom route is fully faded out. expect(find.text(bottomRoute), findsOneWidget); expect(_getOpacity(bottomRoute, tester), 0.0); - // final double halfwayBottomScale = _getScale(bottomRoute, tester); - // expect(halfwayBottomScale, greaterThan(1.0)); - // expect(halfwayBottomScale, lessThan(1.1)); + final double halfwayBottomOffset = _getTranslationOffset(bottomRoute, tester); + expect(halfwayBottomOffset, greaterThan(0.0)); + expect(halfwayBottomOffset, lessThan(30.0)); - // Top route is fading/scaling in. + // Top route is fading/coming in. expect(find.text(topRoute), findsOneWidget); - // final double halfwayTopScale = _getScale(topRoute, tester); + final double halfwayTopOffset = _getTranslationOffset(topRoute, tester); final double halfwayTopOpacity = _getOpacity(topRoute, tester); - // expect(halfwayTopScale, greaterThan(0.8)); - // expect(halfwayTopScale, lessThan(1.0)); + expect(halfwayTopOffset, greaterThan(0.0)); + expect(halfwayTopOffset, lessThan(30.0)); expect(halfwayTopOpacity, greaterThan(0.0)); expect(halfwayTopOpacity, lessThan(1.0)); @@ -231,18 +231,18 @@ void main() { // Nothing should change. expect(find.text(bottomRoute), findsOneWidget); - // expect(_getScale(bottomRoute, tester), halfwayBottomScale); + expect(_getTranslationOffset(bottomRoute, tester), halfwayBottomOffset); expect(_getOpacity(bottomRoute, tester), 0.0); expect(find.text(topRoute), findsOneWidget); - // expect(_getScale(topRoute, tester), halfwayTopScale); + expect(_getTranslationOffset(topRoute, tester), halfwayTopOffset); expect(_getOpacity(topRoute, tester), halfwayTopOpacity); // Jump to the 1/4 (75 ms) point of transition await tester.pump(const Duration(milliseconds: 75)); expect(find.text(bottomRoute), findsOneWidget); - // expect(_getScale(bottomRoute, tester), greaterThan(1.0)); - // expect(_getScale(bottomRoute, tester), lessThan(1.1)); - // expect(_getScale(bottomRoute, tester), lessThan(halfwayBottomScale)); + expect(_getTranslationOffset(bottomRoute, tester), greaterThan(0.0)); + expect(_getTranslationOffset(bottomRoute, tester), lessThan(30.0)); + expect(_getTranslationOffset(bottomRoute, tester), lessThan(halfwayBottomOffset)); expect(_getOpacity(bottomRoute, tester), greaterThan(0.0)); expect(_getOpacity(bottomRoute, tester), lessThan(1.0)); @@ -250,10 +250,10 @@ void main() { // Jump to the end. await tester.pump(const Duration(milliseconds: 75)); expect(find.text(bottomRoute), findsOneWidget); - // expect(_getScale(bottomRoute, tester), 1.0); + expect(_getTranslationOffset(bottomRoute, tester), 0.0); expect(_getOpacity(bottomRoute, tester), 1.0); expect(find.text(topRoute), findsOneWidget); - // expect(_getScale(topRoute, tester), 0.80); + expect(_getTranslationOffset(topRoute, tester), 30.0); expect(_getOpacity(topRoute, tester), 0.0); await tester.pump(const Duration(milliseconds: 1)); @@ -376,16 +376,6 @@ double _getTranslationOffset(String key, WidgetTester tester) { return a + transition.transform.getTranslation().x; }); } -// double _getScale(String key, WidgetTester tester) { -// final Finder finder = find.ancestor( -// of: find.byKey(ValueKey(key)), -// matching: find.byType(ScaleTransition), -// ); -// return tester.widgetList(finder).fold(1.0, (double a, Widget widget) { -// final ScaleTransition transition = widget; -// return a * transition.scale.value; -// }); -// } class _TestWidget extends StatelessWidget { const _TestWidget({this.navigatorKey, this.contentBuilder}); From 4463952d1858ba06ce68981a4a9d952082b775b4 Mon Sep 17 00:00:00 2001 From: Shi Hao Hong Date: Thu, 2 Jan 2020 11:32:57 -0800 Subject: [PATCH 24/37] Remove unnecessary dart_math import --- packages/animations/test/shared_x_axis_transition_test.dart | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/animations/test/shared_x_axis_transition_test.dart b/packages/animations/test/shared_x_axis_transition_test.dart index bf452b0f02db..25b442709e7b 100644 --- a/packages/animations/test/shared_x_axis_transition_test.dart +++ b/packages/animations/test/shared_x_axis_transition_test.dart @@ -8,8 +8,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:flutter/widgets.dart'; -import 'package:vector_math/vector_math_64.dart'; - void main() { testWidgets( 'FadeThroughPageTransitionsBuilder builds a FadeThroughTransition', From b02e075888c8af49d43e861d5f91e1117f8d7d23 Mon Sep 17 00:00:00 2001 From: Shi Hao Hong Date: Thu, 2 Jan 2020 11:36:07 -0800 Subject: [PATCH 25/37] Remove z-axis implementation --- packages/animations/lib/animations.dart | 1 - .../lib/src/shared_z_axis_transition.dart | 394 ---------------- .../test/shared_z_axis_transition_test.dart | 428 ------------------ 3 files changed, 823 deletions(-) delete mode 100644 packages/animations/lib/src/shared_z_axis_transition.dart delete mode 100644 packages/animations/test/shared_z_axis_transition_test.dart diff --git a/packages/animations/lib/animations.dart b/packages/animations/lib/animations.dart index 7b6fb951c1d5..cf58ad52c4ed 100644 --- a/packages/animations/lib/animations.dart +++ b/packages/animations/lib/animations.dart @@ -6,4 +6,3 @@ export 'src/fade_through_transition.dart'; export 'src/open_container.dart'; export 'src/page_transition_switcher.dart'; export 'src/shared_x_axis_transition.dart'; -export 'src/shared_z_axis_transition.dart'; diff --git a/packages/animations/lib/src/shared_z_axis_transition.dart b/packages/animations/lib/src/shared_z_axis_transition.dart deleted file mode 100644 index b5f1ddb4b980..000000000000 --- a/packages/animations/lib/src/shared_z_axis_transition.dart +++ /dev/null @@ -1,394 +0,0 @@ -// Copyright 2019 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import 'package:flutter/animation.dart'; -import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter/widgets.dart'; - -import 'utils/curves.dart'; - -/// Used by [PageTransitionsTheme] to define a page route transition animation -/// in which outgoing and incoming elements share a scale transition. -/// -/// The shared axis pattern provides the transition animation between UI elements -/// that have a spatial or navigational relationship. For example, -/// transitioning from one page of a sign up page to the next one. -/// -/// -/// The following example shows how the SharedZAxisPageTransitionsBuilder can -/// be used in a [PageTransitionsTheme] to change the default transitions -/// of [MaterialPageRoute]s. -/// -/// ```dart -/// MaterialApp( -/// theme: ThemeData( -/// pageTransitionsTheme: PageTransitionsTheme( -/// builders: { -/// TargetPlatform.android: SharedZAxisPageTransitionsBuilder(), -/// TargetPlatform.iOS: SharedZAxisPageTransitionsBuilder(), -/// }, -/// ), -/// ), -/// routes: { -/// '/': (BuildContext context) { -/// return Container( -/// color: Colors.red, -/// child: Center( -/// child: MaterialButton( -/// child: Text('Push route'), -/// onPressed: () { -/// Navigator.of(context).pushNamed('/a'); -/// }, -/// ), -/// ), -/// ); -/// }, -/// '/a' : (BuildContext context) { -/// return Container( -/// color: Colors.blue, -/// child: Center( -/// child: MaterialButton( -/// child: Text('Pop route'), -/// onPressed: () { -/// Navigator.of(context).pop(); -/// }, -/// ), -/// ), -/// ); -/// }, -/// }, -/// ); -/// ``` -class SharedZAxisPageTransitionsBuilder extends PageTransitionsBuilder { - /// Construct a [SharedZAxisPageTransitionsBuilder]. - const SharedZAxisPageTransitionsBuilder(); - - @override - Widget buildTransitions( - PageRoute route, - BuildContext context, - Animation animation, - Animation secondaryAnimation, - Widget child, - ) { - return SharedZAxisTransition( - animation: animation, - secondaryAnimation: secondaryAnimation, - child: child, - ); - } -} - -/// Defines a transition in which outgoing and incoming elements share a scale -/// transition. -/// -/// The shared axis pattern provides the transition animation between UI elements -/// that have a spatial or navigational relationship. For example, -/// transitioning from one page of a sign up page to the next one. -/// -/// Consider using [SharedZAxisTransition] within a -/// [PageTransitionsTheme] if you want to apply this kind of transition to -/// [MaterialPageRoute] transitions within a Navigator (see -/// [SharedZAxisPageTransitionsBuilder] for some example code). -/// -/// This transition can also be used directly in a -/// [PageTransitionSwitcher.transitionBuilder] to transition -/// from one widget to another as seen in the following example: -/// ```dart -/// int _selectedIndex = 0; -/// -/// final List _colors = [Colors.white, Colors.red, Colors.yellow]; -/// -/// @override -/// Widget build(BuildContext context) { -/// return Scaffold( -/// appBar: AppBar( -/// title: const Text('Page Transition Example'), -/// ), -/// body: PageTransitionSwitcher( -/// // reverse: true, // uncomment to see transition in reverse -/// transitionBuilder: ( -/// Widget child, -/// Animation primaryAnimation, -/// Animation secondaryAnimation, -/// ) { -/// return SharedZAxisTransition( -/// animation: primaryAnimation, -/// secondaryAnimation: secondaryAnimation, -/// child: child, -/// ); -/// }, -/// child: Container( -/// key: ValueKey(_selectedIndex), -/// color: _colors[_selectedIndex], -/// child: Center( -/// child: FlutterLogo(size: 300), -/// ) -/// ), -/// ), -/// bottomNavigationBar: BottomNavigationBar( -/// items: const [ -/// BottomNavigationBarItem( -/// icon: Icon(Icons.home), -/// title: Text('White'), -/// ), -/// BottomNavigationBarItem( -/// icon: Icon(Icons.business), -/// title: Text('Red'), -/// ), -/// BottomNavigationBarItem( -/// icon: Icon(Icons.school), -/// title: Text('Yellow'), -/// ), -/// ], -/// currentIndex: _selectedIndex, -/// onTap: (int index) { -/// setState(() { -/// _selectedIndex = index; -/// }); -/// }, -/// ), -/// ); -/// } -/// ``` -class SharedZAxisTransition extends StatefulWidget { - /// Creates a [SharedZAxisTransition]. - /// - /// The [animation] and [secondaryAnimation] argument are required and must - /// not be null. - const SharedZAxisTransition({ - Key key, - @required this.animation, - @required this.secondaryAnimation, - this.child, - }) : super(key: key); - - /// The animation that drives the [child]'s entrance and exit. - /// - /// See also: - /// - /// * [TransitionRoute.animate], which is the value given to this property - /// when it is used as a page transition. - final Animation animation; - - /// The animation that transitions [child] when new content is pushed on top - /// of it. - /// - /// See also: - /// - /// * [TransitionRoute.secondaryAnimation], which is the value given to this - /// property when the it is used as a page transition. - final Animation secondaryAnimation; - - /// The widget below this widget in the tree. - /// - /// This widget will transition in and out as driven by [animation] and - /// [secondaryAnimation]. - final Widget child; - - @override - _SharedZAxisTransitionState createState() => _SharedZAxisTransitionState(); -} - -class _SharedZAxisTransitionState extends State { - AnimationStatus _effectiveAnimationStatus; - AnimationStatus _effectiveSecondaryAnimationStatus; - - @override - void initState() { - super.initState(); - _effectiveAnimationStatus = widget.animation.status; - _effectiveSecondaryAnimationStatus = widget.secondaryAnimation.status; - widget.animation.addStatusListener(_animationListener); - widget.secondaryAnimation.addStatusListener(_secondaryAnimationListener); - } - - void _animationListener(AnimationStatus animationStatus) { - _effectiveAnimationStatus = _calculateEffectiveAnimationStatus( - lastEffective: _effectiveAnimationStatus, - current: animationStatus, - ); - } - - void _secondaryAnimationListener(AnimationStatus animationStatus) { - _effectiveSecondaryAnimationStatus = _calculateEffectiveAnimationStatus( - lastEffective: _effectiveSecondaryAnimationStatus, - current: animationStatus, - ); - } - - // When a transition is interrupted midway we just want to play the ongoing - // animation in reverse. Switching to the actual reverse transition would - // yield a disjoint experience since the forward and reverse transitions are - // very different. - AnimationStatus _calculateEffectiveAnimationStatus({ - @required AnimationStatus lastEffective, - @required AnimationStatus current, - }) { - assert(current != null); - assert(lastEffective != null); - switch (current) { - case AnimationStatus.dismissed: - case AnimationStatus.completed: - return current; - case AnimationStatus.forward: - switch (lastEffective) { - case AnimationStatus.dismissed: - case AnimationStatus.completed: - case AnimationStatus.forward: - return current; - case AnimationStatus.reverse: - return lastEffective; - } - break; - case AnimationStatus.reverse: - switch (lastEffective) { - case AnimationStatus.dismissed: - case AnimationStatus.completed: - case AnimationStatus.reverse: - return current; - case AnimationStatus.forward: - return lastEffective; - } - break; - } - return null; // unreachable - } - - @override - void didUpdateWidget(SharedZAxisTransition oldWidget) { - super.didUpdateWidget(oldWidget); - if (oldWidget.animation != widget.animation) { - oldWidget.animation.removeStatusListener(_animationListener); - widget.animation.addStatusListener(_animationListener); - _animationListener(widget.animation.status); - } - if (oldWidget.secondaryAnimation != widget.secondaryAnimation) { - oldWidget.secondaryAnimation - .removeStatusListener(_secondaryAnimationListener); - widget.secondaryAnimation.addStatusListener(_secondaryAnimationListener); - _secondaryAnimationListener(widget.secondaryAnimation.status); - } - } - - @override - void dispose() { - widget.animation.removeStatusListener(_animationListener); - widget.secondaryAnimation.removeStatusListener(_secondaryAnimationListener); - super.dispose(); - } - - static final Tween _flippedTween = Tween( - begin: 1.0, - end: 0.0, - ); - static Animation _flip(Animation animation) { - return _flippedTween.animate(animation); - } - - @override - Widget build(BuildContext context) { - return AnimatedBuilder( - animation: widget.animation, - builder: (BuildContext context, Widget child) { - assert(_effectiveAnimationStatus != null); - switch (_effectiveAnimationStatus) { - case AnimationStatus.forward: - return _EnterTransition( - animation: widget.animation, - child: child, - ); - case AnimationStatus.dismissed: - case AnimationStatus.reverse: - case AnimationStatus.completed: - return _ExitTransition( - animation: _flip(widget.animation), - child: child, - ); - } - return null; // unreachable - }, - child: AnimatedBuilder( - animation: widget.secondaryAnimation, - builder: (BuildContext context, Widget child) { - assert(_effectiveSecondaryAnimationStatus != null); - switch (_effectiveSecondaryAnimationStatus) { - case AnimationStatus.forward: - return _ExitTransition( - animation: widget.secondaryAnimation, - child: child, - ); - case AnimationStatus.dismissed: - case AnimationStatus.reverse: - case AnimationStatus.completed: - return _EnterTransition( - animation: _flip(widget.secondaryAnimation), - child: child, - ); - } - return null; // unreachable - }, - child: widget.child, - ), - ); - } -} - -class _EnterTransition extends StatelessWidget { - const _EnterTransition({ - this.animation, - this.child, - }); - - final Animation animation; - final Widget child; - - static Animatable fadeInTransition = CurveTween(curve: decelerateEasing) - .chain(CurveTween(curve: const Interval(0.3, 1.0))); - - static Animatable scaleInTransition = Tween(begin: 0.80, end: 1.00) - .chain(CurveTween(curve: standardEasing)); - - @override - Widget build(BuildContext context) { - return FadeTransition( - opacity: fadeInTransition.animate(animation), - child: ScaleTransition( - scale: scaleInTransition.animate(animation), - child: child, - ), - ); - } -} - -class _ExitTransition extends StatelessWidget { - const _ExitTransition({ - this.animation, - this.child, - }); - - final Animation animation; - final Widget child; - - static Animatable fadeOutTransition = FlippedCurveTween(curve: accelerateEasing) - .chain(CurveTween(curve: const Interval(0.0, 0.3))); - - static Animatable scaleOutTransition = Tween(begin: 1.00, end: 1.10) - .chain(CurveTween(curve: standardEasing)); - - @override - Widget build(BuildContext context) { - return FadeTransition( - opacity: fadeOutTransition.animate(animation), - child: Container( - color: Theme.of(context).canvasColor, - child: ScaleTransition( - scale: scaleOutTransition.animate(animation), - child: child, - ), - ), - ); - } -} \ No newline at end of file diff --git a/packages/animations/test/shared_z_axis_transition_test.dart b/packages/animations/test/shared_z_axis_transition_test.dart deleted file mode 100644 index db4de3f0dc5f..000000000000 --- a/packages/animations/test/shared_z_axis_transition_test.dart +++ /dev/null @@ -1,428 +0,0 @@ -// Copyright 2019 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import 'package:animations/src/shared_z_axis_transition.dart'; -import 'package:flutter/animation.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:flutter/widgets.dart'; - -void main() { - testWidgets( - 'FadeThroughPageTransitionsBuilder builds a FadeThroughTransition', - (WidgetTester tester) async { - final AnimationController animation = AnimationController( - vsync: const TestVSync(), - ); - final AnimationController secondaryAnimation = AnimationController( - vsync: const TestVSync(), - ); - - await tester.pumpWidget( - const SharedZAxisPageTransitionsBuilder().buildTransitions( - null, - null, - animation, - secondaryAnimation, - const Placeholder(), - ), - ); - - expect(find.byType(SharedZAxisTransition), findsOneWidget); - }, - ); - - testWidgets( - 'SharedZAxisTransition runs forward', - (WidgetTester tester) async { - final GlobalKey navigator = GlobalKey(); - const String bottomRoute = '/'; - const String topRoute = '/a'; - - await tester.pumpWidget( - _TestWidget(navigatorKey: navigator), - ); - - expect(find.text(bottomRoute), findsOneWidget); - expect(_getScale(bottomRoute, tester), 1.0); - expect(_getOpacity(bottomRoute, tester), 1.0); - expect(find.text(topRoute), findsNothing); - - navigator.currentState.pushNamed(topRoute); - await tester.pump(); - await tester.pump(); - - // Bottom route is full size and fully visible. - expect(find.text(bottomRoute), findsOneWidget); - expect(_getScale(bottomRoute, tester), 1.0); - expect(_getOpacity(bottomRoute, tester), 1.0); - // Top route is at 80% of full size and not visible yet. - expect(find.text(topRoute), findsOneWidget); - expect(_getScale(topRoute, tester), 0.8); - expect(_getOpacity(topRoute, tester), 0.0); - - // Jump 3/10ths of the way through the transition, bottom route - // should be be completely faded out while the top route - // is also completely faded out. - // Transition time: 300ms, 3/10 * 300ms = 90ms - await tester.pump(const Duration(milliseconds: 90)); - - // Bottom route is now invisible - expect(find.text(bottomRoute), findsOneWidget); - expect(_getOpacity(bottomRoute, tester), 0.0); - // Top route is still invisible, but scaling up. - expect(find.text(topRoute), findsOneWidget); - expect(_getOpacity(topRoute, tester), 0.0); - double topScale = _getScale(topRoute, tester); - expect(topScale, greaterThan(0.8)); - expect(topScale, lessThan(1.0)); - - // Jump to the middle of fading in - await tester.pump(const Duration(milliseconds: 90)); - // Bottom route is still invisible - expect(find.text(bottomRoute), findsOneWidget); - expect(_getOpacity(bottomRoute, tester), 0.0); - // Top route is fading in - expect(find.text(topRoute), findsOneWidget); - expect(_getOpacity(topRoute, tester), greaterThan(0)); - expect(_getOpacity(topRoute, tester), lessThan(1.0)); - topScale = _getScale(topRoute, tester); - expect(topScale, greaterThan(0.8)); - expect(topScale, lessThan(1.0)); - - // Jump to the end of the transition - await tester.pump(const Duration(milliseconds: 120)); - // Bottom route is not visible. - expect(find.text(bottomRoute), findsOneWidget); - expect(_getScale(bottomRoute, tester), 1.1); - expect(_getOpacity(bottomRoute, tester), 0.0); - // Top route fully scaled in and visible. - expect(find.text(topRoute), findsOneWidget); - expect(_getScale(topRoute, tester), 1.0); - expect(_getOpacity(topRoute, tester), 1.0); - - await tester.pump(const Duration(milliseconds: 1)); - expect(find.text(bottomRoute), findsNothing); - expect(find.text(topRoute), findsOneWidget); - }, - ); - - testWidgets( - 'SharedZAxisTransition runs in reverse', - (WidgetTester tester) async { - final GlobalKey navigator = GlobalKey(); - const String bottomRoute = '/'; - const String topRoute = '/a'; - - await tester.pumpWidget( - _TestWidget(navigatorKey: navigator), - ); - - navigator.currentState.pushNamed('/a'); - await tester.pumpAndSettle(); - - expect(find.text(topRoute), findsOneWidget); - expect(_getScale(topRoute, tester), 1.0); - expect(_getOpacity(topRoute, tester), 1.0); - expect(find.text(bottomRoute), findsNothing); - - navigator.currentState.pop(); - await tester.pump(); - - // Top route is full size and fully visible. - expect(find.text(topRoute), findsOneWidget); - expect(_getScale(topRoute, tester), 1.0); - expect(_getOpacity(topRoute, tester), 1.0); - // Bottom route is at 80% of full size and not visible yet. - expect(find.text(bottomRoute), findsOneWidget); - expect(_getScale(bottomRoute, tester), 0.8); - expect(_getOpacity(bottomRoute, tester), 0.0); - - // Jump 3/10ths of the way through the transition, bottom route - // should be be completely faded out while the top route - // is also completely faded out. - // Transition time: 300ms, 3/10 * 300ms = 90ms - await tester.pump(const Duration(milliseconds: 90)); - - // Bottom route is now invisible - expect(find.text(topRoute), findsOneWidget); - expect(_getOpacity(topRoute, tester), 0.0); - // Top route is still invisible, but scaling up. - expect(find.text(bottomRoute), findsOneWidget); - expect(_getOpacity(bottomRoute, tester), moreOrLessEquals(0, epsilon: 0.005)); - double bottomScale = _getScale(bottomRoute, tester); - expect(bottomScale, greaterThan(0.8)); - expect(bottomScale, lessThan(1.0)); - - // Jump to the middle of fading in - await tester.pump(const Duration(milliseconds: 90)); - // Top route is still invisible - expect(find.text(topRoute), findsOneWidget); - expect(_getOpacity(topRoute, tester), 0.0); - // Bottom route is fading in - expect(find.text(bottomRoute), findsOneWidget); - expect(_getOpacity(bottomRoute, tester), greaterThan(0)); - expect(_getOpacity(bottomRoute, tester), lessThan(1.0)); - bottomScale = _getScale(bottomRoute, tester); - expect(bottomScale, greaterThan(0.8)); - expect(bottomScale, lessThan(1.0)); - - // Jump to the end of the transition - await tester.pump(const Duration(milliseconds: 120)); - // Top route is not visible. - expect(find.text(topRoute), findsOneWidget); - expect(_getScale(topRoute, tester), 1.1); - expect(_getOpacity(topRoute, tester), 0.0); - // Bottom route fully scaled in and visible. - expect(find.text(bottomRoute), findsOneWidget); - expect(_getScale(bottomRoute, tester), 1.0); - expect(_getOpacity(bottomRoute, tester), 1.0); - - await tester.pump(const Duration(milliseconds: 1)); - expect(find.text(topRoute), findsNothing); - expect(find.text(bottomRoute), findsOneWidget); - }, - ); - - testWidgets( - 'FadeThroughTransition does not jump when interrupted', - (WidgetTester tester) async { - final GlobalKey navigator = GlobalKey(); - const String bottomRoute = '/'; - const String topRoute = '/a'; - - await tester.pumpWidget( - _TestWidget( - navigatorKey: navigator, - ), - ); - expect(find.text(bottomRoute), findsOneWidget); - expect(find.text(topRoute), findsNothing); - - navigator.currentState.pushNamed(topRoute); - await tester.pump(); - - // Jump to halfway point of transition. - await tester.pump(const Duration(milliseconds: 150)); - // Bottom route is fully faded out. - expect(find.text(bottomRoute), findsOneWidget); - expect(_getOpacity(bottomRoute, tester), 0.0); - final double halfwayBottomScale = _getScale(bottomRoute, tester); - expect(halfwayBottomScale, greaterThan(1.0)); - expect(halfwayBottomScale, lessThan(1.1)); - - // Top route is fading/scaling in. - expect(find.text(topRoute), findsOneWidget); - final double halfwayTopScale = _getScale(topRoute, tester); - final double halfwayTopOpacity = _getOpacity(topRoute, tester); - expect(halfwayTopScale, greaterThan(0.8)); - expect(halfwayTopScale, lessThan(1.0)); - expect(halfwayTopOpacity, greaterThan(0.0)); - expect(halfwayTopOpacity, lessThan(1.0)); - - // Interrupt the transition with a pop. - navigator.currentState.pop(); - await tester.pump(); - - // Nothing should change. - expect(find.text(bottomRoute), findsOneWidget); - expect(_getScale(bottomRoute, tester), halfwayBottomScale); - expect(_getOpacity(bottomRoute, tester), 0.0); - expect(find.text(topRoute), findsOneWidget); - expect(_getScale(topRoute, tester), halfwayTopScale); - expect(_getOpacity(topRoute, tester), halfwayTopOpacity); - - // Jump to the 1/4 (75 ms) point of transition - await tester.pump(const Duration(milliseconds: 75)); - expect(find.text(bottomRoute), findsOneWidget); - expect(_getScale(bottomRoute, tester), greaterThan(1.0)); - expect(_getScale(bottomRoute, tester), lessThan(1.1)); - expect(_getScale(bottomRoute, tester), lessThan(halfwayBottomScale)); - expect(_getOpacity(bottomRoute, tester), greaterThan(0.0)); - expect(_getOpacity(bottomRoute, tester), lessThan(1.0)); - - - // Jump to the end. - await tester.pump(const Duration(milliseconds: 75)); - expect(find.text(bottomRoute), findsOneWidget); - expect(_getScale(bottomRoute, tester), 1.0); - expect(_getOpacity(bottomRoute, tester), 1.0); - expect(find.text(topRoute), findsOneWidget); - expect(_getScale(topRoute, tester), 0.80); - expect(_getOpacity(topRoute, tester), 0.0); - - await tester.pump(const Duration(milliseconds: 1)); - expect(find.text(topRoute), findsNothing); - expect(find.text(bottomRoute), findsOneWidget); - }, - ); - - testWidgets( - 'State is not lost when transitioning', - (WidgetTester tester) async { - final GlobalKey navigator = GlobalKey(); - const String bottomRoute = '/'; - const String topRoute = '/a'; - - await tester.pumpWidget( - _TestWidget( - navigatorKey: navigator, - contentBuilder: (RouteSettings settings) { - return _StatefulTestWidget( - key: ValueKey(settings.name), - name: settings.name, - ); - }, - ), - ); - - final _StatefulTestWidgetState bottomState = tester.state( - find.byKey(const ValueKey(bottomRoute)), - ); - expect(bottomState.widget.name, bottomRoute); - - navigator.currentState.pushNamed(topRoute); - await tester.pump(); - await tester.pump(); - - expect( - tester.state(find.byKey(const ValueKey(bottomRoute))), - bottomState, - ); - final _StatefulTestWidgetState topState = tester.state( - find.byKey(const ValueKey(topRoute)), - ); - expect(topState.widget.name, topRoute); - - await tester.pump(const Duration(milliseconds: 150)); - expect( - tester.state(find.byKey(const ValueKey(bottomRoute))), - bottomState, - ); - expect( - tester.state(find.byKey(const ValueKey(topRoute))), - topState, - ); - - await tester.pumpAndSettle(); - expect( - tester.state(find.byKey( - const ValueKey(bottomRoute), - skipOffstage: false, - )), - bottomState, - ); - expect( - tester.state(find.byKey(const ValueKey(topRoute))), - topState, - ); - - navigator.currentState.pop(); - await tester.pump(); - - expect( - tester.state(find.byKey(const ValueKey(bottomRoute))), - bottomState, - ); - expect( - tester.state(find.byKey(const ValueKey(topRoute))), - topState, - ); - - await tester.pump(const Duration(milliseconds: 150)); - expect( - tester.state(find.byKey(const ValueKey(bottomRoute))), - bottomState, - ); - expect( - tester.state(find.byKey(const ValueKey(topRoute))), - topState, - ); - - await tester.pumpAndSettle(); - expect( - tester.state(find.byKey(const ValueKey(bottomRoute))), - bottomState, - ); - expect(find.byKey(const ValueKey(topRoute)), findsNothing); - }, - ); -} - -double _getOpacity(String key, WidgetTester tester) { - final Finder finder = find.ancestor( - of: find.byKey(ValueKey(key)), - matching: find.byType(FadeTransition), - ); - return tester.widgetList(finder).fold(1.0, (double a, Widget widget) { - final FadeTransition transition = widget; - return a * transition.opacity.value; - }); -} - -double _getScale(String key, WidgetTester tester) { - final Finder finder = find.ancestor( - of: find.byKey(ValueKey(key)), - matching: find.byType(ScaleTransition), - ); - return tester.widgetList(finder).fold(1.0, (double a, Widget widget) { - final ScaleTransition transition = widget; - return a * transition.scale.value; - }); -} - -class _TestWidget extends StatelessWidget { - const _TestWidget({this.navigatorKey, this.contentBuilder}); - - final Key navigatorKey; - final _ContentBuilder contentBuilder; - - @override - Widget build(BuildContext context) { - return MaterialApp( - navigatorKey: navigatorKey, - theme: ThemeData( - platform: TargetPlatform.android, - pageTransitionsTheme: const PageTransitionsTheme( - builders: { - TargetPlatform.android: SharedZAxisPageTransitionsBuilder(), - }, - ), - ), - onGenerateRoute: (RouteSettings settings) { - return MaterialPageRoute( - settings: settings, - builder: (BuildContext context) { - return contentBuilder != null - ? contentBuilder(settings) - : Container( - child: Center( - key: ValueKey(settings.name), - child: Text(settings.name), - ), - ); - }, - ); - }, - ); - } -} - -class _StatefulTestWidget extends StatefulWidget { - const _StatefulTestWidget({Key key, this.name}) : super(key: key); - - final String name; - - @override - State<_StatefulTestWidget> createState() => _StatefulTestWidgetState(); -} - -class _StatefulTestWidgetState extends State<_StatefulTestWidget> { - @override - Widget build(BuildContext context) { - return Text(widget.name); - } -} - -typedef _ContentBuilder = Widget Function(RouteSettings settings); From d4f4262716e702c002f071f4c945277dc7850689 Mon Sep 17 00:00:00 2001 From: Shi Hao Hong Date: Thu, 2 Jan 2020 11:50:01 -0800 Subject: [PATCH 26/37] Run formatter --- .../lib/src/shared_x_axis_transition.dart | 12 +++++++----- packages/animations/lib/src/utils/curves.dart | 4 ++-- .../test/shared_x_axis_transition_test.dart | 13 ++++++++----- 3 files changed, 17 insertions(+), 12 deletions(-) diff --git a/packages/animations/lib/src/shared_x_axis_transition.dart b/packages/animations/lib/src/shared_x_axis_transition.dart index 7b5490432087..8d79390eb1ee 100644 --- a/packages/animations/lib/src/shared_x_axis_transition.dart +++ b/packages/animations/lib/src/shared_x_axis_transition.dart @@ -296,7 +296,7 @@ class _SharedXAxisTransitionState extends State { assert(_effectiveAnimationStatus != null); switch (_effectiveAnimationStatus) { case AnimationStatus.forward: - return _EnterTransition( + return _EnterTransition( animation: widget.animation, child: child, ); @@ -345,8 +345,9 @@ class _EnterTransition extends StatelessWidget { final Animation animation; final Widget child; - static Animatable fadeInTransition = CurveTween(curve: decelerateEasing) - .chain(CurveTween(curve: const Interval(0.3, 1.0))); + static Animatable fadeInTransition = + CurveTween(curve: decelerateEasing) + .chain(CurveTween(curve: const Interval(0.3, 1.0))); static Animatable slideInTransition = Tween( begin: const Offset(30, 0.0), @@ -374,8 +375,9 @@ class _ExitTransition extends StatelessWidget { final Animation animation; final Widget child; - static Animatable fadeOutTransition = FlippedCurveTween(curve: accelerateEasing) - .chain(CurveTween(curve: const Interval(0.0, 0.3))); + static Animatable fadeOutTransition = + FlippedCurveTween(curve: accelerateEasing) + .chain(CurveTween(curve: const Interval(0.0, 0.3))); static Animatable slideOutTransition = Tween( begin: Offset.zero, diff --git a/packages/animations/lib/src/utils/curves.dart b/packages/animations/lib/src/utils/curves.dart index 9760f5eb5134..39c4f8b51b2c 100644 --- a/packages/animations/lib/src/utils/curves.dart +++ b/packages/animations/lib/src/utils/curves.dart @@ -49,8 +49,8 @@ class FlippedCurveTween extends CurveTween { /// Creates a vertically flipped [CurveTween]. FlippedCurveTween({ @required Curve curve, - }) : assert(curve != null), - super(curve: curve); + }) : assert(curve != null), + super(curve: curve); @override double transform(double t) => 1.0 - super.transform(t); diff --git a/packages/animations/test/shared_x_axis_transition_test.dart b/packages/animations/test/shared_x_axis_transition_test.dart index 25b442709e7b..a0d5d1c1fe13 100644 --- a/packages/animations/test/shared_x_axis_transition_test.dart +++ b/packages/animations/test/shared_x_axis_transition_test.dart @@ -152,7 +152,8 @@ void main() { expect(_getOpacity(topRoute, tester), 0.0); // Bottom route is still invisible, but moving towards the left. expect(find.text(bottomRoute), findsOneWidget); - expect(_getOpacity(bottomRoute, tester), moreOrLessEquals(0, epsilon: 0.005)); + expect(_getOpacity(bottomRoute, tester), + moreOrLessEquals(0, epsilon: 0.005)); double bottomOffset = _getTranslationOffset(bottomRoute, tester); expect(bottomOffset, greaterThan(0.0)); expect(bottomOffset, lessThan(30.0)); @@ -210,7 +211,8 @@ void main() { // Bottom route is fully faded out. expect(find.text(bottomRoute), findsOneWidget); expect(_getOpacity(bottomRoute, tester), 0.0); - final double halfwayBottomOffset = _getTranslationOffset(bottomRoute, tester); + final double halfwayBottomOffset = + _getTranslationOffset(bottomRoute, tester); expect(halfwayBottomOffset, greaterThan(0.0)); expect(halfwayBottomOffset, lessThan(30.0)); @@ -240,11 +242,11 @@ void main() { expect(find.text(bottomRoute), findsOneWidget); expect(_getTranslationOffset(bottomRoute, tester), greaterThan(0.0)); expect(_getTranslationOffset(bottomRoute, tester), lessThan(30.0)); - expect(_getTranslationOffset(bottomRoute, tester), lessThan(halfwayBottomOffset)); + expect(_getTranslationOffset(bottomRoute, tester), + lessThan(halfwayBottomOffset)); expect(_getOpacity(bottomRoute, tester), greaterThan(0.0)); expect(_getOpacity(bottomRoute, tester), lessThan(1.0)); - // Jump to the end. await tester.pump(const Duration(milliseconds: 75)); expect(find.text(bottomRoute), findsOneWidget); @@ -369,7 +371,8 @@ double _getTranslationOffset(String key, WidgetTester tester) { matching: find.byType(Transform), ); - return tester.widgetList(finder).fold(0.0, (double a, Widget widget) { + return tester.widgetList(finder).fold(0.0, + (double a, Widget widget) { final Transform transition = widget; return a + transition.transform.getTranslation().x; }); From f590e14a0a03a218972efceb9fcbc9c299e2cf89 Mon Sep 17 00:00:00 2001 From: Shi Hao Hong Date: Thu, 2 Jan 2020 12:25:25 -0800 Subject: [PATCH 27/37] Implement ShareAxisTransition with horizontal/vertical options --- ...ition.dart => shared_axis_transition.dart} | 87 ++- .../test/shared_axis_transition_test.dart | 569 ++++++++++++++++++ .../test/shared_x_axis_transition_test.dart | 434 ------------- 3 files changed, 628 insertions(+), 462 deletions(-) rename packages/animations/lib/src/{shared_x_axis_transition.dart => shared_axis_transition.dart} (82%) create mode 100644 packages/animations/test/shared_axis_transition_test.dart delete mode 100644 packages/animations/test/shared_x_axis_transition_test.dart diff --git a/packages/animations/lib/src/shared_x_axis_transition.dart b/packages/animations/lib/src/shared_axis_transition.dart similarity index 82% rename from packages/animations/lib/src/shared_x_axis_transition.dart rename to packages/animations/lib/src/shared_axis_transition.dart index 8d79390eb1ee..8cb7cefb580b 100644 --- a/packages/animations/lib/src/shared_x_axis_transition.dart +++ b/packages/animations/lib/src/shared_axis_transition.dart @@ -9,6 +9,14 @@ import 'package:flutter/widgets.dart'; import 'utils/curves.dart'; +/// Determines which type shared axis transition. +enum SharedAxisTransitionType { + /// Creates a shared axis vertical translation page transition. + vertical, + /// Creates a shared axis horizontal translation page transition. + horizontal, +} + /// Used by [PageTransitionsTheme] to define a page route transition animation /// in which outgoing and incoming elements share a horizontal fade transition. /// @@ -17,7 +25,7 @@ import 'utils/curves.dart'; /// transitioning from one page of a sign up page to the next one. /// /// -/// The following example shows how the SharedXAxisPageTransitionsBuilder can +/// The following example shows how the SharedAxisPageTransitionsBuilder can /// be used in a [PageTransitionsTheme] to change the default transitions /// of [MaterialPageRoute]s. /// @@ -26,8 +34,8 @@ import 'utils/curves.dart'; /// theme: ThemeData( /// pageTransitionsTheme: PageTransitionsTheme( /// builders: { -/// TargetPlatform.android: SharedXAxisPageTransitionsBuilder(), -/// TargetPlatform.iOS: SharedXAxisPageTransitionsBuilder(), +/// TargetPlatform.android: SharedAxisPageTransitionsBuilder(), +/// TargetPlatform.iOS: SharedAxisPageTransitionsBuilder(), /// }, /// ), /// ), @@ -61,9 +69,14 @@ import 'utils/curves.dart'; /// }, /// ); /// ``` -class SharedXAxisPageTransitionsBuilder extends PageTransitionsBuilder { - /// Construct a [SharedXAxisPageTransitionsBuilder]. - const SharedXAxisPageTransitionsBuilder(); +class SharedAxisPageTransitionsBuilder extends PageTransitionsBuilder { + /// Construct a [SharedAxisPageTransitionsBuilder]. + const SharedAxisPageTransitionsBuilder({ + this.transitionType, + }); + + /// Determines which [SharedAxisTransitionType] to build. + final SharedAxisTransitionType transitionType; @override Widget buildTransitions( @@ -71,11 +84,12 @@ class SharedXAxisPageTransitionsBuilder extends PageTransitionsBuilder { BuildContext context, Animation animation, Animation secondaryAnimation, - Widget child, + Widget child ) { - return SharedXAxisTransition( + return SharedAxisTransition( animation: animation, secondaryAnimation: secondaryAnimation, + transitionType: transitionType, child: child, ); } @@ -88,10 +102,10 @@ class SharedXAxisPageTransitionsBuilder extends PageTransitionsBuilder { /// that have a spatial or navigational relationship. For example, /// transitioning from one page of a sign up page to the next one. /// -/// Consider using [SharedXAxisTransition] within a +/// Consider using [SharedAxisTransition] within a /// [PageTransitionsTheme] if you want to apply this kind of transition to /// [MaterialPageRoute] transitions within a Navigator (see -/// [SharedXAxisPageTransitionsBuilder] for example code). +/// [SharedAxisPageTransitionsBuilder] for example code). /// /// This transition can also be used directly in a /// [PageTransitionSwitcher.transitionBuilder] to transition @@ -114,7 +128,7 @@ class SharedXAxisPageTransitionsBuilder extends PageTransitionsBuilder { /// Animation primaryAnimation, /// Animation secondaryAnimation, /// ) { -/// return SharedXAxisTransition( +/// return SharedAxisTransition( /// animation: primaryAnimation, /// secondaryAnimation: secondaryAnimation, /// child: child, @@ -153,17 +167,19 @@ class SharedXAxisPageTransitionsBuilder extends PageTransitionsBuilder { /// ); /// } /// ``` -class SharedXAxisTransition extends StatefulWidget { - /// Creates a [SharedXAxisTransition]. +class SharedAxisTransition extends StatefulWidget { + /// Creates a [SharedAxisTransition]. /// /// The [animation] and [secondaryAnimation] argument are required and must /// not be null. - const SharedXAxisTransition({ + const SharedAxisTransition({ Key key, @required this.animation, @required this.secondaryAnimation, + @required this.transitionType, this.child, - }) : super(key: key); + }) : assert(transitionType != null), + super(key: key); /// The animation that drives the [child]'s entrance and exit. /// @@ -182,6 +198,9 @@ class SharedXAxisTransition extends StatefulWidget { /// property when the it is used as a page transition. final Animation secondaryAnimation; + /// Determines which type shared axis transition. + final SharedAxisTransitionType transitionType; + /// The widget below this widget in the tree. /// /// This widget will transition in and out as driven by [animation] and @@ -189,10 +208,10 @@ class SharedXAxisTransition extends StatefulWidget { final Widget child; @override - _SharedXAxisTransitionState createState() => _SharedXAxisTransitionState(); + _SharedAxisTransitionState createState() => _SharedAxisTransitionState(); } -class _SharedXAxisTransitionState extends State { +class _SharedAxisTransitionState extends State { AnimationStatus _effectiveAnimationStatus; AnimationStatus _effectiveSecondaryAnimationStatus; @@ -258,7 +277,7 @@ class _SharedXAxisTransitionState extends State { } @override - void didUpdateWidget(SharedXAxisTransition oldWidget) { + void didUpdateWidget(SharedAxisTransition oldWidget) { super.didUpdateWidget(oldWidget); if (oldWidget.animation != widget.animation) { oldWidget.animation.removeStatusListener(_animationListener); @@ -298,6 +317,7 @@ class _SharedXAxisTransitionState extends State { case AnimationStatus.forward: return _EnterTransition( animation: widget.animation, + transitionType: widget.transitionType, child: child, ); case AnimationStatus.dismissed: @@ -305,6 +325,7 @@ class _SharedXAxisTransitionState extends State { case AnimationStatus.completed: return _ExitTransition( animation: _flip(widget.animation), + transitionType: widget.transitionType, child: child, ); } @@ -318,6 +339,7 @@ class _SharedXAxisTransitionState extends State { case AnimationStatus.forward: return _ExitTransition( animation: widget.secondaryAnimation, + transitionType: widget.transitionType, child: child, ); case AnimationStatus.dismissed: @@ -325,6 +347,7 @@ class _SharedXAxisTransitionState extends State { case AnimationStatus.completed: return _EnterTransition( animation: _flip(widget.secondaryAnimation), + transitionType: widget.transitionType, child: child, ); } @@ -339,23 +362,27 @@ class _SharedXAxisTransitionState extends State { class _EnterTransition extends StatelessWidget { const _EnterTransition({ this.animation, + this.transitionType, this.child, }); final Animation animation; + final SharedAxisTransitionType transitionType; final Widget child; static Animatable fadeInTransition = CurveTween(curve: decelerateEasing) .chain(CurveTween(curve: const Interval(0.3, 1.0))); - static Animatable slideInTransition = Tween( - begin: const Offset(30, 0.0), - end: Offset.zero, - ).chain(CurveTween(curve: standardEasing)); - @override Widget build(BuildContext context) { + final Animatable slideInTransition = Tween( + begin: transitionType == SharedAxisTransitionType.horizontal + ? const Offset(30, 0.0) + : const Offset(0.0, 30.0), + end: Offset.zero, + ).chain(CurveTween(curve: standardEasing)); + return FadeTransition( opacity: fadeInTransition.animate(animation), child: Transform.translate( @@ -369,23 +396,27 @@ class _EnterTransition extends StatelessWidget { class _ExitTransition extends StatelessWidget { const _ExitTransition({ this.animation, + this.transitionType, this.child, }); final Animation animation; + final SharedAxisTransitionType transitionType; final Widget child; static Animatable fadeOutTransition = FlippedCurveTween(curve: accelerateEasing) .chain(CurveTween(curve: const Interval(0.0, 0.3))); - static Animatable slideOutTransition = Tween( - begin: Offset.zero, - end: const Offset(30, 0.0), - ).chain(CurveTween(curve: standardEasing)); - @override Widget build(BuildContext context) { + final Animatable slideOutTransition = Tween( + begin: Offset.zero, + end: transitionType == SharedAxisTransitionType.horizontal + ? const Offset(30, 0.0) + : const Offset(0.0, 30.0), + ).chain(CurveTween(curve: standardEasing)); + return FadeTransition( opacity: fadeOutTransition.animate(animation), child: Container( diff --git a/packages/animations/test/shared_axis_transition_test.dart b/packages/animations/test/shared_axis_transition_test.dart new file mode 100644 index 000000000000..777309f321ba --- /dev/null +++ b/packages/animations/test/shared_axis_transition_test.dart @@ -0,0 +1,569 @@ +// Copyright 2019 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:animations/src/shared_axis_transition.dart'; +import 'package:flutter/animation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:flutter/widgets.dart'; + +import 'package:vector_math/vector_math_64.dart'; + +void main() { + for (SharedAxisTransitionType transitionType in SharedAxisTransitionType.values) { + group('$transitionType', () { + testWidgets( + 'SharedAxisPageTransitionsBuilder builds a SharedAxisTransition', + (WidgetTester tester) async { + final AnimationController animation = AnimationController( + vsync: const TestVSync(), + ); + final AnimationController secondaryAnimation = AnimationController( + vsync: const TestVSync(), + ); + + await tester.pumpWidget( + SharedAxisPageTransitionsBuilder( + transitionType: transitionType, + ).buildTransitions( + null, + null, + animation, + secondaryAnimation, + const Placeholder(), + ), + ); + + expect(find.byType(SharedAxisTransition), findsOneWidget); + }, + ); + + testWidgets( + 'SharedAxisTransition runs forward', + (WidgetTester tester) async { + final GlobalKey navigator = GlobalKey(); + const String bottomRoute = '/'; + const String topRoute = '/a'; + + await tester.pumpWidget( + _TestWidget( + navigatorKey: navigator, + transitionType: transitionType, + ), + ); + + expect(find.text(bottomRoute), findsOneWidget); + expect(_getTranslationOffset( + bottomRoute, + tester, + transitionType, + ), 0.0); + expect(_getOpacity(bottomRoute, tester), 1.0); + expect(find.text(topRoute), findsNothing); + + navigator.currentState.pushNamed(topRoute); + await tester.pump(); + await tester.pump(); + + // Bottom route is not offset and fully visible. + expect(find.text(bottomRoute), findsOneWidget); + expect(_getTranslationOffset( + bottomRoute, + tester, + transitionType, + ), 0.0); + expect(_getOpacity(bottomRoute, tester), 1.0); + // Top route is offset to the right by 30.0 pixels + // and not visible yet. + expect(find.text(topRoute), findsOneWidget); + expect(_getTranslationOffset( + topRoute, + tester, + transitionType, + ), 30.0); + expect(_getOpacity(topRoute, tester), 0.0); + + // Jump 3/10ths of the way through the transition, bottom route + // should be be completely faded out while the top route + // is also completely faded out. + // Transition time: 300ms, 3/10 * 300ms = 90ms + await tester.pump(const Duration(milliseconds: 90)); + + // Bottom route is now invisible + expect(find.text(bottomRoute), findsOneWidget); + expect(_getOpacity(bottomRoute, tester), 0.0); + // Top route is still invisible, but moving towards the left. + expect(find.text(topRoute), findsOneWidget); + expect(_getOpacity(topRoute, tester), 0.0); + double topOffset = _getTranslationOffset( + topRoute, + tester, + transitionType, + ); + expect(topOffset, lessThan(30.0)); + expect(topOffset, greaterThan(0.0)); + + // Jump to the middle of fading in + await tester.pump(const Duration(milliseconds: 90)); + // Bottom route is still invisible + expect(find.text(bottomRoute), findsOneWidget); + expect(_getOpacity(bottomRoute, tester), 0.0); + // Top route is fading in + expect(find.text(topRoute), findsOneWidget); + expect(_getOpacity(topRoute, tester), greaterThan(0)); + expect(_getOpacity(topRoute, tester), lessThan(1.0)); + topOffset = _getTranslationOffset( + topRoute, + tester, + transitionType, + ); + expect(topOffset, greaterThan(0.0)); + expect(topOffset, lessThan(30.0)); + + // Jump to the end of the transition + await tester.pump(const Duration(milliseconds: 120)); + // Bottom route is not visible. + expect(find.text(bottomRoute), findsOneWidget); + + expect(_getTranslationOffset( + bottomRoute, + tester, + transitionType, + ), 30.0); + expect(_getOpacity(bottomRoute, tester), 0.0); + // Top route has no offset and is visible. + expect(find.text(topRoute), findsOneWidget); + expect(_getTranslationOffset( + topRoute, + tester, + transitionType, + ), 0.0); + expect(_getOpacity(topRoute, tester), 1.0); + + await tester.pump(const Duration(milliseconds: 1)); + expect(find.text(bottomRoute), findsNothing); + expect(find.text(topRoute), findsOneWidget); + }, + ); + + testWidgets( + 'SharedAxisTransition runs in reverse', + (WidgetTester tester) async { + final GlobalKey navigator = GlobalKey(); + const String bottomRoute = '/'; + const String topRoute = '/a'; + + await tester.pumpWidget( + _TestWidget( + navigatorKey: navigator, + transitionType: transitionType, + ), + ); + + navigator.currentState.pushNamed('/a'); + await tester.pumpAndSettle(); + + expect(find.text(topRoute), findsOneWidget); + expect(_getTranslationOffset( + topRoute, + tester, + transitionType, + ), 0.0); + expect(_getOpacity(topRoute, tester), 1.0); + expect(find.text(bottomRoute), findsNothing); + + navigator.currentState.pop(); + await tester.pump(); + + // Top route is is not offset and fully visible. + expect(find.text(topRoute), findsOneWidget); + expect(_getTranslationOffset( + topRoute, + tester, + transitionType, + ), 0.0); + expect(_getOpacity(topRoute, tester), 1.0); + // Bottom route is offset to the right and is not visible yet. + expect(find.text(bottomRoute), findsOneWidget); + expect(_getTranslationOffset( + bottomRoute, + tester, + transitionType, + ), 30.0); + expect(_getOpacity(bottomRoute, tester), 0.0); + + // Jump 3/10ths of the way through the transition, bottom route + // should be be completely faded out while the top route + // is also completely faded out. + // Transition time: 300ms, 3/10 * 300ms = 90ms + await tester.pump(const Duration(milliseconds: 90)); + + // Top route is now invisible + expect(find.text(topRoute), findsOneWidget); + expect(_getOpacity(topRoute, tester), 0.0); + // Bottom route is still invisible, but moving towards the left. + expect(find.text(bottomRoute), findsOneWidget); + expect(_getOpacity(bottomRoute, tester), + moreOrLessEquals(0, epsilon: 0.005)); + double bottomOffset = _getTranslationOffset( + bottomRoute, + tester, + transitionType, + ); + expect(bottomOffset, greaterThan(0.0)); + expect(bottomOffset, lessThan(30.0)); + + // Jump to the middle of fading in + await tester.pump(const Duration(milliseconds: 90)); + // Top route is still invisible + expect(find.text(topRoute), findsOneWidget); + expect(_getOpacity(topRoute, tester), 0.0); + // Bottom route is fading in + expect(find.text(bottomRoute), findsOneWidget); + expect(_getOpacity(bottomRoute, tester), greaterThan(0)); + expect(_getOpacity(bottomRoute, tester), lessThan(1.0)); + bottomOffset = _getTranslationOffset( + bottomRoute, + tester, + transitionType, + ); + expect(bottomOffset, greaterThan(0.0)); + expect(bottomOffset, lessThan(30.0)); + + // Jump to the end of the transition + await tester.pump(const Duration(milliseconds: 120)); + // Top route is not visible and is offset to the right. + expect(find.text(topRoute), findsOneWidget); + expect(_getTranslationOffset( + topRoute, + tester, + transitionType, + ), 30.0); + expect(_getOpacity(topRoute, tester), 0.0); + // Bottom route is not offset and is visible. + expect(find.text(bottomRoute), findsOneWidget); + expect(_getTranslationOffset( + bottomRoute, + tester, + transitionType, + ), 0.0); + expect(_getOpacity(bottomRoute, tester), 1.0); + + await tester.pump(const Duration(milliseconds: 1)); + expect(find.text(topRoute), findsNothing); + expect(find.text(bottomRoute), findsOneWidget); + }, + ); + + testWidgets( + 'SharedAxisTransition does not jump when interrupted', + (WidgetTester tester) async { + final GlobalKey navigator = GlobalKey(); + const String bottomRoute = '/'; + const String topRoute = '/a'; + + await tester.pumpWidget( + _TestWidget( + navigatorKey: navigator, + transitionType: transitionType, + ), + ); + expect(find.text(bottomRoute), findsOneWidget); + expect(find.text(topRoute), findsNothing); + + navigator.currentState.pushNamed(topRoute); + await tester.pump(); + + // Jump to halfway point of transition. + await tester.pump(const Duration(milliseconds: 150)); + // Bottom route is fully faded out. + expect(find.text(bottomRoute), findsOneWidget); + expect(_getOpacity(bottomRoute, tester), 0.0); + final double halfwayBottomOffset = + _getTranslationOffset( + bottomRoute, + tester, + transitionType, + ); + expect(halfwayBottomOffset, greaterThan(0.0)); + expect(halfwayBottomOffset, lessThan(30.0)); + + // Top route is fading/coming in. + expect(find.text(topRoute), findsOneWidget); + final double halfwayTopOffset = _getTranslationOffset( + topRoute, + tester, + transitionType, + ); + final double halfwayTopOpacity = _getOpacity(topRoute, tester); + expect(halfwayTopOffset, greaterThan(0.0)); + expect(halfwayTopOffset, lessThan(30.0)); + expect(halfwayTopOpacity, greaterThan(0.0)); + expect(halfwayTopOpacity, lessThan(1.0)); + + // Interrupt the transition with a pop. + navigator.currentState.pop(); + await tester.pump(); + + // Nothing should change. + expect(find.text(bottomRoute), findsOneWidget); + expect(_getTranslationOffset( + bottomRoute, + tester, + transitionType, + ), halfwayBottomOffset); + expect(_getOpacity(bottomRoute, tester), 0.0); + expect(find.text(topRoute), findsOneWidget); + expect(_getTranslationOffset( + topRoute, + tester, + transitionType, + ), halfwayTopOffset); + expect(_getOpacity(topRoute, tester), halfwayTopOpacity); + + // Jump to the 1/4 (75 ms) point of transition + await tester.pump(const Duration(milliseconds: 75)); + expect(find.text(bottomRoute), findsOneWidget); + expect(_getTranslationOffset( + bottomRoute, + tester, + transitionType, + ), greaterThan(0.0)); + expect(_getTranslationOffset( + bottomRoute, + tester, + transitionType, + ), lessThan(30.0)); + expect(_getTranslationOffset( + bottomRoute, + tester, + transitionType, + ), + lessThan(halfwayBottomOffset)); + expect(_getOpacity(bottomRoute, tester), greaterThan(0.0)); + expect(_getOpacity(bottomRoute, tester), lessThan(1.0)); + + // Jump to the end. + await tester.pump(const Duration(milliseconds: 75)); + expect(find.text(bottomRoute), findsOneWidget); + expect(_getTranslationOffset( + bottomRoute, + tester, + transitionType, + ), 0.0); + expect(_getOpacity(bottomRoute, tester), 1.0); + expect(find.text(topRoute), findsOneWidget); + expect(_getTranslationOffset( + topRoute, + tester, + transitionType, + ), 30.0); + expect(_getOpacity(topRoute, tester), 0.0); + + await tester.pump(const Duration(milliseconds: 1)); + expect(find.text(topRoute), findsNothing); + expect(find.text(bottomRoute), findsOneWidget); + }, + ); + + testWidgets( + 'State is not lost when transitioning', + (WidgetTester tester) async { + final GlobalKey navigator = GlobalKey(); + const String bottomRoute = '/'; + const String topRoute = '/a'; + + await tester.pumpWidget( + _TestWidget( + navigatorKey: navigator, + contentBuilder: (RouteSettings settings) { + return _StatefulTestWidget( + key: ValueKey(settings.name), + name: settings.name, + ); + }, + transitionType: transitionType, + ), + ); + + final _StatefulTestWidgetState bottomState = tester.state( + find.byKey(const ValueKey(bottomRoute)), + ); + expect(bottomState.widget.name, bottomRoute); + + navigator.currentState.pushNamed(topRoute); + await tester.pump(); + await tester.pump(); + + expect( + tester.state(find.byKey(const ValueKey(bottomRoute))), + bottomState, + ); + final _StatefulTestWidgetState topState = tester.state( + find.byKey(const ValueKey(topRoute)), + ); + expect(topState.widget.name, topRoute); + + await tester.pump(const Duration(milliseconds: 150)); + expect( + tester.state(find.byKey(const ValueKey(bottomRoute))), + bottomState, + ); + expect( + tester.state(find.byKey(const ValueKey(topRoute))), + topState, + ); + + await tester.pumpAndSettle(); + expect( + tester.state(find.byKey( + const ValueKey(bottomRoute), + skipOffstage: false, + )), + bottomState, + ); + expect( + tester.state(find.byKey(const ValueKey(topRoute))), + topState, + ); + + navigator.currentState.pop(); + await tester.pump(); + + expect( + tester.state(find.byKey(const ValueKey(bottomRoute))), + bottomState, + ); + expect( + tester.state(find.byKey(const ValueKey(topRoute))), + topState, + ); + + await tester.pump(const Duration(milliseconds: 150)); + expect( + tester.state(find.byKey(const ValueKey(bottomRoute))), + bottomState, + ); + expect( + tester.state(find.byKey(const ValueKey(topRoute))), + topState, + ); + + await tester.pumpAndSettle(); + expect( + tester.state(find.byKey(const ValueKey(bottomRoute))), + bottomState, + ); + expect(find.byKey(const ValueKey(topRoute)), findsNothing); + }, + ); + }); + } +} + +double _getOpacity(String key, WidgetTester tester) { + final Finder finder = find.ancestor( + of: find.byKey(ValueKey(key)), + matching: find.byType(FadeTransition), + ); + return tester.widgetList(finder).fold(1.0, (double a, Widget widget) { + final FadeTransition transition = widget; + return a * transition.opacity.value; + }); +} + +double _getTranslationOffset( + String key, + WidgetTester tester, + SharedAxisTransitionType transitionType, +) { + final Finder finder = find.ancestor( + of: find.byKey(ValueKey(key)), + matching: find.byType(Transform), + ); + + switch (transitionType) { + case SharedAxisTransitionType.horizontal: + return tester.widgetList(finder).fold( + 0.0, + (double a, Widget widget) { + final Transform transition = widget; + final Vector3 translation = transition.transform.getTranslation(); + return a + translation.x; + }); + break; + case SharedAxisTransitionType.vertical: + return tester.widgetList(finder).fold( + 0.0, + (double a, Widget widget) { + final Transform transition = widget; + final Vector3 translation = transition.transform.getTranslation(); + return a + translation.y; + }); + break; + } + return null; // unreachable +} + +class _TestWidget extends StatelessWidget { + const _TestWidget({ + this.navigatorKey, + this.contentBuilder, + this.transitionType, + }); + + final Key navigatorKey; + final _ContentBuilder contentBuilder; + final SharedAxisTransitionType transitionType; + + @override + Widget build(BuildContext context) { + return MaterialApp( + navigatorKey: navigatorKey, + theme: ThemeData( + platform: TargetPlatform.android, + pageTransitionsTheme: PageTransitionsTheme( + builders: { + TargetPlatform.android: SharedAxisPageTransitionsBuilder( + transitionType: transitionType, + ), + }, + ), + ), + onGenerateRoute: (RouteSettings settings) { + return MaterialPageRoute( + settings: settings, + builder: (BuildContext context) { + return contentBuilder != null + ? contentBuilder(settings) + : Container( + child: Center( + key: ValueKey(settings.name), + child: Text(settings.name), + ), + ); + }, + ); + }, + ); + } +} + +class _StatefulTestWidget extends StatefulWidget { + const _StatefulTestWidget({Key key, this.name}) : super(key: key); + + final String name; + + @override + State<_StatefulTestWidget> createState() => _StatefulTestWidgetState(); +} + +class _StatefulTestWidgetState extends State<_StatefulTestWidget> { + @override + Widget build(BuildContext context) { + return Text(widget.name); + } +} + +typedef _ContentBuilder = Widget Function(RouteSettings settings); diff --git a/packages/animations/test/shared_x_axis_transition_test.dart b/packages/animations/test/shared_x_axis_transition_test.dart deleted file mode 100644 index a0d5d1c1fe13..000000000000 --- a/packages/animations/test/shared_x_axis_transition_test.dart +++ /dev/null @@ -1,434 +0,0 @@ -// Copyright 2019 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import 'package:animations/src/shared_x_axis_transition.dart'; -import 'package:flutter/animation.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:flutter/widgets.dart'; - -void main() { - testWidgets( - 'FadeThroughPageTransitionsBuilder builds a FadeThroughTransition', - (WidgetTester tester) async { - final AnimationController animation = AnimationController( - vsync: const TestVSync(), - ); - final AnimationController secondaryAnimation = AnimationController( - vsync: const TestVSync(), - ); - - await tester.pumpWidget( - const SharedXAxisPageTransitionsBuilder().buildTransitions( - null, - null, - animation, - secondaryAnimation, - const Placeholder(), - ), - ); - - expect(find.byType(SharedXAxisTransition), findsOneWidget); - }, - ); - - testWidgets( - 'SharedXAxisTransition runs forward', - (WidgetTester tester) async { - final GlobalKey navigator = GlobalKey(); - const String bottomRoute = '/'; - const String topRoute = '/a'; - - await tester.pumpWidget( - _TestWidget(navigatorKey: navigator), - ); - - expect(find.text(bottomRoute), findsOneWidget); - expect(_getTranslationOffset(bottomRoute, tester), 0.0); - expect(_getOpacity(bottomRoute, tester), 1.0); - expect(find.text(topRoute), findsNothing); - - navigator.currentState.pushNamed(topRoute); - await tester.pump(); - await tester.pump(); - - // Bottom route is not offset and fully visible. - expect(find.text(bottomRoute), findsOneWidget); - expect(_getTranslationOffset(bottomRoute, tester), 0.0); - expect(_getOpacity(bottomRoute, tester), 1.0); - // Top route is offset to the right by 30.0 pixels - // and not visible yet. - expect(find.text(topRoute), findsOneWidget); - expect(_getTranslationOffset(topRoute, tester), 30.0); - expect(_getOpacity(topRoute, tester), 0.0); - - // Jump 3/10ths of the way through the transition, bottom route - // should be be completely faded out while the top route - // is also completely faded out. - // Transition time: 300ms, 3/10 * 300ms = 90ms - await tester.pump(const Duration(milliseconds: 90)); - - // Bottom route is now invisible - expect(find.text(bottomRoute), findsOneWidget); - expect(_getOpacity(bottomRoute, tester), 0.0); - // Top route is still invisible, but moving towards the left. - expect(find.text(topRoute), findsOneWidget); - expect(_getOpacity(topRoute, tester), 0.0); - double topOffset = _getTranslationOffset(topRoute, tester); - expect(topOffset, lessThan(30.0)); - expect(topOffset, greaterThan(0.0)); - - // Jump to the middle of fading in - await tester.pump(const Duration(milliseconds: 90)); - // Bottom route is still invisible - expect(find.text(bottomRoute), findsOneWidget); - expect(_getOpacity(bottomRoute, tester), 0.0); - // Top route is fading in - expect(find.text(topRoute), findsOneWidget); - expect(_getOpacity(topRoute, tester), greaterThan(0)); - expect(_getOpacity(topRoute, tester), lessThan(1.0)); - topOffset = _getTranslationOffset(topRoute, tester); - expect(topOffset, greaterThan(0.0)); - expect(topOffset, lessThan(30.0)); - - // Jump to the end of the transition - await tester.pump(const Duration(milliseconds: 120)); - // Bottom route is not visible. - expect(find.text(bottomRoute), findsOneWidget); - - expect(_getTranslationOffset(bottomRoute, tester), 30.0); - expect(_getOpacity(bottomRoute, tester), 0.0); - // Top route has no offset and is visible. - expect(find.text(topRoute), findsOneWidget); - expect(_getTranslationOffset(topRoute, tester), 0.0); - expect(_getOpacity(topRoute, tester), 1.0); - - await tester.pump(const Duration(milliseconds: 1)); - expect(find.text(bottomRoute), findsNothing); - expect(find.text(topRoute), findsOneWidget); - }, - ); - - testWidgets( - 'SharedXAxisTransition runs in reverse', - (WidgetTester tester) async { - final GlobalKey navigator = GlobalKey(); - const String bottomRoute = '/'; - const String topRoute = '/a'; - - await tester.pumpWidget( - _TestWidget(navigatorKey: navigator), - ); - - navigator.currentState.pushNamed('/a'); - await tester.pumpAndSettle(); - - expect(find.text(topRoute), findsOneWidget); - expect(_getTranslationOffset(topRoute, tester), 0.0); - expect(_getOpacity(topRoute, tester), 1.0); - expect(find.text(bottomRoute), findsNothing); - - navigator.currentState.pop(); - await tester.pump(); - - // Top route is is not offset and fully visible. - expect(find.text(topRoute), findsOneWidget); - expect(_getTranslationOffset(topRoute, tester), 0.0); - expect(_getOpacity(topRoute, tester), 1.0); - // Bottom route is offset to the right and is not visible yet. - expect(find.text(bottomRoute), findsOneWidget); - expect(_getTranslationOffset(bottomRoute, tester), 30.0); - expect(_getOpacity(bottomRoute, tester), 0.0); - - // Jump 3/10ths of the way through the transition, bottom route - // should be be completely faded out while the top route - // is also completely faded out. - // Transition time: 300ms, 3/10 * 300ms = 90ms - await tester.pump(const Duration(milliseconds: 90)); - - // Top route is now invisible - expect(find.text(topRoute), findsOneWidget); - expect(_getOpacity(topRoute, tester), 0.0); - // Bottom route is still invisible, but moving towards the left. - expect(find.text(bottomRoute), findsOneWidget); - expect(_getOpacity(bottomRoute, tester), - moreOrLessEquals(0, epsilon: 0.005)); - double bottomOffset = _getTranslationOffset(bottomRoute, tester); - expect(bottomOffset, greaterThan(0.0)); - expect(bottomOffset, lessThan(30.0)); - - // Jump to the middle of fading in - await tester.pump(const Duration(milliseconds: 90)); - // Top route is still invisible - expect(find.text(topRoute), findsOneWidget); - expect(_getOpacity(topRoute, tester), 0.0); - // Bottom route is fading in - expect(find.text(bottomRoute), findsOneWidget); - expect(_getOpacity(bottomRoute, tester), greaterThan(0)); - expect(_getOpacity(bottomRoute, tester), lessThan(1.0)); - bottomOffset = _getTranslationOffset(bottomRoute, tester); - expect(bottomOffset, greaterThan(0.0)); - expect(bottomOffset, lessThan(30.0)); - - // Jump to the end of the transition - await tester.pump(const Duration(milliseconds: 120)); - // Top route is not visible and is offset to the right. - expect(find.text(topRoute), findsOneWidget); - expect(_getTranslationOffset(topRoute, tester), 30.0); - expect(_getOpacity(topRoute, tester), 0.0); - // Bottom route is not offset and is visible. - expect(find.text(bottomRoute), findsOneWidget); - expect(_getTranslationOffset(bottomRoute, tester), 0.0); - expect(_getOpacity(bottomRoute, tester), 1.0); - - await tester.pump(const Duration(milliseconds: 1)); - expect(find.text(topRoute), findsNothing); - expect(find.text(bottomRoute), findsOneWidget); - }, - ); - - testWidgets( - 'FadeThroughTransition does not jump when interrupted', - (WidgetTester tester) async { - final GlobalKey navigator = GlobalKey(); - const String bottomRoute = '/'; - const String topRoute = '/a'; - - await tester.pumpWidget( - _TestWidget( - navigatorKey: navigator, - ), - ); - expect(find.text(bottomRoute), findsOneWidget); - expect(find.text(topRoute), findsNothing); - - navigator.currentState.pushNamed(topRoute); - await tester.pump(); - - // Jump to halfway point of transition. - await tester.pump(const Duration(milliseconds: 150)); - // Bottom route is fully faded out. - expect(find.text(bottomRoute), findsOneWidget); - expect(_getOpacity(bottomRoute, tester), 0.0); - final double halfwayBottomOffset = - _getTranslationOffset(bottomRoute, tester); - expect(halfwayBottomOffset, greaterThan(0.0)); - expect(halfwayBottomOffset, lessThan(30.0)); - - // Top route is fading/coming in. - expect(find.text(topRoute), findsOneWidget); - final double halfwayTopOffset = _getTranslationOffset(topRoute, tester); - final double halfwayTopOpacity = _getOpacity(topRoute, tester); - expect(halfwayTopOffset, greaterThan(0.0)); - expect(halfwayTopOffset, lessThan(30.0)); - expect(halfwayTopOpacity, greaterThan(0.0)); - expect(halfwayTopOpacity, lessThan(1.0)); - - // Interrupt the transition with a pop. - navigator.currentState.pop(); - await tester.pump(); - - // Nothing should change. - expect(find.text(bottomRoute), findsOneWidget); - expect(_getTranslationOffset(bottomRoute, tester), halfwayBottomOffset); - expect(_getOpacity(bottomRoute, tester), 0.0); - expect(find.text(topRoute), findsOneWidget); - expect(_getTranslationOffset(topRoute, tester), halfwayTopOffset); - expect(_getOpacity(topRoute, tester), halfwayTopOpacity); - - // Jump to the 1/4 (75 ms) point of transition - await tester.pump(const Duration(milliseconds: 75)); - expect(find.text(bottomRoute), findsOneWidget); - expect(_getTranslationOffset(bottomRoute, tester), greaterThan(0.0)); - expect(_getTranslationOffset(bottomRoute, tester), lessThan(30.0)); - expect(_getTranslationOffset(bottomRoute, tester), - lessThan(halfwayBottomOffset)); - expect(_getOpacity(bottomRoute, tester), greaterThan(0.0)); - expect(_getOpacity(bottomRoute, tester), lessThan(1.0)); - - // Jump to the end. - await tester.pump(const Duration(milliseconds: 75)); - expect(find.text(bottomRoute), findsOneWidget); - expect(_getTranslationOffset(bottomRoute, tester), 0.0); - expect(_getOpacity(bottomRoute, tester), 1.0); - expect(find.text(topRoute), findsOneWidget); - expect(_getTranslationOffset(topRoute, tester), 30.0); - expect(_getOpacity(topRoute, tester), 0.0); - - await tester.pump(const Duration(milliseconds: 1)); - expect(find.text(topRoute), findsNothing); - expect(find.text(bottomRoute), findsOneWidget); - }, - ); - - testWidgets( - 'State is not lost when transitioning', - (WidgetTester tester) async { - final GlobalKey navigator = GlobalKey(); - const String bottomRoute = '/'; - const String topRoute = '/a'; - - await tester.pumpWidget( - _TestWidget( - navigatorKey: navigator, - contentBuilder: (RouteSettings settings) { - return _StatefulTestWidget( - key: ValueKey(settings.name), - name: settings.name, - ); - }, - ), - ); - - final _StatefulTestWidgetState bottomState = tester.state( - find.byKey(const ValueKey(bottomRoute)), - ); - expect(bottomState.widget.name, bottomRoute); - - navigator.currentState.pushNamed(topRoute); - await tester.pump(); - await tester.pump(); - - expect( - tester.state(find.byKey(const ValueKey(bottomRoute))), - bottomState, - ); - final _StatefulTestWidgetState topState = tester.state( - find.byKey(const ValueKey(topRoute)), - ); - expect(topState.widget.name, topRoute); - - await tester.pump(const Duration(milliseconds: 150)); - expect( - tester.state(find.byKey(const ValueKey(bottomRoute))), - bottomState, - ); - expect( - tester.state(find.byKey(const ValueKey(topRoute))), - topState, - ); - - await tester.pumpAndSettle(); - expect( - tester.state(find.byKey( - const ValueKey(bottomRoute), - skipOffstage: false, - )), - bottomState, - ); - expect( - tester.state(find.byKey(const ValueKey(topRoute))), - topState, - ); - - navigator.currentState.pop(); - await tester.pump(); - - expect( - tester.state(find.byKey(const ValueKey(bottomRoute))), - bottomState, - ); - expect( - tester.state(find.byKey(const ValueKey(topRoute))), - topState, - ); - - await tester.pump(const Duration(milliseconds: 150)); - expect( - tester.state(find.byKey(const ValueKey(bottomRoute))), - bottomState, - ); - expect( - tester.state(find.byKey(const ValueKey(topRoute))), - topState, - ); - - await tester.pumpAndSettle(); - expect( - tester.state(find.byKey(const ValueKey(bottomRoute))), - bottomState, - ); - expect(find.byKey(const ValueKey(topRoute)), findsNothing); - }, - ); -} - -double _getOpacity(String key, WidgetTester tester) { - final Finder finder = find.ancestor( - of: find.byKey(ValueKey(key)), - matching: find.byType(FadeTransition), - ); - return tester.widgetList(finder).fold(1.0, (double a, Widget widget) { - final FadeTransition transition = widget; - return a * transition.opacity.value; - }); -} - -double _getTranslationOffset(String key, WidgetTester tester) { - final Finder finder = find.ancestor( - of: find.byKey(ValueKey(key)), - matching: find.byType(Transform), - ); - - return tester.widgetList(finder).fold(0.0, - (double a, Widget widget) { - final Transform transition = widget; - return a + transition.transform.getTranslation().x; - }); -} - -class _TestWidget extends StatelessWidget { - const _TestWidget({this.navigatorKey, this.contentBuilder}); - - final Key navigatorKey; - final _ContentBuilder contentBuilder; - - @override - Widget build(BuildContext context) { - return MaterialApp( - navigatorKey: navigatorKey, - theme: ThemeData( - platform: TargetPlatform.android, - pageTransitionsTheme: const PageTransitionsTheme( - builders: { - TargetPlatform.android: SharedXAxisPageTransitionsBuilder(), - }, - ), - ), - onGenerateRoute: (RouteSettings settings) { - return MaterialPageRoute( - settings: settings, - builder: (BuildContext context) { - return contentBuilder != null - ? contentBuilder(settings) - : Container( - child: Center( - key: ValueKey(settings.name), - child: Text(settings.name), - ), - ); - }, - ); - }, - ); - } -} - -class _StatefulTestWidget extends StatefulWidget { - const _StatefulTestWidget({Key key, this.name}) : super(key: key); - - final String name; - - @override - State<_StatefulTestWidget> createState() => _StatefulTestWidgetState(); -} - -class _StatefulTestWidgetState extends State<_StatefulTestWidget> { - @override - Widget build(BuildContext context) { - return Text(widget.name); - } -} - -typedef _ContentBuilder = Widget Function(RouteSettings settings); From f5621b680b72204f1829ef77a21bb547175e3c5b Mon Sep 17 00:00:00 2001 From: Shi Hao Hong Date: Thu, 2 Jan 2020 12:26:06 -0800 Subject: [PATCH 28/37] fix export statement --- packages/animations/lib/animations.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/animations/lib/animations.dart b/packages/animations/lib/animations.dart index cf58ad52c4ed..3b48f3e5cde3 100644 --- a/packages/animations/lib/animations.dart +++ b/packages/animations/lib/animations.dart @@ -5,4 +5,4 @@ export 'src/fade_through_transition.dart'; export 'src/open_container.dart'; export 'src/page_transition_switcher.dart'; -export 'src/shared_x_axis_transition.dart'; +export 'src/shared_axis_transition.dart'; From d436269e1714b9841a81b742802617225be62a5f Mon Sep 17 00:00:00 2001 From: Shi Hao Hong Date: Thu, 2 Jan 2020 12:35:06 -0800 Subject: [PATCH 29/37] Separate out vertical and horizontal tests in case values no longer match completely --- .../test/shared_axis_transition_test.dart | 1341 +++++++++++------ 1 file changed, 893 insertions(+), 448 deletions(-) diff --git a/packages/animations/test/shared_axis_transition_test.dart b/packages/animations/test/shared_axis_transition_test.dart index 777309f321ba..c62108be0256 100644 --- a/packages/animations/test/shared_axis_transition_test.dart +++ b/packages/animations/test/shared_axis_transition_test.dart @@ -11,455 +11,900 @@ import 'package:flutter/widgets.dart'; import 'package:vector_math/vector_math_64.dart'; void main() { - for (SharedAxisTransitionType transitionType in SharedAxisTransitionType.values) { - group('$transitionType', () { - testWidgets( - 'SharedAxisPageTransitionsBuilder builds a SharedAxisTransition', - (WidgetTester tester) async { - final AnimationController animation = AnimationController( - vsync: const TestVSync(), - ); - final AnimationController secondaryAnimation = AnimationController( - vsync: const TestVSync(), - ); - - await tester.pumpWidget( - SharedAxisPageTransitionsBuilder( - transitionType: transitionType, - ).buildTransitions( - null, - null, - animation, - secondaryAnimation, - const Placeholder(), - ), - ); - - expect(find.byType(SharedAxisTransition), findsOneWidget); - }, - ); - - testWidgets( - 'SharedAxisTransition runs forward', - (WidgetTester tester) async { - final GlobalKey navigator = GlobalKey(); - const String bottomRoute = '/'; - const String topRoute = '/a'; - - await tester.pumpWidget( - _TestWidget( - navigatorKey: navigator, - transitionType: transitionType, - ), - ); - - expect(find.text(bottomRoute), findsOneWidget); - expect(_getTranslationOffset( - bottomRoute, - tester, - transitionType, - ), 0.0); - expect(_getOpacity(bottomRoute, tester), 1.0); - expect(find.text(topRoute), findsNothing); - - navigator.currentState.pushNamed(topRoute); - await tester.pump(); - await tester.pump(); - - // Bottom route is not offset and fully visible. - expect(find.text(bottomRoute), findsOneWidget); - expect(_getTranslationOffset( - bottomRoute, - tester, - transitionType, - ), 0.0); - expect(_getOpacity(bottomRoute, tester), 1.0); - // Top route is offset to the right by 30.0 pixels - // and not visible yet. - expect(find.text(topRoute), findsOneWidget); - expect(_getTranslationOffset( - topRoute, - tester, - transitionType, - ), 30.0); - expect(_getOpacity(topRoute, tester), 0.0); - - // Jump 3/10ths of the way through the transition, bottom route - // should be be completely faded out while the top route - // is also completely faded out. - // Transition time: 300ms, 3/10 * 300ms = 90ms - await tester.pump(const Duration(milliseconds: 90)); - - // Bottom route is now invisible - expect(find.text(bottomRoute), findsOneWidget); - expect(_getOpacity(bottomRoute, tester), 0.0); - // Top route is still invisible, but moving towards the left. - expect(find.text(topRoute), findsOneWidget); - expect(_getOpacity(topRoute, tester), 0.0); - double topOffset = _getTranslationOffset( - topRoute, - tester, - transitionType, - ); - expect(topOffset, lessThan(30.0)); - expect(topOffset, greaterThan(0.0)); - - // Jump to the middle of fading in - await tester.pump(const Duration(milliseconds: 90)); - // Bottom route is still invisible - expect(find.text(bottomRoute), findsOneWidget); - expect(_getOpacity(bottomRoute, tester), 0.0); - // Top route is fading in - expect(find.text(topRoute), findsOneWidget); - expect(_getOpacity(topRoute, tester), greaterThan(0)); - expect(_getOpacity(topRoute, tester), lessThan(1.0)); - topOffset = _getTranslationOffset( - topRoute, - tester, - transitionType, - ); - expect(topOffset, greaterThan(0.0)); - expect(topOffset, lessThan(30.0)); - - // Jump to the end of the transition - await tester.pump(const Duration(milliseconds: 120)); - // Bottom route is not visible. - expect(find.text(bottomRoute), findsOneWidget); - - expect(_getTranslationOffset( - bottomRoute, - tester, - transitionType, - ), 30.0); - expect(_getOpacity(bottomRoute, tester), 0.0); - // Top route has no offset and is visible. - expect(find.text(topRoute), findsOneWidget); - expect(_getTranslationOffset( - topRoute, - tester, - transitionType, - ), 0.0); - expect(_getOpacity(topRoute, tester), 1.0); - - await tester.pump(const Duration(milliseconds: 1)); - expect(find.text(bottomRoute), findsNothing); - expect(find.text(topRoute), findsOneWidget); - }, - ); - - testWidgets( - 'SharedAxisTransition runs in reverse', - (WidgetTester tester) async { - final GlobalKey navigator = GlobalKey(); - const String bottomRoute = '/'; - const String topRoute = '/a'; - - await tester.pumpWidget( - _TestWidget( - navigatorKey: navigator, - transitionType: transitionType, - ), - ); - - navigator.currentState.pushNamed('/a'); - await tester.pumpAndSettle(); - - expect(find.text(topRoute), findsOneWidget); - expect(_getTranslationOffset( - topRoute, - tester, - transitionType, - ), 0.0); - expect(_getOpacity(topRoute, tester), 1.0); - expect(find.text(bottomRoute), findsNothing); - - navigator.currentState.pop(); - await tester.pump(); - - // Top route is is not offset and fully visible. - expect(find.text(topRoute), findsOneWidget); - expect(_getTranslationOffset( - topRoute, - tester, - transitionType, - ), 0.0); - expect(_getOpacity(topRoute, tester), 1.0); - // Bottom route is offset to the right and is not visible yet. - expect(find.text(bottomRoute), findsOneWidget); - expect(_getTranslationOffset( - bottomRoute, - tester, - transitionType, - ), 30.0); - expect(_getOpacity(bottomRoute, tester), 0.0); - - // Jump 3/10ths of the way through the transition, bottom route - // should be be completely faded out while the top route - // is also completely faded out. - // Transition time: 300ms, 3/10 * 300ms = 90ms - await tester.pump(const Duration(milliseconds: 90)); - - // Top route is now invisible - expect(find.text(topRoute), findsOneWidget); - expect(_getOpacity(topRoute, tester), 0.0); - // Bottom route is still invisible, but moving towards the left. - expect(find.text(bottomRoute), findsOneWidget); - expect(_getOpacity(bottomRoute, tester), - moreOrLessEquals(0, epsilon: 0.005)); - double bottomOffset = _getTranslationOffset( - bottomRoute, - tester, - transitionType, - ); - expect(bottomOffset, greaterThan(0.0)); - expect(bottomOffset, lessThan(30.0)); - - // Jump to the middle of fading in - await tester.pump(const Duration(milliseconds: 90)); - // Top route is still invisible - expect(find.text(topRoute), findsOneWidget); - expect(_getOpacity(topRoute, tester), 0.0); - // Bottom route is fading in - expect(find.text(bottomRoute), findsOneWidget); - expect(_getOpacity(bottomRoute, tester), greaterThan(0)); - expect(_getOpacity(bottomRoute, tester), lessThan(1.0)); - bottomOffset = _getTranslationOffset( - bottomRoute, - tester, - transitionType, - ); - expect(bottomOffset, greaterThan(0.0)); - expect(bottomOffset, lessThan(30.0)); - - // Jump to the end of the transition - await tester.pump(const Duration(milliseconds: 120)); - // Top route is not visible and is offset to the right. - expect(find.text(topRoute), findsOneWidget); - expect(_getTranslationOffset( - topRoute, - tester, - transitionType, - ), 30.0); - expect(_getOpacity(topRoute, tester), 0.0); - // Bottom route is not offset and is visible. - expect(find.text(bottomRoute), findsOneWidget); - expect(_getTranslationOffset( - bottomRoute, - tester, - transitionType, - ), 0.0); - expect(_getOpacity(bottomRoute, tester), 1.0); - - await tester.pump(const Duration(milliseconds: 1)); - expect(find.text(topRoute), findsNothing); - expect(find.text(bottomRoute), findsOneWidget); - }, - ); - - testWidgets( - 'SharedAxisTransition does not jump when interrupted', - (WidgetTester tester) async { - final GlobalKey navigator = GlobalKey(); - const String bottomRoute = '/'; - const String topRoute = '/a'; - - await tester.pumpWidget( - _TestWidget( - navigatorKey: navigator, - transitionType: transitionType, - ), - ); - expect(find.text(bottomRoute), findsOneWidget); - expect(find.text(topRoute), findsNothing); - - navigator.currentState.pushNamed(topRoute); - await tester.pump(); - - // Jump to halfway point of transition. - await tester.pump(const Duration(milliseconds: 150)); - // Bottom route is fully faded out. - expect(find.text(bottomRoute), findsOneWidget); - expect(_getOpacity(bottomRoute, tester), 0.0); - final double halfwayBottomOffset = - _getTranslationOffset( - bottomRoute, - tester, - transitionType, - ); - expect(halfwayBottomOffset, greaterThan(0.0)); - expect(halfwayBottomOffset, lessThan(30.0)); - - // Top route is fading/coming in. - expect(find.text(topRoute), findsOneWidget); - final double halfwayTopOffset = _getTranslationOffset( - topRoute, - tester, - transitionType, - ); - final double halfwayTopOpacity = _getOpacity(topRoute, tester); - expect(halfwayTopOffset, greaterThan(0.0)); - expect(halfwayTopOffset, lessThan(30.0)); - expect(halfwayTopOpacity, greaterThan(0.0)); - expect(halfwayTopOpacity, lessThan(1.0)); - - // Interrupt the transition with a pop. - navigator.currentState.pop(); - await tester.pump(); - - // Nothing should change. - expect(find.text(bottomRoute), findsOneWidget); - expect(_getTranslationOffset( - bottomRoute, - tester, - transitionType, - ), halfwayBottomOffset); - expect(_getOpacity(bottomRoute, tester), 0.0); - expect(find.text(topRoute), findsOneWidget); - expect(_getTranslationOffset( - topRoute, - tester, - transitionType, - ), halfwayTopOffset); - expect(_getOpacity(topRoute, tester), halfwayTopOpacity); - - // Jump to the 1/4 (75 ms) point of transition - await tester.pump(const Duration(milliseconds: 75)); - expect(find.text(bottomRoute), findsOneWidget); - expect(_getTranslationOffset( - bottomRoute, - tester, - transitionType, - ), greaterThan(0.0)); - expect(_getTranslationOffset( - bottomRoute, - tester, - transitionType, - ), lessThan(30.0)); - expect(_getTranslationOffset( - bottomRoute, - tester, - transitionType, + group('SharedAxisTransitionType.horizontal', () { + testWidgets( + 'SharedAxisPageTransitionsBuilder builds a SharedAxisTransition', + (WidgetTester tester) async { + final AnimationController animation = AnimationController( + vsync: const TestVSync(), + ); + final AnimationController secondaryAnimation = AnimationController( + vsync: const TestVSync(), + ); + + await tester.pumpWidget( + const SharedAxisPageTransitionsBuilder( + transitionType: SharedAxisTransitionType.horizontal, + ).buildTransitions( + null, + null, + animation, + secondaryAnimation, + const Placeholder(), ), - lessThan(halfwayBottomOffset)); - expect(_getOpacity(bottomRoute, tester), greaterThan(0.0)); - expect(_getOpacity(bottomRoute, tester), lessThan(1.0)); - - // Jump to the end. - await tester.pump(const Duration(milliseconds: 75)); - expect(find.text(bottomRoute), findsOneWidget); - expect(_getTranslationOffset( - bottomRoute, - tester, - transitionType, - ), 0.0); - expect(_getOpacity(bottomRoute, tester), 1.0); - expect(find.text(topRoute), findsOneWidget); - expect(_getTranslationOffset( - topRoute, - tester, - transitionType, - ), 30.0); - expect(_getOpacity(topRoute, tester), 0.0); - - await tester.pump(const Duration(milliseconds: 1)); - expect(find.text(topRoute), findsNothing); - expect(find.text(bottomRoute), findsOneWidget); - }, - ); - - testWidgets( - 'State is not lost when transitioning', - (WidgetTester tester) async { - final GlobalKey navigator = GlobalKey(); - const String bottomRoute = '/'; - const String topRoute = '/a'; - - await tester.pumpWidget( - _TestWidget( - navigatorKey: navigator, - contentBuilder: (RouteSettings settings) { - return _StatefulTestWidget( - key: ValueKey(settings.name), - name: settings.name, - ); - }, - transitionType: transitionType, - ), - ); - - final _StatefulTestWidgetState bottomState = tester.state( - find.byKey(const ValueKey(bottomRoute)), - ); - expect(bottomState.widget.name, bottomRoute); - - navigator.currentState.pushNamed(topRoute); - await tester.pump(); - await tester.pump(); - - expect( - tester.state(find.byKey(const ValueKey(bottomRoute))), - bottomState, - ); - final _StatefulTestWidgetState topState = tester.state( - find.byKey(const ValueKey(topRoute)), - ); - expect(topState.widget.name, topRoute); - - await tester.pump(const Duration(milliseconds: 150)); - expect( - tester.state(find.byKey(const ValueKey(bottomRoute))), - bottomState, - ); - expect( - tester.state(find.byKey(const ValueKey(topRoute))), - topState, - ); - - await tester.pumpAndSettle(); - expect( - tester.state(find.byKey( - const ValueKey(bottomRoute), - skipOffstage: false, - )), - bottomState, - ); - expect( - tester.state(find.byKey(const ValueKey(topRoute))), - topState, - ); - - navigator.currentState.pop(); - await tester.pump(); - - expect( - tester.state(find.byKey(const ValueKey(bottomRoute))), - bottomState, - ); - expect( - tester.state(find.byKey(const ValueKey(topRoute))), - topState, - ); - - await tester.pump(const Duration(milliseconds: 150)); - expect( - tester.state(find.byKey(const ValueKey(bottomRoute))), - bottomState, - ); - expect( - tester.state(find.byKey(const ValueKey(topRoute))), - topState, - ); - - await tester.pumpAndSettle(); - expect( - tester.state(find.byKey(const ValueKey(bottomRoute))), - bottomState, - ); - expect(find.byKey(const ValueKey(topRoute)), findsNothing); - }, - ); - }); - } + ); + + expect(find.byType(SharedAxisTransition), findsOneWidget); + }, + ); + + testWidgets( + 'SharedAxisTransition runs forward', + (WidgetTester tester) async { + final GlobalKey navigator = GlobalKey(); + const String bottomRoute = '/'; + const String topRoute = '/a'; + + await tester.pumpWidget( + _TestWidget( + navigatorKey: navigator, + transitionType: SharedAxisTransitionType.horizontal, + ), + ); + + expect(find.text(bottomRoute), findsOneWidget); + expect(_getTranslationOffset( + bottomRoute, + tester, + SharedAxisTransitionType.horizontal, + ), 0.0); + expect(_getOpacity(bottomRoute, tester), 1.0); + expect(find.text(topRoute), findsNothing); + + navigator.currentState.pushNamed(topRoute); + await tester.pump(); + await tester.pump(); + + // Bottom route is not offset and fully visible. + expect(find.text(bottomRoute), findsOneWidget); + expect(_getTranslationOffset( + bottomRoute, + tester, + SharedAxisTransitionType.horizontal, + ), 0.0); + expect(_getOpacity(bottomRoute, tester), 1.0); + // Top route is offset to the right by 30.0 pixels + // and not visible yet. + expect(find.text(topRoute), findsOneWidget); + expect(_getTranslationOffset( + topRoute, + tester, + SharedAxisTransitionType.horizontal, + ), 30.0); + expect(_getOpacity(topRoute, tester), 0.0); + + // Jump 3/10ths of the way through the transition, bottom route + // should be be completely faded out while the top route + // is also completely faded out. + // Transition time: 300ms, 3/10 * 300ms = 90ms + await tester.pump(const Duration(milliseconds: 90)); + + // Bottom route is now invisible + expect(find.text(bottomRoute), findsOneWidget); + expect(_getOpacity(bottomRoute, tester), 0.0); + // Top route is still invisible, but moving towards the left. + expect(find.text(topRoute), findsOneWidget); + expect(_getOpacity(topRoute, tester), 0.0); + double topOffset = _getTranslationOffset( + topRoute, + tester, + SharedAxisTransitionType.horizontal, + ); + expect(topOffset, lessThan(30.0)); + expect(topOffset, greaterThan(0.0)); + + // Jump to the middle of fading in + await tester.pump(const Duration(milliseconds: 90)); + // Bottom route is still invisible + expect(find.text(bottomRoute), findsOneWidget); + expect(_getOpacity(bottomRoute, tester), 0.0); + // Top route is fading in + expect(find.text(topRoute), findsOneWidget); + expect(_getOpacity(topRoute, tester), greaterThan(0)); + expect(_getOpacity(topRoute, tester), lessThan(1.0)); + topOffset = _getTranslationOffset( + topRoute, + tester, + SharedAxisTransitionType.horizontal, + ); + expect(topOffset, greaterThan(0.0)); + expect(topOffset, lessThan(30.0)); + + // Jump to the end of the transition + await tester.pump(const Duration(milliseconds: 120)); + // Bottom route is not visible. + expect(find.text(bottomRoute), findsOneWidget); + + expect(_getTranslationOffset( + bottomRoute, + tester, + SharedAxisTransitionType.horizontal, + ), 30.0); + expect(_getOpacity(bottomRoute, tester), 0.0); + // Top route has no offset and is visible. + expect(find.text(topRoute), findsOneWidget); + expect(_getTranslationOffset( + topRoute, + tester, + SharedAxisTransitionType.horizontal, + ), 0.0); + expect(_getOpacity(topRoute, tester), 1.0); + + await tester.pump(const Duration(milliseconds: 1)); + expect(find.text(bottomRoute), findsNothing); + expect(find.text(topRoute), findsOneWidget); + }, + ); + + testWidgets( + 'SharedAxisTransition runs in reverse', + (WidgetTester tester) async { + final GlobalKey navigator = GlobalKey(); + const String bottomRoute = '/'; + const String topRoute = '/a'; + + await tester.pumpWidget( + _TestWidget( + navigatorKey: navigator, + transitionType: SharedAxisTransitionType.horizontal, + ), + ); + + navigator.currentState.pushNamed('/a'); + await tester.pumpAndSettle(); + + expect(find.text(topRoute), findsOneWidget); + expect(_getTranslationOffset( + topRoute, + tester, + SharedAxisTransitionType.horizontal, + ), 0.0); + expect(_getOpacity(topRoute, tester), 1.0); + expect(find.text(bottomRoute), findsNothing); + + navigator.currentState.pop(); + await tester.pump(); + + // Top route is is not offset and fully visible. + expect(find.text(topRoute), findsOneWidget); + expect(_getTranslationOffset( + topRoute, + tester, + SharedAxisTransitionType.horizontal, + ), 0.0); + expect(_getOpacity(topRoute, tester), 1.0); + // Bottom route is offset to the right and is not visible yet. + expect(find.text(bottomRoute), findsOneWidget); + expect(_getTranslationOffset( + bottomRoute, + tester, + SharedAxisTransitionType.horizontal, + ), 30.0); + expect(_getOpacity(bottomRoute, tester), 0.0); + + // Jump 3/10ths of the way through the transition, bottom route + // should be be completely faded out while the top route + // is also completely faded out. + // Transition time: 300ms, 3/10 * 300ms = 90ms + await tester.pump(const Duration(milliseconds: 90)); + + // Top route is now invisible + expect(find.text(topRoute), findsOneWidget); + expect(_getOpacity(topRoute, tester), 0.0); + // Bottom route is still invisible, but moving towards the left. + expect(find.text(bottomRoute), findsOneWidget); + expect(_getOpacity(bottomRoute, tester), + moreOrLessEquals(0, epsilon: 0.005)); + double bottomOffset = _getTranslationOffset( + bottomRoute, + tester, + SharedAxisTransitionType.horizontal, + ); + expect(bottomOffset, greaterThan(0.0)); + expect(bottomOffset, lessThan(30.0)); + + // Jump to the middle of fading in + await tester.pump(const Duration(milliseconds: 90)); + // Top route is still invisible + expect(find.text(topRoute), findsOneWidget); + expect(_getOpacity(topRoute, tester), 0.0); + // Bottom route is fading in + expect(find.text(bottomRoute), findsOneWidget); + expect(_getOpacity(bottomRoute, tester), greaterThan(0)); + expect(_getOpacity(bottomRoute, tester), lessThan(1.0)); + bottomOffset = _getTranslationOffset( + bottomRoute, + tester, + SharedAxisTransitionType.horizontal, + ); + expect(bottomOffset, greaterThan(0.0)); + expect(bottomOffset, lessThan(30.0)); + + // Jump to the end of the transition + await tester.pump(const Duration(milliseconds: 120)); + // Top route is not visible and is offset to the right. + expect(find.text(topRoute), findsOneWidget); + expect(_getTranslationOffset( + topRoute, + tester, + SharedAxisTransitionType.horizontal, + ), 30.0); + expect(_getOpacity(topRoute, tester), 0.0); + // Bottom route is not offset and is visible. + expect(find.text(bottomRoute), findsOneWidget); + expect(_getTranslationOffset( + bottomRoute, + tester, + SharedAxisTransitionType.horizontal, + ), 0.0); + expect(_getOpacity(bottomRoute, tester), 1.0); + + await tester.pump(const Duration(milliseconds: 1)); + expect(find.text(topRoute), findsNothing); + expect(find.text(bottomRoute), findsOneWidget); + }, + ); + + testWidgets( + 'SharedAxisTransition does not jump when interrupted', + (WidgetTester tester) async { + final GlobalKey navigator = GlobalKey(); + const String bottomRoute = '/'; + const String topRoute = '/a'; + + await tester.pumpWidget( + _TestWidget( + navigatorKey: navigator, + transitionType: SharedAxisTransitionType.horizontal, + ), + ); + expect(find.text(bottomRoute), findsOneWidget); + expect(find.text(topRoute), findsNothing); + + navigator.currentState.pushNamed(topRoute); + await tester.pump(); + + // Jump to halfway point of transition. + await tester.pump(const Duration(milliseconds: 150)); + // Bottom route is fully faded out. + expect(find.text(bottomRoute), findsOneWidget); + expect(_getOpacity(bottomRoute, tester), 0.0); + final double halfwayBottomOffset = + _getTranslationOffset( + bottomRoute, + tester, + SharedAxisTransitionType.horizontal, + ); + expect(halfwayBottomOffset, greaterThan(0.0)); + expect(halfwayBottomOffset, lessThan(30.0)); + + // Top route is fading/coming in. + expect(find.text(topRoute), findsOneWidget); + final double halfwayTopOffset = _getTranslationOffset( + topRoute, + tester, + SharedAxisTransitionType.horizontal, + ); + final double halfwayTopOpacity = _getOpacity(topRoute, tester); + expect(halfwayTopOffset, greaterThan(0.0)); + expect(halfwayTopOffset, lessThan(30.0)); + expect(halfwayTopOpacity, greaterThan(0.0)); + expect(halfwayTopOpacity, lessThan(1.0)); + + // Interrupt the transition with a pop. + navigator.currentState.pop(); + await tester.pump(); + + // Nothing should change. + expect(find.text(bottomRoute), findsOneWidget); + expect(_getTranslationOffset( + bottomRoute, + tester, + SharedAxisTransitionType.horizontal, + ), halfwayBottomOffset); + expect(_getOpacity(bottomRoute, tester), 0.0); + expect(find.text(topRoute), findsOneWidget); + expect(_getTranslationOffset( + topRoute, + tester, + SharedAxisTransitionType.horizontal, + ), halfwayTopOffset); + expect(_getOpacity(topRoute, tester), halfwayTopOpacity); + + // Jump to the 1/4 (75 ms) point of transition + await tester.pump(const Duration(milliseconds: 75)); + expect(find.text(bottomRoute), findsOneWidget); + expect(_getTranslationOffset( + bottomRoute, + tester, + SharedAxisTransitionType.horizontal, + ), greaterThan(0.0)); + expect(_getTranslationOffset( + bottomRoute, + tester, + SharedAxisTransitionType.horizontal, + ), lessThan(30.0)); + expect(_getTranslationOffset( + bottomRoute, + tester, + SharedAxisTransitionType.horizontal, + ), + lessThan(halfwayBottomOffset)); + expect(_getOpacity(bottomRoute, tester), greaterThan(0.0)); + expect(_getOpacity(bottomRoute, tester), lessThan(1.0)); + + // Jump to the end. + await tester.pump(const Duration(milliseconds: 75)); + expect(find.text(bottomRoute), findsOneWidget); + expect(_getTranslationOffset( + bottomRoute, + tester, + SharedAxisTransitionType.horizontal, + ), 0.0); + expect(_getOpacity(bottomRoute, tester), 1.0); + expect(find.text(topRoute), findsOneWidget); + expect(_getTranslationOffset( + topRoute, + tester, + SharedAxisTransitionType.horizontal, + ), 30.0); + expect(_getOpacity(topRoute, tester), 0.0); + + await tester.pump(const Duration(milliseconds: 1)); + expect(find.text(topRoute), findsNothing); + expect(find.text(bottomRoute), findsOneWidget); + }, + ); + + testWidgets( + 'State is not lost when transitioning', + (WidgetTester tester) async { + final GlobalKey navigator = GlobalKey(); + const String bottomRoute = '/'; + const String topRoute = '/a'; + + await tester.pumpWidget( + _TestWidget( + navigatorKey: navigator, + contentBuilder: (RouteSettings settings) { + return _StatefulTestWidget( + key: ValueKey(settings.name), + name: settings.name, + ); + }, + transitionType: SharedAxisTransitionType.horizontal, + ), + ); + + final _StatefulTestWidgetState bottomState = tester.state( + find.byKey(const ValueKey(bottomRoute)), + ); + expect(bottomState.widget.name, bottomRoute); + + navigator.currentState.pushNamed(topRoute); + await tester.pump(); + await tester.pump(); + + expect( + tester.state(find.byKey(const ValueKey(bottomRoute))), + bottomState, + ); + final _StatefulTestWidgetState topState = tester.state( + find.byKey(const ValueKey(topRoute)), + ); + expect(topState.widget.name, topRoute); + + await tester.pump(const Duration(milliseconds: 150)); + expect( + tester.state(find.byKey(const ValueKey(bottomRoute))), + bottomState, + ); + expect( + tester.state(find.byKey(const ValueKey(topRoute))), + topState, + ); + + await tester.pumpAndSettle(); + expect( + tester.state(find.byKey( + const ValueKey(bottomRoute), + skipOffstage: false, + )), + bottomState, + ); + expect( + tester.state(find.byKey(const ValueKey(topRoute))), + topState, + ); + + navigator.currentState.pop(); + await tester.pump(); + + expect( + tester.state(find.byKey(const ValueKey(bottomRoute))), + bottomState, + ); + expect( + tester.state(find.byKey(const ValueKey(topRoute))), + topState, + ); + + await tester.pump(const Duration(milliseconds: 150)); + expect( + tester.state(find.byKey(const ValueKey(bottomRoute))), + bottomState, + ); + expect( + tester.state(find.byKey(const ValueKey(topRoute))), + topState, + ); + + await tester.pumpAndSettle(); + expect( + tester.state(find.byKey(const ValueKey(bottomRoute))), + bottomState, + ); + expect(find.byKey(const ValueKey(topRoute)), findsNothing); + }, + ); + }); + + group('SharedAxisTransitionType.vertical', () { + testWidgets( + 'SharedAxisPageTransitionsBuilder builds a SharedAxisTransition', + (WidgetTester tester) async { + final AnimationController animation = AnimationController( + vsync: const TestVSync(), + ); + final AnimationController secondaryAnimation = AnimationController( + vsync: const TestVSync(), + ); + + await tester.pumpWidget( + const SharedAxisPageTransitionsBuilder( + transitionType: SharedAxisTransitionType.vertical, + ).buildTransitions( + null, + null, + animation, + secondaryAnimation, + const Placeholder(), + ), + ); + + expect(find.byType(SharedAxisTransition), findsOneWidget); + }, + ); + + testWidgets( + 'SharedAxisTransition runs forward', + (WidgetTester tester) async { + final GlobalKey navigator = GlobalKey(); + const String bottomRoute = '/'; + const String topRoute = '/a'; + + await tester.pumpWidget( + _TestWidget( + navigatorKey: navigator, + transitionType: SharedAxisTransitionType.vertical, + ), + ); + + expect(find.text(bottomRoute), findsOneWidget); + expect(_getTranslationOffset( + bottomRoute, + tester, + SharedAxisTransitionType.vertical, + ), 0.0); + expect(_getOpacity(bottomRoute, tester), 1.0); + expect(find.text(topRoute), findsNothing); + + navigator.currentState.pushNamed(topRoute); + await tester.pump(); + await tester.pump(); + + // Bottom route is not offset and fully visible. + expect(find.text(bottomRoute), findsOneWidget); + expect(_getTranslationOffset( + bottomRoute, + tester, + SharedAxisTransitionType.vertical, + ), 0.0); + expect(_getOpacity(bottomRoute, tester), 1.0); + // Top route is offset to the right by 30.0 pixels + // and not visible yet. + expect(find.text(topRoute), findsOneWidget); + expect(_getTranslationOffset( + topRoute, + tester, + SharedAxisTransitionType.vertical, + ), 30.0); + expect(_getOpacity(topRoute, tester), 0.0); + + // Jump 3/10ths of the way through the transition, bottom route + // should be be completely faded out while the top route + // is also completely faded out. + // Transition time: 300ms, 3/10 * 300ms = 90ms + await tester.pump(const Duration(milliseconds: 90)); + + // Bottom route is now invisible + expect(find.text(bottomRoute), findsOneWidget); + expect(_getOpacity(bottomRoute, tester), 0.0); + // Top route is still invisible, but moving towards the left. + expect(find.text(topRoute), findsOneWidget); + expect(_getOpacity(topRoute, tester), 0.0); + double topOffset = _getTranslationOffset( + topRoute, + tester, + SharedAxisTransitionType.vertical, + ); + expect(topOffset, lessThan(30.0)); + expect(topOffset, greaterThan(0.0)); + + // Jump to the middle of fading in + await tester.pump(const Duration(milliseconds: 90)); + // Bottom route is still invisible + expect(find.text(bottomRoute), findsOneWidget); + expect(_getOpacity(bottomRoute, tester), 0.0); + // Top route is fading in + expect(find.text(topRoute), findsOneWidget); + expect(_getOpacity(topRoute, tester), greaterThan(0)); + expect(_getOpacity(topRoute, tester), lessThan(1.0)); + topOffset = _getTranslationOffset( + topRoute, + tester, + SharedAxisTransitionType.vertical, + ); + expect(topOffset, greaterThan(0.0)); + expect(topOffset, lessThan(30.0)); + + // Jump to the end of the transition + await tester.pump(const Duration(milliseconds: 120)); + // Bottom route is not visible. + expect(find.text(bottomRoute), findsOneWidget); + + expect(_getTranslationOffset( + bottomRoute, + tester, + SharedAxisTransitionType.vertical, + ), 30.0); + expect(_getOpacity(bottomRoute, tester), 0.0); + // Top route has no offset and is visible. + expect(find.text(topRoute), findsOneWidget); + expect(_getTranslationOffset( + topRoute, + tester, + SharedAxisTransitionType.vertical, + ), 0.0); + expect(_getOpacity(topRoute, tester), 1.0); + + await tester.pump(const Duration(milliseconds: 1)); + expect(find.text(bottomRoute), findsNothing); + expect(find.text(topRoute), findsOneWidget); + }, + ); + + testWidgets( + 'SharedAxisTransition runs in reverse', + (WidgetTester tester) async { + final GlobalKey navigator = GlobalKey(); + const String bottomRoute = '/'; + const String topRoute = '/a'; + + await tester.pumpWidget( + _TestWidget( + navigatorKey: navigator, + transitionType: SharedAxisTransitionType.vertical, + ), + ); + + navigator.currentState.pushNamed('/a'); + await tester.pumpAndSettle(); + + expect(find.text(topRoute), findsOneWidget); + expect(_getTranslationOffset( + topRoute, + tester, + SharedAxisTransitionType.vertical, + ), 0.0); + expect(_getOpacity(topRoute, tester), 1.0); + expect(find.text(bottomRoute), findsNothing); + + navigator.currentState.pop(); + await tester.pump(); + + // Top route is is not offset and fully visible. + expect(find.text(topRoute), findsOneWidget); + expect(_getTranslationOffset( + topRoute, + tester, + SharedAxisTransitionType.vertical, + ), 0.0); + expect(_getOpacity(topRoute, tester), 1.0); + // Bottom route is offset to the right and is not visible yet. + expect(find.text(bottomRoute), findsOneWidget); + expect(_getTranslationOffset( + bottomRoute, + tester, + SharedAxisTransitionType.vertical, + ), 30.0); + expect(_getOpacity(bottomRoute, tester), 0.0); + + // Jump 3/10ths of the way through the transition, bottom route + // should be be completely faded out while the top route + // is also completely faded out. + // Transition time: 300ms, 3/10 * 300ms = 90ms + await tester.pump(const Duration(milliseconds: 90)); + + // Top route is now invisible + expect(find.text(topRoute), findsOneWidget); + expect(_getOpacity(topRoute, tester), 0.0); + // Bottom route is still invisible, but moving towards the left. + expect(find.text(bottomRoute), findsOneWidget); + expect(_getOpacity(bottomRoute, tester), + moreOrLessEquals(0, epsilon: 0.005)); + double bottomOffset = _getTranslationOffset( + bottomRoute, + tester, + SharedAxisTransitionType.vertical, + ); + expect(bottomOffset, greaterThan(0.0)); + expect(bottomOffset, lessThan(30.0)); + + // Jump to the middle of fading in + await tester.pump(const Duration(milliseconds: 90)); + // Top route is still invisible + expect(find.text(topRoute), findsOneWidget); + expect(_getOpacity(topRoute, tester), 0.0); + // Bottom route is fading in + expect(find.text(bottomRoute), findsOneWidget); + expect(_getOpacity(bottomRoute, tester), greaterThan(0)); + expect(_getOpacity(bottomRoute, tester), lessThan(1.0)); + bottomOffset = _getTranslationOffset( + bottomRoute, + tester, + SharedAxisTransitionType.vertical, + ); + expect(bottomOffset, greaterThan(0.0)); + expect(bottomOffset, lessThan(30.0)); + + // Jump to the end of the transition + await tester.pump(const Duration(milliseconds: 120)); + // Top route is not visible and is offset to the right. + expect(find.text(topRoute), findsOneWidget); + expect(_getTranslationOffset( + topRoute, + tester, + SharedAxisTransitionType.vertical, + ), 30.0); + expect(_getOpacity(topRoute, tester), 0.0); + // Bottom route is not offset and is visible. + expect(find.text(bottomRoute), findsOneWidget); + expect(_getTranslationOffset( + bottomRoute, + tester, + SharedAxisTransitionType.vertical, + ), 0.0); + expect(_getOpacity(bottomRoute, tester), 1.0); + + await tester.pump(const Duration(milliseconds: 1)); + expect(find.text(topRoute), findsNothing); + expect(find.text(bottomRoute), findsOneWidget); + }, + ); + + testWidgets( + 'SharedAxisTransition does not jump when interrupted', + (WidgetTester tester) async { + final GlobalKey navigator = GlobalKey(); + const String bottomRoute = '/'; + const String topRoute = '/a'; + + await tester.pumpWidget( + _TestWidget( + navigatorKey: navigator, + transitionType: SharedAxisTransitionType.vertical, + ), + ); + expect(find.text(bottomRoute), findsOneWidget); + expect(find.text(topRoute), findsNothing); + + navigator.currentState.pushNamed(topRoute); + await tester.pump(); + + // Jump to halfway point of transition. + await tester.pump(const Duration(milliseconds: 150)); + // Bottom route is fully faded out. + expect(find.text(bottomRoute), findsOneWidget); + expect(_getOpacity(bottomRoute, tester), 0.0); + final double halfwayBottomOffset = + _getTranslationOffset( + bottomRoute, + tester, + SharedAxisTransitionType.vertical, + ); + expect(halfwayBottomOffset, greaterThan(0.0)); + expect(halfwayBottomOffset, lessThan(30.0)); + + // Top route is fading/coming in. + expect(find.text(topRoute), findsOneWidget); + final double halfwayTopOffset = _getTranslationOffset( + topRoute, + tester, + SharedAxisTransitionType.vertical, + ); + final double halfwayTopOpacity = _getOpacity(topRoute, tester); + expect(halfwayTopOffset, greaterThan(0.0)); + expect(halfwayTopOffset, lessThan(30.0)); + expect(halfwayTopOpacity, greaterThan(0.0)); + expect(halfwayTopOpacity, lessThan(1.0)); + + // Interrupt the transition with a pop. + navigator.currentState.pop(); + await tester.pump(); + + // Nothing should change. + expect(find.text(bottomRoute), findsOneWidget); + expect(_getTranslationOffset( + bottomRoute, + tester, + SharedAxisTransitionType.vertical, + ), halfwayBottomOffset); + expect(_getOpacity(bottomRoute, tester), 0.0); + expect(find.text(topRoute), findsOneWidget); + expect(_getTranslationOffset( + topRoute, + tester, + SharedAxisTransitionType.vertical, + ), halfwayTopOffset); + expect(_getOpacity(topRoute, tester), halfwayTopOpacity); + + // Jump to the 1/4 (75 ms) point of transition + await tester.pump(const Duration(milliseconds: 75)); + expect(find.text(bottomRoute), findsOneWidget); + expect(_getTranslationOffset( + bottomRoute, + tester, + SharedAxisTransitionType.vertical, + ), greaterThan(0.0)); + expect(_getTranslationOffset( + bottomRoute, + tester, + SharedAxisTransitionType.vertical, + ), lessThan(30.0)); + expect(_getTranslationOffset( + bottomRoute, + tester, + SharedAxisTransitionType.vertical, + ), lessThan(halfwayBottomOffset)); + expect(_getOpacity(bottomRoute, tester), greaterThan(0.0)); + expect(_getOpacity(bottomRoute, tester), lessThan(1.0)); + + // Jump to the end. + await tester.pump(const Duration(milliseconds: 75)); + expect(find.text(bottomRoute), findsOneWidget); + expect(_getTranslationOffset( + bottomRoute, + tester, + SharedAxisTransitionType.vertical, + ), 0.0); + expect(_getOpacity(bottomRoute, tester), 1.0); + expect(find.text(topRoute), findsOneWidget); + expect(_getTranslationOffset( + topRoute, + tester, + SharedAxisTransitionType.vertical, + ), 30.0); + expect(_getOpacity(topRoute, tester), 0.0); + + await tester.pump(const Duration(milliseconds: 1)); + expect(find.text(topRoute), findsNothing); + expect(find.text(bottomRoute), findsOneWidget); + }, + ); + + testWidgets( + 'State is not lost when transitioning', + (WidgetTester tester) async { + final GlobalKey navigator = GlobalKey(); + const String bottomRoute = '/'; + const String topRoute = '/a'; + + await tester.pumpWidget( + _TestWidget( + navigatorKey: navigator, + contentBuilder: (RouteSettings settings) { + return _StatefulTestWidget( + key: ValueKey(settings.name), + name: settings.name, + ); + }, + transitionType: SharedAxisTransitionType.vertical, + ), + ); + + final _StatefulTestWidgetState bottomState = tester.state( + find.byKey(const ValueKey(bottomRoute)), + ); + expect(bottomState.widget.name, bottomRoute); + + navigator.currentState.pushNamed(topRoute); + await tester.pump(); + await tester.pump(); + + expect( + tester.state(find.byKey(const ValueKey(bottomRoute))), + bottomState, + ); + final _StatefulTestWidgetState topState = tester.state( + find.byKey(const ValueKey(topRoute)), + ); + expect(topState.widget.name, topRoute); + + await tester.pump(const Duration(milliseconds: 150)); + expect( + tester.state(find.byKey(const ValueKey(bottomRoute))), + bottomState, + ); + expect( + tester.state(find.byKey(const ValueKey(topRoute))), + topState, + ); + + await tester.pumpAndSettle(); + expect( + tester.state(find.byKey( + const ValueKey(bottomRoute), + skipOffstage: false, + )), + bottomState, + ); + expect( + tester.state(find.byKey(const ValueKey(topRoute))), + topState, + ); + + navigator.currentState.pop(); + await tester.pump(); + + expect( + tester.state(find.byKey(const ValueKey(bottomRoute))), + bottomState, + ); + expect( + tester.state(find.byKey(const ValueKey(topRoute))), + topState, + ); + + await tester.pump(const Duration(milliseconds: 150)); + expect( + tester.state(find.byKey(const ValueKey(bottomRoute))), + bottomState, + ); + expect( + tester.state(find.byKey(const ValueKey(topRoute))), + topState, + ); + + await tester.pumpAndSettle(); + expect( + tester.state(find.byKey(const ValueKey(bottomRoute))), + bottomState, + ); + expect(find.byKey(const ValueKey(topRoute)), findsNothing); + }, + ); + }); } double _getOpacity(String key, WidgetTester tester) { From 2a42a77620aa938f17dfa96aa8e4678b6f572803 Mon Sep 17 00:00:00 2001 From: Shi Hao Hong Date: Thu, 2 Jan 2020 12:36:38 -0800 Subject: [PATCH 30/37] run formatter --- .../lib/src/shared_axis_transition.dart | 24 +- .../test/shared_axis_transition_test.dart | 439 ++++++++++-------- 2 files changed, 263 insertions(+), 200 deletions(-) diff --git a/packages/animations/lib/src/shared_axis_transition.dart b/packages/animations/lib/src/shared_axis_transition.dart index 8cb7cefb580b..c78706f81441 100644 --- a/packages/animations/lib/src/shared_axis_transition.dart +++ b/packages/animations/lib/src/shared_axis_transition.dart @@ -13,6 +13,7 @@ import 'utils/curves.dart'; enum SharedAxisTransitionType { /// Creates a shared axis vertical translation page transition. vertical, + /// Creates a shared axis horizontal translation page transition. horizontal, } @@ -80,12 +81,11 @@ class SharedAxisPageTransitionsBuilder extends PageTransitionsBuilder { @override Widget buildTransitions( - PageRoute route, - BuildContext context, - Animation animation, - Animation secondaryAnimation, - Widget child - ) { + PageRoute route, + BuildContext context, + Animation animation, + Animation secondaryAnimation, + Widget child) { return SharedAxisTransition( animation: animation, secondaryAnimation: secondaryAnimation, @@ -178,8 +178,8 @@ class SharedAxisTransition extends StatefulWidget { @required this.secondaryAnimation, @required this.transitionType, this.child, - }) : assert(transitionType != null), - super(key: key); + }) : assert(transitionType != null), + super(key: key); /// The animation that drives the [child]'s entrance and exit. /// @@ -378,8 +378,8 @@ class _EnterTransition extends StatelessWidget { Widget build(BuildContext context) { final Animatable slideInTransition = Tween( begin: transitionType == SharedAxisTransitionType.horizontal - ? const Offset(30, 0.0) - : const Offset(0.0, 30.0), + ? const Offset(30, 0.0) + : const Offset(0.0, 30.0), end: Offset.zero, ).chain(CurveTween(curve: standardEasing)); @@ -413,8 +413,8 @@ class _ExitTransition extends StatelessWidget { final Animatable slideOutTransition = Tween( begin: Offset.zero, end: transitionType == SharedAxisTransitionType.horizontal - ? const Offset(30, 0.0) - : const Offset(0.0, 30.0), + ? const Offset(30, 0.0) + : const Offset(0.0, 30.0), ).chain(CurveTween(curve: standardEasing)); return FadeTransition( diff --git a/packages/animations/test/shared_axis_transition_test.dart b/packages/animations/test/shared_axis_transition_test.dart index c62108be0256..19447c125208 100644 --- a/packages/animations/test/shared_axis_transition_test.dart +++ b/packages/animations/test/shared_axis_transition_test.dart @@ -53,11 +53,13 @@ void main() { ); expect(find.text(bottomRoute), findsOneWidget); - expect(_getTranslationOffset( - bottomRoute, - tester, - SharedAxisTransitionType.horizontal, - ), 0.0); + expect( + _getTranslationOffset( + bottomRoute, + tester, + SharedAxisTransitionType.horizontal, + ), + 0.0); expect(_getOpacity(bottomRoute, tester), 1.0); expect(find.text(topRoute), findsNothing); @@ -67,20 +69,24 @@ void main() { // Bottom route is not offset and fully visible. expect(find.text(bottomRoute), findsOneWidget); - expect(_getTranslationOffset( - bottomRoute, - tester, - SharedAxisTransitionType.horizontal, - ), 0.0); + expect( + _getTranslationOffset( + bottomRoute, + tester, + SharedAxisTransitionType.horizontal, + ), + 0.0); expect(_getOpacity(bottomRoute, tester), 1.0); // Top route is offset to the right by 30.0 pixels // and not visible yet. expect(find.text(topRoute), findsOneWidget); - expect(_getTranslationOffset( - topRoute, - tester, - SharedAxisTransitionType.horizontal, - ), 30.0); + expect( + _getTranslationOffset( + topRoute, + tester, + SharedAxisTransitionType.horizontal, + ), + 30.0); expect(_getOpacity(topRoute, tester), 0.0); // Jump 3/10ths of the way through the transition, bottom route @@ -125,19 +131,23 @@ void main() { // Bottom route is not visible. expect(find.text(bottomRoute), findsOneWidget); - expect(_getTranslationOffset( - bottomRoute, - tester, - SharedAxisTransitionType.horizontal, - ), 30.0); + expect( + _getTranslationOffset( + bottomRoute, + tester, + SharedAxisTransitionType.horizontal, + ), + 30.0); expect(_getOpacity(bottomRoute, tester), 0.0); // Top route has no offset and is visible. expect(find.text(topRoute), findsOneWidget); - expect(_getTranslationOffset( - topRoute, - tester, - SharedAxisTransitionType.horizontal, - ), 0.0); + expect( + _getTranslationOffset( + topRoute, + tester, + SharedAxisTransitionType.horizontal, + ), + 0.0); expect(_getOpacity(topRoute, tester), 1.0); await tester.pump(const Duration(milliseconds: 1)); @@ -164,11 +174,13 @@ void main() { await tester.pumpAndSettle(); expect(find.text(topRoute), findsOneWidget); - expect(_getTranslationOffset( - topRoute, - tester, - SharedAxisTransitionType.horizontal, - ), 0.0); + expect( + _getTranslationOffset( + topRoute, + tester, + SharedAxisTransitionType.horizontal, + ), + 0.0); expect(_getOpacity(topRoute, tester), 1.0); expect(find.text(bottomRoute), findsNothing); @@ -177,19 +189,23 @@ void main() { // Top route is is not offset and fully visible. expect(find.text(topRoute), findsOneWidget); - expect(_getTranslationOffset( - topRoute, - tester, - SharedAxisTransitionType.horizontal, - ), 0.0); + expect( + _getTranslationOffset( + topRoute, + tester, + SharedAxisTransitionType.horizontal, + ), + 0.0); expect(_getOpacity(topRoute, tester), 1.0); // Bottom route is offset to the right and is not visible yet. expect(find.text(bottomRoute), findsOneWidget); - expect(_getTranslationOffset( - bottomRoute, - tester, - SharedAxisTransitionType.horizontal, - ), 30.0); + expect( + _getTranslationOffset( + bottomRoute, + tester, + SharedAxisTransitionType.horizontal, + ), + 30.0); expect(_getOpacity(bottomRoute, tester), 0.0); // Jump 3/10ths of the way through the transition, bottom route @@ -234,19 +250,23 @@ void main() { await tester.pump(const Duration(milliseconds: 120)); // Top route is not visible and is offset to the right. expect(find.text(topRoute), findsOneWidget); - expect(_getTranslationOffset( - topRoute, - tester, - SharedAxisTransitionType.horizontal, - ), 30.0); + expect( + _getTranslationOffset( + topRoute, + tester, + SharedAxisTransitionType.horizontal, + ), + 30.0); expect(_getOpacity(topRoute, tester), 0.0); // Bottom route is not offset and is visible. expect(find.text(bottomRoute), findsOneWidget); - expect(_getTranslationOffset( - bottomRoute, - tester, - SharedAxisTransitionType.horizontal, - ), 0.0); + expect( + _getTranslationOffset( + bottomRoute, + tester, + SharedAxisTransitionType.horizontal, + ), + 0.0); expect(_getOpacity(bottomRoute, tester), 1.0); await tester.pump(const Duration(milliseconds: 1)); @@ -279,8 +299,7 @@ void main() { // Bottom route is fully faded out. expect(find.text(bottomRoute), findsOneWidget); expect(_getOpacity(bottomRoute, tester), 0.0); - final double halfwayBottomOffset = - _getTranslationOffset( + final double halfwayBottomOffset = _getTranslationOffset( bottomRoute, tester, SharedAxisTransitionType.horizontal, @@ -307,38 +326,47 @@ void main() { // Nothing should change. expect(find.text(bottomRoute), findsOneWidget); - expect(_getTranslationOffset( - bottomRoute, - tester, - SharedAxisTransitionType.horizontal, - ), halfwayBottomOffset); + expect( + _getTranslationOffset( + bottomRoute, + tester, + SharedAxisTransitionType.horizontal, + ), + halfwayBottomOffset); expect(_getOpacity(bottomRoute, tester), 0.0); expect(find.text(topRoute), findsOneWidget); - expect(_getTranslationOffset( - topRoute, - tester, - SharedAxisTransitionType.horizontal, - ), halfwayTopOffset); + expect( + _getTranslationOffset( + topRoute, + tester, + SharedAxisTransitionType.horizontal, + ), + halfwayTopOffset); expect(_getOpacity(topRoute, tester), halfwayTopOpacity); // Jump to the 1/4 (75 ms) point of transition await tester.pump(const Duration(milliseconds: 75)); expect(find.text(bottomRoute), findsOneWidget); - expect(_getTranslationOffset( - bottomRoute, - tester, - SharedAxisTransitionType.horizontal, - ), greaterThan(0.0)); - expect(_getTranslationOffset( - bottomRoute, - tester, - SharedAxisTransitionType.horizontal, - ), lessThan(30.0)); - expect(_getTranslationOffset( - bottomRoute, - tester, - SharedAxisTransitionType.horizontal, - ), + expect( + _getTranslationOffset( + bottomRoute, + tester, + SharedAxisTransitionType.horizontal, + ), + greaterThan(0.0)); + expect( + _getTranslationOffset( + bottomRoute, + tester, + SharedAxisTransitionType.horizontal, + ), + lessThan(30.0)); + expect( + _getTranslationOffset( + bottomRoute, + tester, + SharedAxisTransitionType.horizontal, + ), lessThan(halfwayBottomOffset)); expect(_getOpacity(bottomRoute, tester), greaterThan(0.0)); expect(_getOpacity(bottomRoute, tester), lessThan(1.0)); @@ -346,18 +374,22 @@ void main() { // Jump to the end. await tester.pump(const Duration(milliseconds: 75)); expect(find.text(bottomRoute), findsOneWidget); - expect(_getTranslationOffset( - bottomRoute, - tester, - SharedAxisTransitionType.horizontal, - ), 0.0); + expect( + _getTranslationOffset( + bottomRoute, + tester, + SharedAxisTransitionType.horizontal, + ), + 0.0); expect(_getOpacity(bottomRoute, tester), 1.0); expect(find.text(topRoute), findsOneWidget); - expect(_getTranslationOffset( - topRoute, - tester, - SharedAxisTransitionType.horizontal, - ), 30.0); + expect( + _getTranslationOffset( + topRoute, + tester, + SharedAxisTransitionType.horizontal, + ), + 30.0); expect(_getOpacity(topRoute, tester), 0.0); await tester.pump(const Duration(milliseconds: 1)); @@ -501,11 +533,13 @@ void main() { ); expect(find.text(bottomRoute), findsOneWidget); - expect(_getTranslationOffset( - bottomRoute, - tester, - SharedAxisTransitionType.vertical, - ), 0.0); + expect( + _getTranslationOffset( + bottomRoute, + tester, + SharedAxisTransitionType.vertical, + ), + 0.0); expect(_getOpacity(bottomRoute, tester), 1.0); expect(find.text(topRoute), findsNothing); @@ -515,20 +549,24 @@ void main() { // Bottom route is not offset and fully visible. expect(find.text(bottomRoute), findsOneWidget); - expect(_getTranslationOffset( - bottomRoute, - tester, - SharedAxisTransitionType.vertical, - ), 0.0); + expect( + _getTranslationOffset( + bottomRoute, + tester, + SharedAxisTransitionType.vertical, + ), + 0.0); expect(_getOpacity(bottomRoute, tester), 1.0); // Top route is offset to the right by 30.0 pixels // and not visible yet. expect(find.text(topRoute), findsOneWidget); - expect(_getTranslationOffset( - topRoute, - tester, - SharedAxisTransitionType.vertical, - ), 30.0); + expect( + _getTranslationOffset( + topRoute, + tester, + SharedAxisTransitionType.vertical, + ), + 30.0); expect(_getOpacity(topRoute, tester), 0.0); // Jump 3/10ths of the way through the transition, bottom route @@ -573,19 +611,23 @@ void main() { // Bottom route is not visible. expect(find.text(bottomRoute), findsOneWidget); - expect(_getTranslationOffset( - bottomRoute, - tester, - SharedAxisTransitionType.vertical, - ), 30.0); + expect( + _getTranslationOffset( + bottomRoute, + tester, + SharedAxisTransitionType.vertical, + ), + 30.0); expect(_getOpacity(bottomRoute, tester), 0.0); // Top route has no offset and is visible. expect(find.text(topRoute), findsOneWidget); - expect(_getTranslationOffset( - topRoute, - tester, - SharedAxisTransitionType.vertical, - ), 0.0); + expect( + _getTranslationOffset( + topRoute, + tester, + SharedAxisTransitionType.vertical, + ), + 0.0); expect(_getOpacity(topRoute, tester), 1.0); await tester.pump(const Duration(milliseconds: 1)); @@ -612,11 +654,13 @@ void main() { await tester.pumpAndSettle(); expect(find.text(topRoute), findsOneWidget); - expect(_getTranslationOffset( - topRoute, - tester, - SharedAxisTransitionType.vertical, - ), 0.0); + expect( + _getTranslationOffset( + topRoute, + tester, + SharedAxisTransitionType.vertical, + ), + 0.0); expect(_getOpacity(topRoute, tester), 1.0); expect(find.text(bottomRoute), findsNothing); @@ -625,19 +669,23 @@ void main() { // Top route is is not offset and fully visible. expect(find.text(topRoute), findsOneWidget); - expect(_getTranslationOffset( - topRoute, - tester, - SharedAxisTransitionType.vertical, - ), 0.0); + expect( + _getTranslationOffset( + topRoute, + tester, + SharedAxisTransitionType.vertical, + ), + 0.0); expect(_getOpacity(topRoute, tester), 1.0); // Bottom route is offset to the right and is not visible yet. expect(find.text(bottomRoute), findsOneWidget); - expect(_getTranslationOffset( - bottomRoute, - tester, - SharedAxisTransitionType.vertical, - ), 30.0); + expect( + _getTranslationOffset( + bottomRoute, + tester, + SharedAxisTransitionType.vertical, + ), + 30.0); expect(_getOpacity(bottomRoute, tester), 0.0); // Jump 3/10ths of the way through the transition, bottom route @@ -682,19 +730,23 @@ void main() { await tester.pump(const Duration(milliseconds: 120)); // Top route is not visible and is offset to the right. expect(find.text(topRoute), findsOneWidget); - expect(_getTranslationOffset( - topRoute, - tester, - SharedAxisTransitionType.vertical, - ), 30.0); + expect( + _getTranslationOffset( + topRoute, + tester, + SharedAxisTransitionType.vertical, + ), + 30.0); expect(_getOpacity(topRoute, tester), 0.0); // Bottom route is not offset and is visible. expect(find.text(bottomRoute), findsOneWidget); - expect(_getTranslationOffset( - bottomRoute, - tester, - SharedAxisTransitionType.vertical, - ), 0.0); + expect( + _getTranslationOffset( + bottomRoute, + tester, + SharedAxisTransitionType.vertical, + ), + 0.0); expect(_getOpacity(bottomRoute, tester), 1.0); await tester.pump(const Duration(milliseconds: 1)); @@ -727,8 +779,7 @@ void main() { // Bottom route is fully faded out. expect(find.text(bottomRoute), findsOneWidget); expect(_getOpacity(bottomRoute, tester), 0.0); - final double halfwayBottomOffset = - _getTranslationOffset( + final double halfwayBottomOffset = _getTranslationOffset( bottomRoute, tester, SharedAxisTransitionType.vertical, @@ -755,56 +806,70 @@ void main() { // Nothing should change. expect(find.text(bottomRoute), findsOneWidget); - expect(_getTranslationOffset( - bottomRoute, - tester, - SharedAxisTransitionType.vertical, - ), halfwayBottomOffset); + expect( + _getTranslationOffset( + bottomRoute, + tester, + SharedAxisTransitionType.vertical, + ), + halfwayBottomOffset); expect(_getOpacity(bottomRoute, tester), 0.0); expect(find.text(topRoute), findsOneWidget); - expect(_getTranslationOffset( - topRoute, - tester, - SharedAxisTransitionType.vertical, - ), halfwayTopOffset); + expect( + _getTranslationOffset( + topRoute, + tester, + SharedAxisTransitionType.vertical, + ), + halfwayTopOffset); expect(_getOpacity(topRoute, tester), halfwayTopOpacity); // Jump to the 1/4 (75 ms) point of transition await tester.pump(const Duration(milliseconds: 75)); expect(find.text(bottomRoute), findsOneWidget); - expect(_getTranslationOffset( - bottomRoute, - tester, - SharedAxisTransitionType.vertical, - ), greaterThan(0.0)); - expect(_getTranslationOffset( - bottomRoute, - tester, - SharedAxisTransitionType.vertical, - ), lessThan(30.0)); - expect(_getTranslationOffset( - bottomRoute, - tester, - SharedAxisTransitionType.vertical, - ), lessThan(halfwayBottomOffset)); + expect( + _getTranslationOffset( + bottomRoute, + tester, + SharedAxisTransitionType.vertical, + ), + greaterThan(0.0)); + expect( + _getTranslationOffset( + bottomRoute, + tester, + SharedAxisTransitionType.vertical, + ), + lessThan(30.0)); + expect( + _getTranslationOffset( + bottomRoute, + tester, + SharedAxisTransitionType.vertical, + ), + lessThan(halfwayBottomOffset)); expect(_getOpacity(bottomRoute, tester), greaterThan(0.0)); expect(_getOpacity(bottomRoute, tester), lessThan(1.0)); // Jump to the end. await tester.pump(const Duration(milliseconds: 75)); expect(find.text(bottomRoute), findsOneWidget); - expect(_getTranslationOffset( - bottomRoute, - tester, - SharedAxisTransitionType.vertical, - ), 0.0); + expect( + _getTranslationOffset( + bottomRoute, + tester, + SharedAxisTransitionType.vertical, + ), + 0.0); expect(_getOpacity(bottomRoute, tester), 1.0); expect(find.text(topRoute), findsOneWidget); - expect(_getTranslationOffset( - topRoute, - tester, - SharedAxisTransitionType.vertical, - ), 30.0); + expect( + _getTranslationOffset( + topRoute, + tester, + SharedAxisTransitionType.vertical, + ), + 30.0); expect(_getOpacity(topRoute, tester), 0.0); await tester.pump(const Duration(milliseconds: 1)); @@ -930,22 +995,20 @@ double _getTranslationOffset( switch (transitionType) { case SharedAxisTransitionType.horizontal: - return tester.widgetList(finder).fold( - 0.0, - (double a, Widget widget) { - final Transform transition = widget; - final Vector3 translation = transition.transform.getTranslation(); - return a + translation.x; - }); + return tester.widgetList(finder).fold(0.0, + (double a, Widget widget) { + final Transform transition = widget; + final Vector3 translation = transition.transform.getTranslation(); + return a + translation.x; + }); break; case SharedAxisTransitionType.vertical: - return tester.widgetList(finder).fold( - 0.0, - (double a, Widget widget) { - final Transform transition = widget; - final Vector3 translation = transition.transform.getTranslation(); - return a + translation.y; - }); + return tester.widgetList(finder).fold(0.0, + (double a, Widget widget) { + final Transform transition = widget; + final Vector3 translation = transition.transform.getTranslation(); + return a + translation.y; + }); break; } return null; // unreachable From 7663c536d5311d6b146aa1014b3acfadd698caf3 Mon Sep 17 00:00:00 2001 From: Shi Hao Hong Date: Thu, 2 Jan 2020 12:41:47 -0800 Subject: [PATCH 31/37] Fix up documentation --- .../animations/lib/src/shared_axis_transition.dart | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/packages/animations/lib/src/shared_axis_transition.dart b/packages/animations/lib/src/shared_axis_transition.dart index c78706f81441..36438f39478f 100644 --- a/packages/animations/lib/src/shared_axis_transition.dart +++ b/packages/animations/lib/src/shared_axis_transition.dart @@ -19,7 +19,7 @@ enum SharedAxisTransitionType { } /// Used by [PageTransitionsTheme] to define a page route transition animation -/// in which outgoing and incoming elements share a horizontal fade transition. +/// in which outgoing and incoming elements share a fade transition. /// /// The shared axis pattern provides the transition animation between UI elements /// that have a spatial or navigational relationship. For example, @@ -35,8 +35,12 @@ enum SharedAxisTransitionType { /// theme: ThemeData( /// pageTransitionsTheme: PageTransitionsTheme( /// builders: { -/// TargetPlatform.android: SharedAxisPageTransitionsBuilder(), -/// TargetPlatform.iOS: SharedAxisPageTransitionsBuilder(), +/// TargetPlatform.android: SharedAxisPageTransitionsBuilder( +/// transitionType: SharedAxisTransitionType.horizontal, +/// ), +/// TargetPlatform.iOS: SharedAxisPageTransitionsBuilder( +/// transitionType: SharedAxisTransitionType.horizontal, +/// ), /// }, /// ), /// ), @@ -95,7 +99,7 @@ class SharedAxisPageTransitionsBuilder extends PageTransitionsBuilder { } } -/// Defines a transition in which outgoing and incoming elements share a horizontal +/// Defines a transition in which outgoing and incoming elements share a fade /// transition. /// /// The shared axis pattern provides the transition animation between UI elements @@ -131,6 +135,7 @@ class SharedAxisPageTransitionsBuilder extends PageTransitionsBuilder { /// return SharedAxisTransition( /// animation: primaryAnimation, /// secondaryAnimation: secondaryAnimation, +/// transitionType: SharedAxisTransitionType.horizontal, /// child: child, /// ); /// }, From 175895e0a3abda58987aefdc4af0a70ac7777b9d Mon Sep 17 00:00:00 2001 From: Shi Hao Hong Date: Thu, 2 Jan 2020 12:48:06 -0800 Subject: [PATCH 32/37] update pubspec.yaml --- packages/animations/pubspec.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/animations/pubspec.yaml b/packages/animations/pubspec.yaml index 6bfc61343745..4462aadf93db 100644 --- a/packages/animations/pubspec.yaml +++ b/packages/animations/pubspec.yaml @@ -9,6 +9,7 @@ environment: dependencies: flutter: sdk: flutter + vector_math: 2.0.8 dev_dependencies: flutter_test: From f86c7600fb71db60018c5bbd5990a7d7b4558ade Mon Sep 17 00:00:00 2001 From: Shi Hao Hong Date: Thu, 2 Jan 2020 12:52:59 -0800 Subject: [PATCH 33/37] loosen vector_math version constraint --- packages/animations/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/animations/pubspec.yaml b/packages/animations/pubspec.yaml index 4462aadf93db..26fe35d391b1 100644 --- a/packages/animations/pubspec.yaml +++ b/packages/animations/pubspec.yaml @@ -9,7 +9,7 @@ environment: dependencies: flutter: sdk: flutter - vector_math: 2.0.8 + vector_math: ^2.0.8 dev_dependencies: flutter_test: From a91571c69a491ee577daaa6e32a79e25bf279970 Mon Sep 17 00:00:00 2001 From: Shi Hao Hong Date: Mon, 13 Jan 2020 14:52:29 -0800 Subject: [PATCH 34/37] Roll Shared Z Axis Transitions into the same builder as rest of SharedAxisTransition types --- packages/animations/lib/animations.dart | 1 - .../lib/src/shared_axis_transition.dart | 145 ++++-- .../lib/src/shared_z_axis_transition.dart | 414 ---------------- .../test/shared_axis_transition_test.dart | 408 +++++++++++++++ .../test/shared_z_axis_transition_test.dart | 467 ------------------ 5 files changed, 516 insertions(+), 919 deletions(-) delete mode 100644 packages/animations/lib/src/shared_z_axis_transition.dart delete mode 100644 packages/animations/test/shared_z_axis_transition_test.dart diff --git a/packages/animations/lib/animations.dart b/packages/animations/lib/animations.dart index 5fb7462237d6..3b48f3e5cde3 100644 --- a/packages/animations/lib/animations.dart +++ b/packages/animations/lib/animations.dart @@ -6,4 +6,3 @@ export 'src/fade_through_transition.dart'; export 'src/open_container.dart'; export 'src/page_transition_switcher.dart'; export 'src/shared_axis_transition.dart'; -export 'src/shared_z_axis_transition.dart'; diff --git a/packages/animations/lib/src/shared_axis_transition.dart b/packages/animations/lib/src/shared_axis_transition.dart index 36438f39478f..3e22f575762a 100644 --- a/packages/animations/lib/src/shared_axis_transition.dart +++ b/packages/animations/lib/src/shared_axis_transition.dart @@ -16,6 +16,9 @@ enum SharedAxisTransitionType { /// Creates a shared axis horizontal translation page transition. horizontal, + + /// Creates a shared axis scaled translation page transition. + scaled, } /// Used by [PageTransitionsTheme] to define a page route transition animation @@ -375,26 +378,57 @@ class _EnterTransition extends StatelessWidget { final SharedAxisTransitionType transitionType; final Widget child; - static Animatable fadeInTransition = - CurveTween(curve: decelerateEasing) - .chain(CurveTween(curve: const Interval(0.3, 1.0))); + static Animatable fadeInTransition = CurveTween( + curve: decelerateEasing, + ).chain(CurveTween(curve: const Interval(0.3, 1.0))); + + static Animatable scaleInTransition = Tween( + begin: 0.80, + end: 1.00, + ).chain(CurveTween(curve: standardEasing)); @override Widget build(BuildContext context) { - final Animatable slideInTransition = Tween( - begin: transitionType == SharedAxisTransitionType.horizontal - ? const Offset(30, 0.0) - : const Offset(0.0, 30.0), - end: Offset.zero, - ).chain(CurveTween(curve: standardEasing)); - - return FadeTransition( - opacity: fadeInTransition.animate(animation), - child: Transform.translate( - offset: slideInTransition.evaluate(animation), - child: child, - ), - ); + switch (transitionType) { + case SharedAxisTransitionType.horizontal: + final Animatable slideInTransition = Tween( + begin: const Offset(30, 0.0), + end: Offset.zero, + ).chain(CurveTween(curve: standardEasing)); + + return FadeTransition( + opacity: fadeInTransition.animate(animation), + child: Transform.translate( + offset: slideInTransition.evaluate(animation), + child: child, + ), + ); + break; + case SharedAxisTransitionType.vertical: + final Animatable slideInTransition = Tween( + begin: const Offset(0.0, 30), + end: Offset.zero, + ).chain(CurveTween(curve: standardEasing)); + + return FadeTransition( + opacity: fadeInTransition.animate(animation), + child: Transform.translate( + offset: slideInTransition.evaluate(animation), + child: child, + ), + ); + break; + case SharedAxisTransitionType.scaled: + return FadeTransition( + opacity: fadeInTransition.animate(animation), + child: ScaleTransition( + scale: scaleInTransition.animate(animation), + child: child, + ), + ); + break; + } + return null; // unreachable } } @@ -409,28 +443,65 @@ class _ExitTransition extends StatelessWidget { final SharedAxisTransitionType transitionType; final Widget child; - static Animatable fadeOutTransition = - FlippedCurveTween(curve: accelerateEasing) - .chain(CurveTween(curve: const Interval(0.0, 0.3))); + static Animatable fadeOutTransition = FlippedCurveTween( + curve: accelerateEasing, + ).chain(CurveTween(curve: const Interval(0.0, 0.3))); + + static Animatable scaleOutTransition = Tween( + begin: 1.00, + end: 1.10, + ).chain(CurveTween(curve: standardEasing)); @override Widget build(BuildContext context) { - final Animatable slideOutTransition = Tween( - begin: Offset.zero, - end: transitionType == SharedAxisTransitionType.horizontal - ? const Offset(30, 0.0) - : const Offset(0.0, 30.0), - ).chain(CurveTween(curve: standardEasing)); - - return FadeTransition( - opacity: fadeOutTransition.animate(animation), - child: Container( - color: Theme.of(context).canvasColor, - child: Transform.translate( - offset: slideOutTransition.evaluate(animation), - child: child, - ), - ), - ); + switch (transitionType) { + case SharedAxisTransitionType.horizontal: + final Animatable slideOutTransition = Tween( + begin: Offset.zero, + end: const Offset(30, 0.0), + ).chain(CurveTween(curve: standardEasing)); + + return FadeTransition( + opacity: fadeOutTransition.animate(animation), + child: Container( + color: Theme.of(context).canvasColor, + child: Transform.translate( + offset: slideOutTransition.evaluate(animation), + child: child, + ), + ), + ); + break; + case SharedAxisTransitionType.vertical: + final Animatable slideOutTransition = Tween( + begin: Offset.zero, + end: const Offset(0.0, 30), + ).chain(CurveTween(curve: standardEasing)); + + return FadeTransition( + opacity: fadeOutTransition.animate(animation), + child: Container( + color: Theme.of(context).canvasColor, + child: Transform.translate( + offset: slideOutTransition.evaluate(animation), + child: child, + ), + ), + ); + break; + case SharedAxisTransitionType.scaled: + return FadeTransition( + opacity: fadeOutTransition.animate(animation), + child: Container( + color: Theme.of(context).canvasColor, + child: ScaleTransition( + scale: scaleOutTransition.animate(animation), + child: child, + ), + ), + ); + break; + } + return null; // unreachable } } diff --git a/packages/animations/lib/src/shared_z_axis_transition.dart b/packages/animations/lib/src/shared_z_axis_transition.dart deleted file mode 100644 index a678ec6ea4a1..000000000000 --- a/packages/animations/lib/src/shared_z_axis_transition.dart +++ /dev/null @@ -1,414 +0,0 @@ -// Copyright 2019 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import 'package:flutter/animation.dart'; -import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter/widgets.dart'; - -import 'utils/curves.dart'; - -/// An in-place fade and scale transition used by [PageTransitionsTheme] -/// to create a page transition with [SharedZAxisTransition]. -/// -/// The shared axis pattern provides the transition animation -/// between UI elements that have a spatial or navigational -/// relationship. For example, transitioning from one page of a -/// sign-up page to the next one. -/// -/// In this particular transition, the outgoing widget expands and -/// fades away while the incoming widget shrinks and fades into place. -/// -/// The following example shows how the SharedZAxisPageTransitionsBuilder can -/// be used in a [PageTransitionsTheme] to change the default transitions -/// of [MaterialPageRoute]s. -/// -/// ```dart -/// MaterialApp( -/// theme: ThemeData( -/// pageTransitionsTheme: PageTransitionsTheme( -/// builders: { -/// TargetPlatform.android: SharedZAxisPageTransitionsBuilder(), -/// TargetPlatform.iOS: SharedZAxisPageTransitionsBuilder(), -/// }, -/// ), -/// ), -/// routes: { -/// '/': (BuildContext context) { -/// return Container( -/// color: Colors.red, -/// child: Center( -/// child: MaterialButton( -/// child: Text('Push route'), -/// onPressed: () { -/// Navigator.of(context).pushNamed('/a'); -/// }, -/// ), -/// ), -/// ); -/// }, -/// '/a': (BuildContext context) { -/// return Container( -/// color: Colors.blue, -/// child: Center( -/// child: RaisedButton( -/// child: Text('Pop route'), -/// onPressed: () { -/// Navigator.of(context).pop(); -/// }, -/// ), -/// ), -/// ); -/// }, -/// }, -/// ); -/// ``` -class SharedZAxisPageTransitionsBuilder extends PageTransitionsBuilder { - /// Construct a [SharedZAxisPageTransitionsBuilder]. - const SharedZAxisPageTransitionsBuilder(); - - @override - Widget buildTransitions( - PageRoute route, - BuildContext context, - Animation animation, - Animation secondaryAnimation, - Widget child, - ) { - return SharedZAxisTransition( - animation: animation, - secondaryAnimation: secondaryAnimation, - child: child, - ); - } -} - -/// Defines a in-place transition in which the outgoing widget expands -/// and fades away while the incoming widget shrinks and fades into place. -/// -/// The shared axis pattern provides the transition animation between UI elements -/// that have a spatial or navigational relationship. For example, -/// transitioning from one page of a sign-up page to the next one. -/// -/// Consider using [SharedZAxisTransition] within a -/// [PageTransitionsTheme] if you want to apply this kind of transition to -/// all [MaterialPageRoute] transitions within a Navigator (see -/// [SharedZAxisPageTransitionsBuilder] for some example code). -/// -/// This transition can also be used directly in a -/// [PageTransitionSwitcher.transitionBuilder] to transition -/// from one widget to another as seen in the following example: -/// ```dart -/// int _selectedIndex = 0; -/// -/// final List _colors = [Colors.white, Colors.red, Colors.yellow]; -/// -/// @override -/// Widget build(BuildContext context) { -/// return Scaffold( -/// appBar: AppBar( -/// title: const Text('Page Transition Example'), -/// ), -/// body: PageTransitionSwitcher( -/// // reverse: true, // uncomment to see transition in reverse -/// transitionBuilder: ( -/// Widget child, -/// Animation primaryAnimation, -/// Animation secondaryAnimation, -/// ) { -/// return SharedZAxisTransition( -/// animation: primaryAnimation, -/// secondaryAnimation: secondaryAnimation, -/// child: child, -/// ); -/// }, -/// child: Container( -/// key: ValueKey(_selectedIndex), -/// color: _colors[_selectedIndex], -/// child: Center( -/// child: FlutterLogo(size: 300), -/// ) -/// ), -/// ), -/// bottomNavigationBar: BottomNavigationBar( -/// items: const [ -/// BottomNavigationBarItem( -/// icon: Icon(Icons.home), -/// title: Text('White'), -/// ), -/// BottomNavigationBarItem( -/// icon: Icon(Icons.business), -/// title: Text('Red'), -/// ), -/// BottomNavigationBarItem( -/// icon: Icon(Icons.school), -/// title: Text('Yellow'), -/// ), -/// ], -/// currentIndex: _selectedIndex, -/// onTap: (int index) { -/// setState(() { -/// _selectedIndex = index; -/// }); -/// }, -/// ), -/// ); -/// } -/// ``` -class SharedZAxisTransition extends StatefulWidget { - /// Creates a [SharedZAxisTransition]. - /// - /// The [animation] and [secondaryAnimation] arguments are required and must - /// not be null. - const SharedZAxisTransition({ - Key key, - @required this.animation, - @required this.secondaryAnimation, - this.child, - }) : assert(animation != null), - assert(secondaryAnimation != null), - super(key: key); - - /// The animation that drives the [child]'s entrance and exit. - /// - /// See also: - /// - /// * [TransitionRoute.animate], which is the value given to this property - /// when it is used as a page transition. - final Animation animation; - - /// The animation that transitions [child] when new content is pushed on top - /// of it. - /// - /// See also: - /// - /// * [TransitionRoute.secondaryAnimation], which is the value given to this - /// property when the it is used as a page transition. - final Animation secondaryAnimation; - - /// The widget below this widget in the tree. - /// - /// This widget will transition in and out as driven by [animation] and - /// [secondaryAnimation]. - final Widget child; - - @override - _SharedZAxisTransitionState createState() => _SharedZAxisTransitionState(); -} - -class _SharedZAxisTransitionState extends State { - AnimationStatus _effectiveAnimationStatus; - AnimationStatus _effectiveSecondaryAnimationStatus; - - @override - void initState() { - super.initState(); - _effectiveAnimationStatus = widget.animation.status; - _effectiveSecondaryAnimationStatus = widget.secondaryAnimation.status; - widget.animation.addStatusListener(_animationListener); - widget.secondaryAnimation.addStatusListener(_secondaryAnimationListener); - } - - void _animationListener(AnimationStatus animationStatus) { - _effectiveAnimationStatus = _calculateEffectiveAnimationStatus( - lastEffective: _effectiveAnimationStatus, - current: animationStatus, - ); - } - - void _secondaryAnimationListener(AnimationStatus animationStatus) { - _effectiveSecondaryAnimationStatus = _calculateEffectiveAnimationStatus( - lastEffective: _effectiveSecondaryAnimationStatus, - current: animationStatus, - ); - } - - // When a transition is interrupted midway we just want to play the ongoing - // animation in reverse. Switching to the actual reverse transition would - // yield a disjoint experience since the forward and reverse transitions are - // very different. - AnimationStatus _calculateEffectiveAnimationStatus({ - @required AnimationStatus lastEffective, - @required AnimationStatus current, - }) { - assert(current != null); - assert(lastEffective != null); - switch (current) { - case AnimationStatus.dismissed: - case AnimationStatus.completed: - return current; - case AnimationStatus.forward: - switch (lastEffective) { - case AnimationStatus.dismissed: - case AnimationStatus.completed: - case AnimationStatus.forward: - return current; - case AnimationStatus.reverse: - return lastEffective; - } - break; - case AnimationStatus.reverse: - switch (lastEffective) { - case AnimationStatus.dismissed: - case AnimationStatus.completed: - case AnimationStatus.reverse: - return current; - case AnimationStatus.forward: - return lastEffective; - } - break; - } - return null; // unreachable - } - - void _updateAnimationListener( - Animation oldAnimation, - Animation animation, - ) { - if (oldAnimation != animation) { - oldAnimation.removeStatusListener(_animationListener); - animation.addStatusListener(_animationListener); - _animationListener(animation.status); - } - } - - @override - void didUpdateWidget(SharedZAxisTransition oldWidget) { - super.didUpdateWidget(oldWidget); - _updateAnimationListener( - oldWidget.animation, - widget.animation, - ); - _updateAnimationListener( - oldWidget.secondaryAnimation, - widget.secondaryAnimation, - ); - } - - @override - void dispose() { - widget.animation.removeStatusListener(_animationListener); - widget.secondaryAnimation.removeStatusListener(_secondaryAnimationListener); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - final Tween flippedTween = Tween( - begin: 1.0, - end: 0.0, - ); - - return AnimatedBuilder( - animation: widget.animation, - builder: (BuildContext context, Widget child) { - assert(_effectiveAnimationStatus != null); - switch (_effectiveAnimationStatus) { - case AnimationStatus.forward: - return _EnterTransition( - animation: widget.animation, - child: child, - ); - case AnimationStatus.dismissed: - case AnimationStatus.reverse: - case AnimationStatus.completed: - return _ExitTransition( - animation: flippedTween.animate( - widget.animation, - ), - child: child, - ); - } - return null; // unreachable - }, - child: AnimatedBuilder( - animation: widget.secondaryAnimation, - builder: (BuildContext context, Widget child) { - assert(_effectiveSecondaryAnimationStatus != null); - switch (_effectiveSecondaryAnimationStatus) { - case AnimationStatus.forward: - return _ExitTransition( - animation: widget.secondaryAnimation, - child: child, - ); - case AnimationStatus.dismissed: - case AnimationStatus.reverse: - case AnimationStatus.completed: - return _EnterTransition( - animation: flippedTween.animate( - widget.secondaryAnimation, - ), - child: child, - ); - } - return null; // unreachable - }, - child: widget.child, - ), - ); - } -} - -class _EnterTransition extends StatelessWidget { - const _EnterTransition({ - this.animation, - this.child, - }); - - final Animation animation; - final Widget child; - - static Animatable fadeInTransition = CurveTween( - curve: decelerateEasing, - ).chain(CurveTween(curve: const Interval(0.3, 1.0))); - - static Animatable scaleInTransition = Tween( - begin: 0.80, - end: 1.00, - ).chain(CurveTween(curve: standardEasing)); - - @override - Widget build(BuildContext context) { - return FadeTransition( - opacity: fadeInTransition.animate(animation), - child: ScaleTransition( - scale: scaleInTransition.animate(animation), - child: child, - ), - ); - } -} - -class _ExitTransition extends StatelessWidget { - const _ExitTransition({ - this.animation, - this.child, - }); - - final Animation animation; - final Widget child; - - static Animatable fadeOutTransition = FlippedCurveTween( - curve: accelerateEasing, - ).chain(CurveTween(curve: const Interval(0.0, 0.3))); - - static Animatable scaleOutTransition = Tween( - begin: 1.00, - end: 1.10, - ).chain(CurveTween(curve: standardEasing)); - - @override - Widget build(BuildContext context) { - return FadeTransition( - opacity: fadeOutTransition.animate(animation), - child: Container( - color: Theme.of(context).canvasColor, - child: ScaleTransition( - scale: scaleOutTransition.animate(animation), - child: child, - ), - ), - ); - } -} diff --git a/packages/animations/test/shared_axis_transition_test.dart b/packages/animations/test/shared_axis_transition_test.dart index 19447c125208..60d59087ce0c 100644 --- a/packages/animations/test/shared_axis_transition_test.dart +++ b/packages/animations/test/shared_axis_transition_test.dart @@ -970,6 +970,398 @@ void main() { }, ); }); + + group('SharedAxisTransitionType.scaled', () { + testWidgets( + 'SharedAxisPageTransitionsBuilder builds a SharedAxisTransition', + (WidgetTester tester) async { + final AnimationController animation = AnimationController( + vsync: const TestVSync(), + ); + final AnimationController secondaryAnimation = AnimationController( + vsync: const TestVSync(), + ); + + await tester.pumpWidget( + const SharedAxisPageTransitionsBuilder( + transitionType: SharedAxisTransitionType.scaled, + ).buildTransitions( + null, + null, + animation, + secondaryAnimation, + const Placeholder(), + ), + ); + + expect(find.byType(SharedAxisTransition), findsOneWidget); + }, + ); + + testWidgets( + 'SharedAxisTransition runs forward', + (WidgetTester tester) async { + final GlobalKey navigator = GlobalKey(); + const String bottomRoute = '/'; + const String topRoute = '/a'; + + await tester.pumpWidget( + _TestWidget( + navigatorKey: navigator, + transitionType: SharedAxisTransitionType.scaled, + ), + ); + + expect(find.text(bottomRoute), findsOneWidget); + expect(_getScale(bottomRoute, tester), 1.0); + expect(_getOpacity(bottomRoute, tester), 1.0); + expect(find.text(topRoute), findsNothing); + + navigator.currentState.pushNamed(topRoute); + await tester.pump(); + await tester.pump(); + + // Bottom route is full size and fully visible. + expect(find.text(bottomRoute), findsOneWidget); + expect(_getScale(bottomRoute, tester), 1.0); + expect(_getOpacity(bottomRoute, tester), 1.0); + // Top route is at 80% of full size and not visible yet. + expect(find.text(topRoute), findsOneWidget); + expect(_getScale(topRoute, tester), 0.8); + expect(_getOpacity(topRoute, tester), 0.0); + + // Jump 3/10ths of the way through the transition, bottom route + // should be be completely faded out while the top route + // is also completely faded out. + // Transition time: 300ms, 3/10 * 300ms = 90ms + await tester.pump(const Duration(milliseconds: 90)); + + // Bottom route is now invisible + expect(find.text(bottomRoute), findsOneWidget); + expect(_getOpacity(bottomRoute, tester), 0.0); + // Top route is still invisible, but scaling up. + expect(find.text(topRoute), findsOneWidget); + expect(_getOpacity(topRoute, tester), 0.0); + double topScale = _getScale(topRoute, tester); + expect(topScale, greaterThan(0.8)); + expect(topScale, lessThan(1.0)); + + // Jump to the middle of fading in + await tester.pump(const Duration(milliseconds: 90)); + // Bottom route is still invisible + expect(find.text(bottomRoute), findsOneWidget); + expect(_getOpacity(bottomRoute, tester), 0.0); + // Top route is fading in + expect(find.text(topRoute), findsOneWidget); + expect(_getOpacity(topRoute, tester), greaterThan(0)); + expect(_getOpacity(topRoute, tester), lessThan(1.0)); + topScale = _getScale(topRoute, tester); + expect(topScale, greaterThan(0.8)); + expect(topScale, lessThan(1.0)); + + // Jump to the end of the transition + await tester.pump(const Duration(milliseconds: 120)); + // Bottom route is not visible. + expect(find.text(bottomRoute), findsOneWidget); + expect(_getScale(bottomRoute, tester), 1.1); + expect(_getOpacity(bottomRoute, tester), 0.0); + // Top route fully scaled in and visible. + expect(find.text(topRoute), findsOneWidget); + expect(_getScale(topRoute, tester), 1.0); + expect(_getOpacity(topRoute, tester), 1.0); + + await tester.pump(const Duration(milliseconds: 1)); + expect(find.text(bottomRoute), findsNothing); + expect(find.text(topRoute), findsOneWidget); + }, + ); + + testWidgets( + 'SharedAxisTransition runs in reverse', + (WidgetTester tester) async { + final GlobalKey navigator = GlobalKey(); + const String bottomRoute = '/'; + const String topRoute = '/a'; + + await tester.pumpWidget( + _TestWidget( + navigatorKey: navigator, + transitionType: SharedAxisTransitionType.scaled, + ), + ); + + navigator.currentState.pushNamed(topRoute); + await tester.pumpAndSettle(); + + expect(find.text(topRoute), findsOneWidget); + expect(_getScale(topRoute, tester), 1.0); + expect(_getOpacity(topRoute, tester), 1.0); + expect(find.text(bottomRoute), findsNothing); + + navigator.currentState.pop(); + await tester.pump(); + + // Top route is full size and fully visible. + expect(find.text(topRoute), findsOneWidget); + expect(_getScale(topRoute, tester), 1.0); + expect(_getOpacity(topRoute, tester), 1.0); + // Bottom route is at 80% of full size and not visible yet. + expect(find.text(bottomRoute), findsOneWidget); + expect(_getScale(bottomRoute, tester), 0.8); + expect(_getOpacity(bottomRoute, tester), 0.0); + + // Jump 3/10ths of the way through the transition, bottom route + // should be be completely faded out while the top route + // is also completely faded out. + // Transition time: 300ms, 3/10 * 300ms = 90ms + await tester.pump(const Duration(milliseconds: 90)); + + // Bottom route is now invisible + expect(find.text(topRoute), findsOneWidget); + expect(_getOpacity(topRoute, tester), 0.0); + // Top route is still invisible, but scaling up. + expect(find.text(bottomRoute), findsOneWidget); + expect( + _getOpacity(bottomRoute, tester), + moreOrLessEquals(0, epsilon: 0.005), + ); + double bottomScale = _getScale(bottomRoute, tester); + expect(bottomScale, greaterThan(0.8)); + expect(bottomScale, lessThan(1.0)); + + // Jump to the middle of fading in + await tester.pump(const Duration(milliseconds: 90)); + // Top route is still invisible + expect(find.text(topRoute), findsOneWidget); + expect(_getOpacity(topRoute, tester), 0.0); + // Bottom route is fading in + expect(find.text(bottomRoute), findsOneWidget); + expect(_getOpacity(bottomRoute, tester), greaterThan(0)); + expect(_getOpacity(bottomRoute, tester), lessThan(1.0)); + bottomScale = _getScale(bottomRoute, tester); + expect(bottomScale, greaterThan(0.8)); + expect(bottomScale, lessThan(1.0)); + + // Jump to the end of the transition + await tester.pump(const Duration(milliseconds: 120)); + // Top route is not visible. + expect(find.text(topRoute), findsOneWidget); + expect(_getScale(topRoute, tester), 1.1); + expect(_getOpacity(topRoute, tester), 0.0); + // Bottom route fully scaled in and visible. + expect(find.text(bottomRoute), findsOneWidget); + expect(_getScale(bottomRoute, tester), 1.0); + expect(_getOpacity(bottomRoute, tester), 1.0); + + await tester.pump(const Duration(milliseconds: 1)); + expect(find.text(topRoute), findsNothing); + expect(find.text(bottomRoute), findsOneWidget); + }, + ); + + testWidgets( + 'SharedAxisTransition does not jump when interrupted', + (WidgetTester tester) async { + final GlobalKey navigator = GlobalKey(); + const String bottomRoute = '/'; + const String topRoute = '/a'; + + await tester.pumpWidget( + _TestWidget( + navigatorKey: navigator, + transitionType: SharedAxisTransitionType.scaled, + ), + ); + expect(find.text(bottomRoute), findsOneWidget); + expect(find.text(topRoute), findsNothing); + + navigator.currentState.pushNamed(topRoute); + await tester.pump(); + + // Jump to halfway point of transition. + await tester.pump(const Duration(milliseconds: 150)); + // Bottom route is fully faded out. + expect(find.text(bottomRoute), findsOneWidget); + expect(_getOpacity(bottomRoute, tester), 0.0); + final double halfwayBottomScale = _getScale(bottomRoute, tester); + expect(halfwayBottomScale, greaterThan(1.0)); + expect(halfwayBottomScale, lessThan(1.1)); + + // Top route is fading/scaling in. + expect(find.text(topRoute), findsOneWidget); + final double halfwayTopScale = _getScale(topRoute, tester); + final double halfwayTopOpacity = _getOpacity(topRoute, tester); + expect(halfwayTopScale, greaterThan(0.8)); + expect(halfwayTopScale, lessThan(1.0)); + expect(halfwayTopOpacity, greaterThan(0.0)); + expect(halfwayTopOpacity, lessThan(1.0)); + + // Interrupt the transition with a pop. + navigator.currentState.pop(); + await tester.pump(); + + // Nothing should change. + expect(find.text(bottomRoute), findsOneWidget); + expect(_getScale(bottomRoute, tester), halfwayBottomScale); + expect(_getOpacity(bottomRoute, tester), 0.0); + expect(find.text(topRoute), findsOneWidget); + expect(_getScale(topRoute, tester), halfwayTopScale); + expect(_getOpacity(topRoute, tester), halfwayTopOpacity); + + // Jump to the 1/4 (75 ms) point of transition + await tester.pump(const Duration(milliseconds: 75)); + expect(find.text(bottomRoute), findsOneWidget); + expect(_getScale(bottomRoute, tester), greaterThan(1.0)); + expect(_getScale(bottomRoute, tester), lessThan(1.1)); + expect(_getScale(bottomRoute, tester), lessThan(halfwayBottomScale)); + expect(_getOpacity(bottomRoute, tester), greaterThan(0.0)); + expect(_getOpacity(bottomRoute, tester), lessThan(1.0)); + + // Jump to the end. + await tester.pump(const Duration(milliseconds: 75)); + expect(find.text(bottomRoute), findsOneWidget); + expect(_getScale(bottomRoute, tester), 1.0); + expect(_getOpacity(bottomRoute, tester), 1.0); + expect(find.text(topRoute), findsOneWidget); + expect(_getScale(topRoute, tester), 0.80); + expect(_getOpacity(topRoute, tester), 0.0); + + await tester.pump(const Duration(milliseconds: 1)); + expect(find.text(topRoute), findsNothing); + expect(find.text(bottomRoute), findsOneWidget); + }, + ); + + testWidgets( + 'SharedAxisTransition properly disposes animation', + (WidgetTester tester) async { + final GlobalKey navigator = GlobalKey(); + const String bottomRoute = '/'; + const String topRoute = '/a'; + + await tester.pumpWidget( + _TestWidget( + navigatorKey: navigator, + transitionType: SharedAxisTransitionType.scaled, + ), + ); + expect(find.text(bottomRoute), findsOneWidget); + expect(find.text(topRoute), findsNothing); + + navigator.currentState.pushNamed(topRoute); + await tester.pump(); + + // Jump to halfway point of transition. + await tester.pump(const Duration(milliseconds: 150)); + expect(find.byType(SharedAxisTransition), findsNWidgets(2)); + + // Rebuild the app without the transition. + await tester.pumpWidget( + MaterialApp( + navigatorKey: navigator, + home: const Material( + child: Text('abc'), + ), + ), + ); + await tester.pump(); + // Transitions should have been disposed of. + expect(find.byType(SharedAxisTransition), findsNothing); + }, + ); + + testWidgets( + 'State is not lost when transitioning', + (WidgetTester tester) async { + final GlobalKey navigator = GlobalKey(); + const String bottomRoute = '/'; + const String topRoute = '/a'; + + await tester.pumpWidget( + _TestWidget( + navigatorKey: navigator, + transitionType: SharedAxisTransitionType.scaled, + contentBuilder: (RouteSettings settings) { + return _StatefulTestWidget( + key: ValueKey(settings.name), + name: settings.name, + ); + }, + ), + ); + + final _StatefulTestWidgetState bottomState = tester.state( + find.byKey(const ValueKey(bottomRoute)), + ); + expect(bottomState.widget.name, bottomRoute); + + navigator.currentState.pushNamed(topRoute); + await tester.pump(); + await tester.pump(); + + expect( + tester.state(find.byKey(const ValueKey(bottomRoute))), + bottomState, + ); + final _StatefulTestWidgetState topState = tester.state( + find.byKey(const ValueKey(topRoute)), + ); + expect(topState.widget.name, topRoute); + + await tester.pump(const Duration(milliseconds: 150)); + expect( + tester.state(find.byKey(const ValueKey(bottomRoute))), + bottomState, + ); + expect( + tester.state(find.byKey(const ValueKey(topRoute))), + topState, + ); + + await tester.pumpAndSettle(); + expect( + tester.state(find.byKey( + const ValueKey(bottomRoute), + skipOffstage: false, + )), + bottomState, + ); + expect( + tester.state(find.byKey(const ValueKey(topRoute))), + topState, + ); + + navigator.currentState.pop(); + await tester.pump(); + + expect( + tester.state(find.byKey(const ValueKey(bottomRoute))), + bottomState, + ); + expect( + tester.state(find.byKey(const ValueKey(topRoute))), + topState, + ); + + await tester.pump(const Duration(milliseconds: 150)); + expect( + tester.state(find.byKey(const ValueKey(bottomRoute))), + bottomState, + ); + expect( + tester.state(find.byKey(const ValueKey(topRoute))), + topState, + ); + + await tester.pumpAndSettle(); + expect( + tester.state(find.byKey(const ValueKey(bottomRoute))), + bottomState, + ); + expect(find.byKey(const ValueKey(topRoute)), findsNothing); + }, + ); + }); } double _getOpacity(String key, WidgetTester tester) { @@ -1010,10 +1402,26 @@ double _getTranslationOffset( return a + translation.y; }); break; + case SharedAxisTransitionType.scaled: + // SharedAxisTransitionType.scaled should not return a translation + // offset. + return null; + break; } return null; // unreachable } +double _getScale(String key, WidgetTester tester) { + final Finder finder = find.ancestor( + of: find.byKey(ValueKey(key)), + matching: find.byType(ScaleTransition), + ); + return tester.widgetList(finder).fold(1.0, (double a, Widget widget) { + final ScaleTransition transition = widget; + return a * transition.scale.value; + }); +} + class _TestWidget extends StatelessWidget { const _TestWidget({ this.navigatorKey, diff --git a/packages/animations/test/shared_z_axis_transition_test.dart b/packages/animations/test/shared_z_axis_transition_test.dart deleted file mode 100644 index ff30fb550ea2..000000000000 --- a/packages/animations/test/shared_z_axis_transition_test.dart +++ /dev/null @@ -1,467 +0,0 @@ -// Copyright 2019 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import 'package:animations/src/shared_z_axis_transition.dart'; -import 'package:flutter/animation.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:flutter/widgets.dart'; - -void main() { - testWidgets( - 'SharedZAxisPageTransitionsBuilder builds a SharedZAxisTransition', - (WidgetTester tester) async { - final AnimationController animation = AnimationController( - vsync: const TestVSync(), - ); - final AnimationController secondaryAnimation = AnimationController( - vsync: const TestVSync(), - ); - - await tester.pumpWidget( - const SharedZAxisPageTransitionsBuilder().buildTransitions( - null, - null, - animation, - secondaryAnimation, - const Placeholder(), - ), - ); - - expect(find.byType(SharedZAxisTransition), findsOneWidget); - }, - ); - - testWidgets( - 'SharedZAxisTransition runs forward', - (WidgetTester tester) async { - final GlobalKey navigator = GlobalKey(); - const String bottomRoute = '/'; - const String topRoute = '/a'; - - await tester.pumpWidget( - _TestWidget(navigatorKey: navigator), - ); - - expect(find.text(bottomRoute), findsOneWidget); - expect(_getScale(bottomRoute, tester), 1.0); - expect(_getOpacity(bottomRoute, tester), 1.0); - expect(find.text(topRoute), findsNothing); - - navigator.currentState.pushNamed(topRoute); - await tester.pump(); - await tester.pump(); - - // Bottom route is full size and fully visible. - expect(find.text(bottomRoute), findsOneWidget); - expect(_getScale(bottomRoute, tester), 1.0); - expect(_getOpacity(bottomRoute, tester), 1.0); - // Top route is at 80% of full size and not visible yet. - expect(find.text(topRoute), findsOneWidget); - expect(_getScale(topRoute, tester), 0.8); - expect(_getOpacity(topRoute, tester), 0.0); - - // Jump 3/10ths of the way through the transition, bottom route - // should be be completely faded out while the top route - // is also completely faded out. - // Transition time: 300ms, 3/10 * 300ms = 90ms - await tester.pump(const Duration(milliseconds: 90)); - - // Bottom route is now invisible - expect(find.text(bottomRoute), findsOneWidget); - expect(_getOpacity(bottomRoute, tester), 0.0); - // Top route is still invisible, but scaling up. - expect(find.text(topRoute), findsOneWidget); - expect(_getOpacity(topRoute, tester), 0.0); - double topScale = _getScale(topRoute, tester); - expect(topScale, greaterThan(0.8)); - expect(topScale, lessThan(1.0)); - - // Jump to the middle of fading in - await tester.pump(const Duration(milliseconds: 90)); - // Bottom route is still invisible - expect(find.text(bottomRoute), findsOneWidget); - expect(_getOpacity(bottomRoute, tester), 0.0); - // Top route is fading in - expect(find.text(topRoute), findsOneWidget); - expect(_getOpacity(topRoute, tester), greaterThan(0)); - expect(_getOpacity(topRoute, tester), lessThan(1.0)); - topScale = _getScale(topRoute, tester); - expect(topScale, greaterThan(0.8)); - expect(topScale, lessThan(1.0)); - - // Jump to the end of the transition - await tester.pump(const Duration(milliseconds: 120)); - // Bottom route is not visible. - expect(find.text(bottomRoute), findsOneWidget); - expect(_getScale(bottomRoute, tester), 1.1); - expect(_getOpacity(bottomRoute, tester), 0.0); - // Top route fully scaled in and visible. - expect(find.text(topRoute), findsOneWidget); - expect(_getScale(topRoute, tester), 1.0); - expect(_getOpacity(topRoute, tester), 1.0); - - await tester.pump(const Duration(milliseconds: 1)); - expect(find.text(bottomRoute), findsNothing); - expect(find.text(topRoute), findsOneWidget); - }, - ); - - testWidgets( - 'SharedZAxisTransition runs in reverse', - (WidgetTester tester) async { - final GlobalKey navigator = GlobalKey(); - const String bottomRoute = '/'; - const String topRoute = '/a'; - - await tester.pumpWidget( - _TestWidget(navigatorKey: navigator), - ); - - navigator.currentState.pushNamed(topRoute); - await tester.pumpAndSettle(); - - expect(find.text(topRoute), findsOneWidget); - expect(_getScale(topRoute, tester), 1.0); - expect(_getOpacity(topRoute, tester), 1.0); - expect(find.text(bottomRoute), findsNothing); - - navigator.currentState.pop(); - await tester.pump(); - - // Top route is full size and fully visible. - expect(find.text(topRoute), findsOneWidget); - expect(_getScale(topRoute, tester), 1.0); - expect(_getOpacity(topRoute, tester), 1.0); - // Bottom route is at 80% of full size and not visible yet. - expect(find.text(bottomRoute), findsOneWidget); - expect(_getScale(bottomRoute, tester), 0.8); - expect(_getOpacity(bottomRoute, tester), 0.0); - - // Jump 3/10ths of the way through the transition, bottom route - // should be be completely faded out while the top route - // is also completely faded out. - // Transition time: 300ms, 3/10 * 300ms = 90ms - await tester.pump(const Duration(milliseconds: 90)); - - // Bottom route is now invisible - expect(find.text(topRoute), findsOneWidget); - expect(_getOpacity(topRoute, tester), 0.0); - // Top route is still invisible, but scaling up. - expect(find.text(bottomRoute), findsOneWidget); - expect( - _getOpacity(bottomRoute, tester), - moreOrLessEquals(0, epsilon: 0.005), - ); - double bottomScale = _getScale(bottomRoute, tester); - expect(bottomScale, greaterThan(0.8)); - expect(bottomScale, lessThan(1.0)); - - // Jump to the middle of fading in - await tester.pump(const Duration(milliseconds: 90)); - // Top route is still invisible - expect(find.text(topRoute), findsOneWidget); - expect(_getOpacity(topRoute, tester), 0.0); - // Bottom route is fading in - expect(find.text(bottomRoute), findsOneWidget); - expect(_getOpacity(bottomRoute, tester), greaterThan(0)); - expect(_getOpacity(bottomRoute, tester), lessThan(1.0)); - bottomScale = _getScale(bottomRoute, tester); - expect(bottomScale, greaterThan(0.8)); - expect(bottomScale, lessThan(1.0)); - - // Jump to the end of the transition - await tester.pump(const Duration(milliseconds: 120)); - // Top route is not visible. - expect(find.text(topRoute), findsOneWidget); - expect(_getScale(topRoute, tester), 1.1); - expect(_getOpacity(topRoute, tester), 0.0); - // Bottom route fully scaled in and visible. - expect(find.text(bottomRoute), findsOneWidget); - expect(_getScale(bottomRoute, tester), 1.0); - expect(_getOpacity(bottomRoute, tester), 1.0); - - await tester.pump(const Duration(milliseconds: 1)); - expect(find.text(topRoute), findsNothing); - expect(find.text(bottomRoute), findsOneWidget); - }, - ); - - testWidgets( - 'SharedZAxisTransition does not jump when interrupted', - (WidgetTester tester) async { - final GlobalKey navigator = GlobalKey(); - const String bottomRoute = '/'; - const String topRoute = '/a'; - - await tester.pumpWidget( - _TestWidget( - navigatorKey: navigator, - ), - ); - expect(find.text(bottomRoute), findsOneWidget); - expect(find.text(topRoute), findsNothing); - - navigator.currentState.pushNamed(topRoute); - await tester.pump(); - - // Jump to halfway point of transition. - await tester.pump(const Duration(milliseconds: 150)); - // Bottom route is fully faded out. - expect(find.text(bottomRoute), findsOneWidget); - expect(_getOpacity(bottomRoute, tester), 0.0); - final double halfwayBottomScale = _getScale(bottomRoute, tester); - expect(halfwayBottomScale, greaterThan(1.0)); - expect(halfwayBottomScale, lessThan(1.1)); - - // Top route is fading/scaling in. - expect(find.text(topRoute), findsOneWidget); - final double halfwayTopScale = _getScale(topRoute, tester); - final double halfwayTopOpacity = _getOpacity(topRoute, tester); - expect(halfwayTopScale, greaterThan(0.8)); - expect(halfwayTopScale, lessThan(1.0)); - expect(halfwayTopOpacity, greaterThan(0.0)); - expect(halfwayTopOpacity, lessThan(1.0)); - - // Interrupt the transition with a pop. - navigator.currentState.pop(); - await tester.pump(); - - // Nothing should change. - expect(find.text(bottomRoute), findsOneWidget); - expect(_getScale(bottomRoute, tester), halfwayBottomScale); - expect(_getOpacity(bottomRoute, tester), 0.0); - expect(find.text(topRoute), findsOneWidget); - expect(_getScale(topRoute, tester), halfwayTopScale); - expect(_getOpacity(topRoute, tester), halfwayTopOpacity); - - // Jump to the 1/4 (75 ms) point of transition - await tester.pump(const Duration(milliseconds: 75)); - expect(find.text(bottomRoute), findsOneWidget); - expect(_getScale(bottomRoute, tester), greaterThan(1.0)); - expect(_getScale(bottomRoute, tester), lessThan(1.1)); - expect(_getScale(bottomRoute, tester), lessThan(halfwayBottomScale)); - expect(_getOpacity(bottomRoute, tester), greaterThan(0.0)); - expect(_getOpacity(bottomRoute, tester), lessThan(1.0)); - - // Jump to the end. - await tester.pump(const Duration(milliseconds: 75)); - expect(find.text(bottomRoute), findsOneWidget); - expect(_getScale(bottomRoute, tester), 1.0); - expect(_getOpacity(bottomRoute, tester), 1.0); - expect(find.text(topRoute), findsOneWidget); - expect(_getScale(topRoute, tester), 0.80); - expect(_getOpacity(topRoute, tester), 0.0); - - await tester.pump(const Duration(milliseconds: 1)); - expect(find.text(topRoute), findsNothing); - expect(find.text(bottomRoute), findsOneWidget); - }, - ); - - testWidgets( - 'SharedZAxisTransition properly disposes animation', - (WidgetTester tester) async { - final GlobalKey navigator = GlobalKey(); - const String bottomRoute = '/'; - const String topRoute = '/a'; - - await tester.pumpWidget( - _TestWidget( - navigatorKey: navigator, - ), - ); - expect(find.text(bottomRoute), findsOneWidget); - expect(find.text(topRoute), findsNothing); - - navigator.currentState.pushNamed(topRoute); - await tester.pump(); - - // Jump to halfway point of transition. - await tester.pump(const Duration(milliseconds: 150)); - expect(find.byType(SharedZAxisTransition), findsNWidgets(2)); - - // Rebuild the app without the transition. - await tester.pumpWidget( - MaterialApp( - navigatorKey: navigator, - home: const Material( - child: Text('abc'), - ), - ), - ); - await tester.pump(); - // Transitions should have been disposed of. - expect(find.byType(SharedZAxisTransition), findsNothing); - }, - ); - - testWidgets( - 'State is not lost when transitioning', - (WidgetTester tester) async { - final GlobalKey navigator = GlobalKey(); - const String bottomRoute = '/'; - const String topRoute = '/a'; - - await tester.pumpWidget( - _TestWidget( - navigatorKey: navigator, - contentBuilder: (RouteSettings settings) { - return _StatefulTestWidget( - key: ValueKey(settings.name), - name: settings.name, - ); - }, - ), - ); - - final _StatefulTestWidgetState bottomState = tester.state( - find.byKey(const ValueKey(bottomRoute)), - ); - expect(bottomState.widget.name, bottomRoute); - - navigator.currentState.pushNamed(topRoute); - await tester.pump(); - await tester.pump(); - - expect( - tester.state(find.byKey(const ValueKey(bottomRoute))), - bottomState, - ); - final _StatefulTestWidgetState topState = tester.state( - find.byKey(const ValueKey(topRoute)), - ); - expect(topState.widget.name, topRoute); - - await tester.pump(const Duration(milliseconds: 150)); - expect( - tester.state(find.byKey(const ValueKey(bottomRoute))), - bottomState, - ); - expect( - tester.state(find.byKey(const ValueKey(topRoute))), - topState, - ); - - await tester.pumpAndSettle(); - expect( - tester.state(find.byKey( - const ValueKey(bottomRoute), - skipOffstage: false, - )), - bottomState, - ); - expect( - tester.state(find.byKey(const ValueKey(topRoute))), - topState, - ); - - navigator.currentState.pop(); - await tester.pump(); - - expect( - tester.state(find.byKey(const ValueKey(bottomRoute))), - bottomState, - ); - expect( - tester.state(find.byKey(const ValueKey(topRoute))), - topState, - ); - - await tester.pump(const Duration(milliseconds: 150)); - expect( - tester.state(find.byKey(const ValueKey(bottomRoute))), - bottomState, - ); - expect( - tester.state(find.byKey(const ValueKey(topRoute))), - topState, - ); - - await tester.pumpAndSettle(); - expect( - tester.state(find.byKey(const ValueKey(bottomRoute))), - bottomState, - ); - expect(find.byKey(const ValueKey(topRoute)), findsNothing); - }, - ); -} - -double _getOpacity(String key, WidgetTester tester) { - final Finder finder = find.ancestor( - of: find.byKey(ValueKey(key)), - matching: find.byType(FadeTransition), - ); - return tester.widgetList(finder).fold(1.0, (double a, Widget widget) { - final FadeTransition transition = widget; - return a * transition.opacity.value; - }); -} - -double _getScale(String key, WidgetTester tester) { - final Finder finder = find.ancestor( - of: find.byKey(ValueKey(key)), - matching: find.byType(ScaleTransition), - ); - return tester.widgetList(finder).fold(1.0, (double a, Widget widget) { - final ScaleTransition transition = widget; - return a * transition.scale.value; - }); -} - -class _TestWidget extends StatelessWidget { - const _TestWidget({this.navigatorKey, this.contentBuilder}); - - final Key navigatorKey; - final _ContentBuilder contentBuilder; - - @override - Widget build(BuildContext context) { - return MaterialApp( - navigatorKey: navigatorKey, - theme: ThemeData( - platform: TargetPlatform.android, - pageTransitionsTheme: const PageTransitionsTheme( - builders: { - TargetPlatform.android: SharedZAxisPageTransitionsBuilder(), - }, - ), - ), - onGenerateRoute: (RouteSettings settings) { - return MaterialPageRoute( - settings: settings, - builder: (BuildContext context) { - return contentBuilder != null - ? contentBuilder(settings) - : Container( - child: Center( - key: ValueKey(settings.name), - child: Text(settings.name), - ), - ); - }, - ); - }, - ); - } -} - -class _StatefulTestWidget extends StatefulWidget { - const _StatefulTestWidget({Key key, this.name}) : super(key: key); - - final String name; - - @override - State<_StatefulTestWidget> createState() => _StatefulTestWidgetState(); -} - -class _StatefulTestWidgetState extends State<_StatefulTestWidget> { - @override - Widget build(BuildContext context) { - return Text(widget.name); - } -} - -typedef _ContentBuilder = Widget Function(RouteSettings settings); From d163acd31fac0a0ea2243ef6ddddba1b91d104d3 Mon Sep 17 00:00:00 2001 From: Shi Hao Hong Date: Mon, 13 Jan 2020 15:42:53 -0800 Subject: [PATCH 35/37] Address code review feedback --- .../lib/src/shared_axis_transition.dart | 21 +- .../test/shared_axis_transition_test.dart | 448 ++++++++++-------- 2 files changed, 256 insertions(+), 213 deletions(-) diff --git a/packages/animations/lib/src/shared_axis_transition.dart b/packages/animations/lib/src/shared_axis_transition.dart index 3e22f575762a..d572ef1706c7 100644 --- a/packages/animations/lib/src/shared_axis_transition.dart +++ b/packages/animations/lib/src/shared_axis_transition.dart @@ -9,7 +9,7 @@ import 'package:flutter/widgets.dart'; import 'utils/curves.dart'; -/// Determines which type shared axis transition. +/// Determines which type of shared axis transition is used. enum SharedAxisTransitionType { /// Creates a shared axis vertical translation page transition. vertical, @@ -88,11 +88,12 @@ class SharedAxisPageTransitionsBuilder extends PageTransitionsBuilder { @override Widget buildTransitions( - PageRoute route, - BuildContext context, - Animation animation, - Animation secondaryAnimation, - Widget child) { + PageRoute route, + BuildContext context, + Animation animation, + Animation secondaryAnimation, + Widget child, + ) { return SharedAxisTransition( animation: animation, secondaryAnimation: secondaryAnimation, @@ -117,6 +118,7 @@ class SharedAxisPageTransitionsBuilder extends PageTransitionsBuilder { /// This transition can also be used directly in a /// [PageTransitionSwitcher.transitionBuilder] to transition /// from one widget to another as seen in the following example: +/// /// ```dart /// int _selectedIndex = 0; /// @@ -206,7 +208,12 @@ class SharedAxisTransition extends StatefulWidget { /// property when the it is used as a page transition. final Animation secondaryAnimation; - /// Determines which type shared axis transition. + /// Determines which type of shared axis transition is used. + /// + /// See also: + /// + /// * [SharedAxisTransitionType], which defines and describes all shared + /// axis transition types. final SharedAxisTransitionType transitionType; /// The widget below this widget in the tree. diff --git a/packages/animations/test/shared_axis_transition_test.dart b/packages/animations/test/shared_axis_transition_test.dart index 60d59087ce0c..e8808434cb51 100644 --- a/packages/animations/test/shared_axis_transition_test.dart +++ b/packages/animations/test/shared_axis_transition_test.dart @@ -54,12 +54,13 @@ void main() { expect(find.text(bottomRoute), findsOneWidget); expect( - _getTranslationOffset( - bottomRoute, - tester, - SharedAxisTransitionType.horizontal, - ), - 0.0); + _getTranslationOffset( + bottomRoute, + tester, + SharedAxisTransitionType.horizontal, + ), + 0.0, + ); expect(_getOpacity(bottomRoute, tester), 1.0); expect(find.text(topRoute), findsNothing); @@ -70,23 +71,25 @@ void main() { // Bottom route is not offset and fully visible. expect(find.text(bottomRoute), findsOneWidget); expect( - _getTranslationOffset( - bottomRoute, - tester, - SharedAxisTransitionType.horizontal, - ), - 0.0); + _getTranslationOffset( + bottomRoute, + tester, + SharedAxisTransitionType.horizontal, + ), + 0.0, + ); expect(_getOpacity(bottomRoute, tester), 1.0); // Top route is offset to the right by 30.0 pixels // and not visible yet. expect(find.text(topRoute), findsOneWidget); expect( - _getTranslationOffset( - topRoute, - tester, - SharedAxisTransitionType.horizontal, - ), - 30.0); + _getTranslationOffset( + topRoute, + tester, + SharedAxisTransitionType.horizontal, + ), + 30.0, + ); expect(_getOpacity(topRoute, tester), 0.0); // Jump 3/10ths of the way through the transition, bottom route @@ -132,22 +135,24 @@ void main() { expect(find.text(bottomRoute), findsOneWidget); expect( - _getTranslationOffset( - bottomRoute, - tester, - SharedAxisTransitionType.horizontal, - ), - 30.0); + _getTranslationOffset( + bottomRoute, + tester, + SharedAxisTransitionType.horizontal, + ), + 30.0, + ); expect(_getOpacity(bottomRoute, tester), 0.0); // Top route has no offset and is visible. expect(find.text(topRoute), findsOneWidget); expect( - _getTranslationOffset( - topRoute, - tester, - SharedAxisTransitionType.horizontal, - ), - 0.0); + _getTranslationOffset( + topRoute, + tester, + SharedAxisTransitionType.horizontal, + ), + 0.0, + ); expect(_getOpacity(topRoute, tester), 1.0); await tester.pump(const Duration(milliseconds: 1)); @@ -175,12 +180,13 @@ void main() { expect(find.text(topRoute), findsOneWidget); expect( - _getTranslationOffset( - topRoute, - tester, - SharedAxisTransitionType.horizontal, - ), - 0.0); + _getTranslationOffset( + topRoute, + tester, + SharedAxisTransitionType.horizontal, + ), + 0.0, + ); expect(_getOpacity(topRoute, tester), 1.0); expect(find.text(bottomRoute), findsNothing); @@ -190,22 +196,24 @@ void main() { // Top route is is not offset and fully visible. expect(find.text(topRoute), findsOneWidget); expect( - _getTranslationOffset( - topRoute, - tester, - SharedAxisTransitionType.horizontal, - ), - 0.0); + _getTranslationOffset( + topRoute, + tester, + SharedAxisTransitionType.horizontal, + ), + 0.0, + ); expect(_getOpacity(topRoute, tester), 1.0); // Bottom route is offset to the right and is not visible yet. expect(find.text(bottomRoute), findsOneWidget); expect( - _getTranslationOffset( - bottomRoute, - tester, - SharedAxisTransitionType.horizontal, - ), - 30.0); + _getTranslationOffset( + bottomRoute, + tester, + SharedAxisTransitionType.horizontal, + ), + 30.0, + ); expect(_getOpacity(bottomRoute, tester), 0.0); // Jump 3/10ths of the way through the transition, bottom route @@ -251,22 +259,24 @@ void main() { // Top route is not visible and is offset to the right. expect(find.text(topRoute), findsOneWidget); expect( - _getTranslationOffset( - topRoute, - tester, - SharedAxisTransitionType.horizontal, - ), - 30.0); + _getTranslationOffset( + topRoute, + tester, + SharedAxisTransitionType.horizontal, + ), + 30.0, + ); expect(_getOpacity(topRoute, tester), 0.0); // Bottom route is not offset and is visible. expect(find.text(bottomRoute), findsOneWidget); expect( - _getTranslationOffset( - bottomRoute, - tester, - SharedAxisTransitionType.horizontal, - ), - 0.0); + _getTranslationOffset( + bottomRoute, + tester, + SharedAxisTransitionType.horizontal, + ), + 0.0, + ); expect(_getOpacity(bottomRoute, tester), 1.0); await tester.pump(const Duration(milliseconds: 1)); @@ -327,47 +337,52 @@ void main() { // Nothing should change. expect(find.text(bottomRoute), findsOneWidget); expect( - _getTranslationOffset( - bottomRoute, - tester, - SharedAxisTransitionType.horizontal, - ), - halfwayBottomOffset); + _getTranslationOffset( + bottomRoute, + tester, + SharedAxisTransitionType.horizontal, + ), + halfwayBottomOffset, + ); expect(_getOpacity(bottomRoute, tester), 0.0); expect(find.text(topRoute), findsOneWidget); expect( - _getTranslationOffset( - topRoute, - tester, - SharedAxisTransitionType.horizontal, - ), - halfwayTopOffset); + _getTranslationOffset( + topRoute, + tester, + SharedAxisTransitionType.horizontal, + ), + halfwayTopOffset, + ); expect(_getOpacity(topRoute, tester), halfwayTopOpacity); // Jump to the 1/4 (75 ms) point of transition await tester.pump(const Duration(milliseconds: 75)); expect(find.text(bottomRoute), findsOneWidget); expect( - _getTranslationOffset( - bottomRoute, - tester, - SharedAxisTransitionType.horizontal, - ), - greaterThan(0.0)); + _getTranslationOffset( + bottomRoute, + tester, + SharedAxisTransitionType.horizontal, + ), + greaterThan(0.0), + ); expect( - _getTranslationOffset( - bottomRoute, - tester, - SharedAxisTransitionType.horizontal, - ), - lessThan(30.0)); + _getTranslationOffset( + bottomRoute, + tester, + SharedAxisTransitionType.horizontal, + ), + lessThan(30.0), + ); expect( - _getTranslationOffset( - bottomRoute, - tester, - SharedAxisTransitionType.horizontal, - ), - lessThan(halfwayBottomOffset)); + _getTranslationOffset( + bottomRoute, + tester, + SharedAxisTransitionType.horizontal, + ), + lessThan(halfwayBottomOffset), + ); expect(_getOpacity(bottomRoute, tester), greaterThan(0.0)); expect(_getOpacity(bottomRoute, tester), lessThan(1.0)); @@ -375,21 +390,23 @@ void main() { await tester.pump(const Duration(milliseconds: 75)); expect(find.text(bottomRoute), findsOneWidget); expect( - _getTranslationOffset( - bottomRoute, - tester, - SharedAxisTransitionType.horizontal, - ), - 0.0); + _getTranslationOffset( + bottomRoute, + tester, + SharedAxisTransitionType.horizontal, + ), + 0.0, + ); expect(_getOpacity(bottomRoute, tester), 1.0); expect(find.text(topRoute), findsOneWidget); expect( - _getTranslationOffset( - topRoute, - tester, - SharedAxisTransitionType.horizontal, - ), - 30.0); + _getTranslationOffset( + topRoute, + tester, + SharedAxisTransitionType.horizontal, + ), + 30.0, + ); expect(_getOpacity(topRoute, tester), 0.0); await tester.pump(const Duration(milliseconds: 1)); @@ -534,12 +551,13 @@ void main() { expect(find.text(bottomRoute), findsOneWidget); expect( - _getTranslationOffset( - bottomRoute, - tester, - SharedAxisTransitionType.vertical, - ), - 0.0); + _getTranslationOffset( + bottomRoute, + tester, + SharedAxisTransitionType.vertical, + ), + 0.0, + ); expect(_getOpacity(bottomRoute, tester), 1.0); expect(find.text(topRoute), findsNothing); @@ -550,23 +568,25 @@ void main() { // Bottom route is not offset and fully visible. expect(find.text(bottomRoute), findsOneWidget); expect( - _getTranslationOffset( - bottomRoute, - tester, - SharedAxisTransitionType.vertical, - ), - 0.0); + _getTranslationOffset( + bottomRoute, + tester, + SharedAxisTransitionType.vertical, + ), + 0.0, + ); expect(_getOpacity(bottomRoute, tester), 1.0); // Top route is offset to the right by 30.0 pixels // and not visible yet. expect(find.text(topRoute), findsOneWidget); expect( - _getTranslationOffset( - topRoute, - tester, - SharedAxisTransitionType.vertical, - ), - 30.0); + _getTranslationOffset( + topRoute, + tester, + SharedAxisTransitionType.vertical, + ), + 30.0, + ); expect(_getOpacity(topRoute, tester), 0.0); // Jump 3/10ths of the way through the transition, bottom route @@ -612,22 +632,24 @@ void main() { expect(find.text(bottomRoute), findsOneWidget); expect( - _getTranslationOffset( - bottomRoute, - tester, - SharedAxisTransitionType.vertical, - ), - 30.0); + _getTranslationOffset( + bottomRoute, + tester, + SharedAxisTransitionType.vertical, + ), + 30.0, + ); expect(_getOpacity(bottomRoute, tester), 0.0); // Top route has no offset and is visible. expect(find.text(topRoute), findsOneWidget); expect( - _getTranslationOffset( - topRoute, - tester, - SharedAxisTransitionType.vertical, - ), - 0.0); + _getTranslationOffset( + topRoute, + tester, + SharedAxisTransitionType.vertical, + ), + 0.0, + ); expect(_getOpacity(topRoute, tester), 1.0); await tester.pump(const Duration(milliseconds: 1)); @@ -655,12 +677,13 @@ void main() { expect(find.text(topRoute), findsOneWidget); expect( - _getTranslationOffset( - topRoute, - tester, - SharedAxisTransitionType.vertical, - ), - 0.0); + _getTranslationOffset( + topRoute, + tester, + SharedAxisTransitionType.vertical, + ), + 0.0, + ); expect(_getOpacity(topRoute, tester), 1.0); expect(find.text(bottomRoute), findsNothing); @@ -670,22 +693,24 @@ void main() { // Top route is is not offset and fully visible. expect(find.text(topRoute), findsOneWidget); expect( - _getTranslationOffset( - topRoute, - tester, - SharedAxisTransitionType.vertical, - ), - 0.0); + _getTranslationOffset( + topRoute, + tester, + SharedAxisTransitionType.vertical, + ), + 0.0, + ); expect(_getOpacity(topRoute, tester), 1.0); // Bottom route is offset to the right and is not visible yet. expect(find.text(bottomRoute), findsOneWidget); expect( - _getTranslationOffset( - bottomRoute, - tester, - SharedAxisTransitionType.vertical, - ), - 30.0); + _getTranslationOffset( + bottomRoute, + tester, + SharedAxisTransitionType.vertical, + ), + 30.0, + ); expect(_getOpacity(bottomRoute, tester), 0.0); // Jump 3/10ths of the way through the transition, bottom route @@ -699,8 +724,10 @@ void main() { expect(_getOpacity(topRoute, tester), 0.0); // Bottom route is still invisible, but moving towards the left. expect(find.text(bottomRoute), findsOneWidget); - expect(_getOpacity(bottomRoute, tester), - moreOrLessEquals(0, epsilon: 0.005)); + expect( + _getOpacity(bottomRoute, tester), + moreOrLessEquals(0, epsilon: 0.005), + ); double bottomOffset = _getTranslationOffset( bottomRoute, tester, @@ -731,22 +758,24 @@ void main() { // Top route is not visible and is offset to the right. expect(find.text(topRoute), findsOneWidget); expect( - _getTranslationOffset( - topRoute, - tester, - SharedAxisTransitionType.vertical, - ), - 30.0); + _getTranslationOffset( + topRoute, + tester, + SharedAxisTransitionType.vertical, + ), + 30.0, + ); expect(_getOpacity(topRoute, tester), 0.0); // Bottom route is not offset and is visible. expect(find.text(bottomRoute), findsOneWidget); expect( - _getTranslationOffset( - bottomRoute, - tester, - SharedAxisTransitionType.vertical, - ), - 0.0); + _getTranslationOffset( + bottomRoute, + tester, + SharedAxisTransitionType.vertical, + ), + 0.0, + ); expect(_getOpacity(bottomRoute, tester), 1.0); await tester.pump(const Duration(milliseconds: 1)); @@ -807,47 +836,52 @@ void main() { // Nothing should change. expect(find.text(bottomRoute), findsOneWidget); expect( - _getTranslationOffset( - bottomRoute, - tester, - SharedAxisTransitionType.vertical, - ), - halfwayBottomOffset); + _getTranslationOffset( + bottomRoute, + tester, + SharedAxisTransitionType.vertical, + ), + halfwayBottomOffset, + ); expect(_getOpacity(bottomRoute, tester), 0.0); expect(find.text(topRoute), findsOneWidget); expect( - _getTranslationOffset( - topRoute, - tester, - SharedAxisTransitionType.vertical, - ), - halfwayTopOffset); + _getTranslationOffset( + topRoute, + tester, + SharedAxisTransitionType.vertical, + ), + halfwayTopOffset, + ); expect(_getOpacity(topRoute, tester), halfwayTopOpacity); // Jump to the 1/4 (75 ms) point of transition await tester.pump(const Duration(milliseconds: 75)); expect(find.text(bottomRoute), findsOneWidget); expect( - _getTranslationOffset( - bottomRoute, - tester, - SharedAxisTransitionType.vertical, - ), - greaterThan(0.0)); + _getTranslationOffset( + bottomRoute, + tester, + SharedAxisTransitionType.vertical, + ), + greaterThan(0.0), + ); expect( - _getTranslationOffset( - bottomRoute, - tester, - SharedAxisTransitionType.vertical, - ), - lessThan(30.0)); + _getTranslationOffset( + bottomRoute, + tester, + SharedAxisTransitionType.vertical, + ), + lessThan(30.0), + ); expect( - _getTranslationOffset( - bottomRoute, - tester, - SharedAxisTransitionType.vertical, - ), - lessThan(halfwayBottomOffset)); + _getTranslationOffset( + bottomRoute, + tester, + SharedAxisTransitionType.vertical, + ), + lessThan(halfwayBottomOffset), + ); expect(_getOpacity(bottomRoute, tester), greaterThan(0.0)); expect(_getOpacity(bottomRoute, tester), lessThan(1.0)); @@ -855,21 +889,23 @@ void main() { await tester.pump(const Duration(milliseconds: 75)); expect(find.text(bottomRoute), findsOneWidget); expect( - _getTranslationOffset( - bottomRoute, - tester, - SharedAxisTransitionType.vertical, - ), - 0.0); + _getTranslationOffset( + bottomRoute, + tester, + SharedAxisTransitionType.vertical, + ), + 0.0, + ); expect(_getOpacity(bottomRoute, tester), 1.0); expect(find.text(topRoute), findsOneWidget); expect( - _getTranslationOffset( - topRoute, - tester, - SharedAxisTransitionType.vertical, - ), - 30.0); + _getTranslationOffset( + topRoute, + tester, + SharedAxisTransitionType.vertical, + ), + 30.0, + ); expect(_getOpacity(topRoute, tester), 0.0); await tester.pump(const Duration(milliseconds: 1)); From 7813900413445c981542f94a0d31d460f049d043 Mon Sep 17 00:00:00 2001 From: Shi Hao Hong Date: Mon, 13 Jan 2020 15:51:20 -0800 Subject: [PATCH 36/37] Remove newline between imports --- packages/animations/test/shared_axis_transition_test.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/animations/test/shared_axis_transition_test.dart b/packages/animations/test/shared_axis_transition_test.dart index e8808434cb51..10613971fe09 100644 --- a/packages/animations/test/shared_axis_transition_test.dart +++ b/packages/animations/test/shared_axis_transition_test.dart @@ -7,7 +7,6 @@ import 'package:flutter/animation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:flutter/widgets.dart'; - import 'package:vector_math/vector_math_64.dart'; void main() { From cd865d851895422fc9a7f57cc354ba37a54d7c38 Mon Sep 17 00:00:00 2001 From: Shi Hao Hong Date: Mon, 13 Jan 2020 17:08:09 -0800 Subject: [PATCH 37/37] Address code review feedback --- .../animations/lib/src/shared_axis_transition.dart | 10 +++++----- packages/animations/pubspec.yaml | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/animations/lib/src/shared_axis_transition.dart b/packages/animations/lib/src/shared_axis_transition.dart index d572ef1706c7..3d8cc6121d72 100644 --- a/packages/animations/lib/src/shared_axis_transition.dart +++ b/packages/animations/lib/src/shared_axis_transition.dart @@ -11,13 +11,13 @@ import 'utils/curves.dart'; /// Determines which type of shared axis transition is used. enum SharedAxisTransitionType { - /// Creates a shared axis vertical translation page transition. + /// Creates a shared axis vertical (y-axis) page transition. vertical, - /// Creates a shared axis horizontal translation page transition. + /// Creates a shared axis horizontal (x-axis) page transition. horizontal, - /// Creates a shared axis scaled translation page transition. + /// Creates a shared axis scaled (z-axis) page transition. scaled, } @@ -52,7 +52,7 @@ enum SharedAxisTransitionType { /// return Container( /// color: Colors.red, /// child: Center( -/// child: MaterialButton( +/// child: RaisedButton( /// child: Text('Push route'), /// onPressed: () { /// Navigator.of(context).pushNamed('/a'); @@ -65,7 +65,7 @@ enum SharedAxisTransitionType { /// return Container( /// color: Colors.blue, /// child: Center( -/// child: MaterialButton( +/// child: RaisedButton( /// child: Text('Pop route'), /// onPressed: () { /// Navigator.of(context).pop(); diff --git a/packages/animations/pubspec.yaml b/packages/animations/pubspec.yaml index 26fe35d391b1..b778a35daf78 100644 --- a/packages/animations/pubspec.yaml +++ b/packages/animations/pubspec.yaml @@ -9,8 +9,8 @@ environment: dependencies: flutter: sdk: flutter - vector_math: ^2.0.8 dev_dependencies: flutter_test: sdk: flutter + vector_math: ^2.0.8