Revert "[PageTransitionsBuilder] Fix 'ZoomPageTransition' built more than once (#58686)" (#59992)
This reverts commit fe15d1e793cb509f0d616caf1eab19477262b804.
diff --git a/packages/flutter/lib/src/material/page_transitions_theme.dart b/packages/flutter/lib/src/material/page_transitions_theme.dart
index 05ab2b6..2b2c523 100644
--- a/packages/flutter/lib/src/material/page_transitions_theme.dart
+++ b/packages/flutter/lib/src/material/page_transitions_theme.dart
@@ -150,19 +150,19 @@
// Zooms and fades a new page in, zooming out the previous page. This transition
// is designed to match the Android 10 activity transition.
-class _ZoomPageTransition extends StatelessWidget {
- /// Creates a [_ZoomPageTransition].
- ///
- /// The [animation] and [secondaryAnimation] argument are required and must
- /// not be null.
+class _ZoomPageTransition extends StatefulWidget {
const _ZoomPageTransition({
Key key,
- @required this.animation,
- @required this.secondaryAnimation,
+ this.animation,
+ this.secondaryAnimation,
this.child,
- }) : assert(animation != null),
- assert(secondaryAnimation != null),
- super(key: key);
+ }) : super(key: key);
+
+ // The scrim obscures the old page by becoming increasingly opaque.
+ static final Tween<double> _scrimOpacityTween = Tween<double>(
+ begin: 0.0,
+ end: 0.60,
+ );
// A curve sequence that is similar to the 'fastOutExtraSlowIn' curve used in
// the native transition.
@@ -179,207 +179,132 @@
),
];
static final TweenSequence<double> _scaleCurveSequence = TweenSequence<double>(fastOutExtraSlowInTweenSequenceItems);
+ static final FlippedTweenSequence _flippedScaleCurveSequence = FlippedTweenSequence(fastOutExtraSlowInTweenSequenceItems);
- /// The animation that drives the [child]'s entrance and exit.
- ///
- /// See also:
- ///
- /// * [TransitionRoute.animation], which is the value given to this property
- /// when the [_ZoomPageTransition] is used as a page transition.
final Animation<double> 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 [_ZoomPageTransition] is used as a page transition.
final Animation<double> 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
- Widget build(BuildContext context) {
- return DualTransitionBuilder(
- animation: animation,
- forwardBuilder: (
- BuildContext context,
- Animation<double> animation,
- Widget child,
- ) {
- return _ZoomEnterTransition(
- animation: animation,
- child: child,
- );
- },
- reverseBuilder: (
- BuildContext context,
- Animation<double> animation,
- Widget child,
- ) {
- return _ZoomExitTransition(
- animation: animation,
- reverse: true,
- child: child,
- );
- },
- child: DualTransitionBuilder(
- animation: ReverseAnimation(secondaryAnimation),
- forwardBuilder: (
- BuildContext context,
- Animation<double> animation,
- Widget child,
- ) {
- return _ZoomEnterTransition(
- animation: animation,
- reverse: true,
- child: child,
- );
- },
- reverseBuilder: (
- BuildContext context,
- Animation<double> animation,
- Widget child,
- ) {
- return _ZoomExitTransition(
- animation: animation,
- child: child,
- );
- },
- child: child,
- ),
- );
- }
+ __ZoomPageTransitionState createState() => __ZoomPageTransitionState();
}
-class _ZoomEnterTransition extends StatelessWidget {
- const _ZoomEnterTransition({
- Key key,
- @required this.animation,
- this.reverse = false,
- this.child,
- }) : assert(animation != null),
- assert(reverse != null),
- super(key: key);
+class __ZoomPageTransitionState extends State<_ZoomPageTransition> {
+ AnimationStatus _currentAnimationStatus;
+ AnimationStatus _lastAnimationStatus;
- final Animation<double> animation;
- final Widget child;
- final bool reverse;
+ @override
+ void initState() {
+ super.initState();
+ widget.animation.addStatusListener((AnimationStatus animationStatus) {
+ _lastAnimationStatus = _currentAnimationStatus;
+ _currentAnimationStatus = animationStatus;
+ });
+ }
- static final Animatable<double> _fadeInTransition = Tween<double>(
- begin: 0.0,
- end: 1.00,
- ).chain(CurveTween(curve: const Interval(0.125, 0.250)));
+ // This check ensures that the animation reverses the original animation if
+ // the transition were interrupted midway. This prevents a disjointed
+ // experience since the reverse animation uses different fade and scaling
+ // curves.
+ bool get _transitionWasInterrupted {
+ bool wasInProgress = false;
+ bool isInProgress = false;
- static final Animatable<double> _scaleDownTransition = Tween<double>(
- begin: 1.10,
- end: 1.00,
- ).chain(_ZoomPageTransition._scaleCurveSequence);
-
- static final Animatable<double> _scaleUpTransition = Tween<double>(
- begin: 0.85,
- end: 1.00,
- ).chain(_ZoomPageTransition._scaleCurveSequence);
-
- static final Animatable<double> _scrimOpacityTween = Tween<double>(
- begin: 0.0,
- end: 0.60,
- ).chain(CurveTween(curve: const Interval(0.2075, 0.4175)));
+ switch (_currentAnimationStatus) {
+ case AnimationStatus.completed:
+ case AnimationStatus.dismissed:
+ isInProgress = false;
+ break;
+ case AnimationStatus.forward:
+ case AnimationStatus.reverse:
+ isInProgress = true;
+ break;
+ }
+ switch (_lastAnimationStatus) {
+ case AnimationStatus.completed:
+ case AnimationStatus.dismissed:
+ wasInProgress = false;
+ break;
+ case AnimationStatus.forward:
+ case AnimationStatus.reverse:
+ wasInProgress = true;
+ break;
+ }
+ return wasInProgress && isInProgress;
+ }
@override
Widget build(BuildContext context) {
- double opacity = 0;
- // The transition's scrim opacity only increases on the forward transition. In the reverse
- // transition, the opacity should always be 0.0.
- //
- // Therefore, we need to only apply the scrim opacity animation when the transition
- // is running forwards.
- //
- // The reason that we check that the animation's status is not `completed` instead
- // of checking that it is `forward` is that this allows the interrupted reversal of the
- // forward transition to smoothly fade the scrim away. This prevents a disjointed
- // removal of the scrim.
- if (!reverse && animation.status != AnimationStatus.completed) {
- opacity = _scrimOpacityTween.evaluate(animation);
- }
+ final Animation<double> _forwardScrimOpacityAnimation = widget.animation.drive(
+ _ZoomPageTransition._scrimOpacityTween
+ .chain(CurveTween(curve: const Interval(0.2075, 0.4175))));
- final Animation<double> fadeTransition = reverse
- ? kAlwaysCompleteAnimation
- : _fadeInTransition.animate(animation);
+ final Animation<double> _forwardEndScreenScaleTransition = widget.animation.drive(
+ Tween<double>(begin: 0.85, end: 1.00)
+ .chain(_ZoomPageTransition._scaleCurveSequence));
- final Animation<double> scaleTransition = (reverse
- ? _scaleDownTransition
- : _scaleUpTransition
- ).animate(animation);
+ final Animation<double> _forwardStartScreenScaleTransition = widget.secondaryAnimation.drive(
+ Tween<double>(begin: 1.00, end: 1.05)
+ .chain(_ZoomPageTransition._scaleCurveSequence));
+
+ final Animation<double> _forwardEndScreenFadeTransition = widget.animation.drive(
+ Tween<double>(begin: 0.0, end: 1.00)
+ .chain(CurveTween(curve: const Interval(0.125, 0.250))));
+
+ final Animation<double> _reverseEndScreenScaleTransition = widget.secondaryAnimation.drive(
+ Tween<double>(begin: 1.00, end: 1.10)
+ .chain(_ZoomPageTransition._flippedScaleCurveSequence));
+
+ final Animation<double> _reverseStartScreenScaleTransition = widget.animation.drive(
+ Tween<double>(begin: 0.9, end: 1.0)
+ .chain(_ZoomPageTransition._flippedScaleCurveSequence));
+
+ final Animation<double> _reverseStartScreenFadeTransition = widget.animation.drive(
+ Tween<double>(begin: 0.0, end: 1.00)
+ .chain(CurveTween(curve: const Interval(1 - 0.2075, 1 - 0.0825))));
return AnimatedBuilder(
- animation: animation,
+ animation: widget.animation,
builder: (BuildContext context, Widget child) {
- return Container(
- color: Colors.black.withOpacity(opacity),
- child: child,
- );
+ if (widget.animation.status == AnimationStatus.forward || _transitionWasInterrupted) {
+ return Container(
+ color: Colors.black.withOpacity(_forwardScrimOpacityAnimation.value),
+ child: FadeTransition(
+ opacity: _forwardEndScreenFadeTransition,
+ child: ScaleTransition(
+ scale: _forwardEndScreenScaleTransition,
+ child: child,
+ ),
+ ),
+ );
+ } else if (widget.animation.status == AnimationStatus.reverse) {
+ return ScaleTransition(
+ scale: _reverseStartScreenScaleTransition,
+ child: FadeTransition(
+ opacity: _reverseStartScreenFadeTransition,
+ child: child,
+ ),
+ );
+ }
+ return child;
},
- child: FadeTransition(
- opacity: fadeTransition,
- child: ScaleTransition(
- scale: scaleTransition,
- child: child,
- ),
- ),
- );
- }
-}
-
-class _ZoomExitTransition extends StatelessWidget {
- const _ZoomExitTransition({
- Key key,
- @required this.animation,
- this.reverse = false,
- this.child,
- }) : assert(animation != null),
- assert(reverse != null),
- super(key: key);
-
- final Animation<double> animation;
- final bool reverse;
- final Widget child;
-
- static final Animatable<double> _fadeOutTransition = Tween<double>(
- begin: 1.0,
- end: 0.0,
- ).chain(CurveTween(curve: const Interval(0.0825, 0.2075)));
-
- static final Animatable<double> _scaleUpTransition = Tween<double>(
- begin: 1.00,
- end: 1.05,
- ).chain(_ZoomPageTransition._scaleCurveSequence);
-
- static final Animatable<double> _scaleDownTransition = Tween<double>(
- begin: 1.00,
- end: 0.90,
- ).chain(_ZoomPageTransition._scaleCurveSequence);
-
- @override
- Widget build(BuildContext context) {
- final Animation<double> fadeTransition = reverse
- ? _fadeOutTransition.animate(animation)
- : kAlwaysCompleteAnimation;
- final Animation<double> scaleTransition = (reverse
- ? _scaleDownTransition
- : _scaleUpTransition
- ).animate(animation);
-
- return FadeTransition(
- opacity: fadeTransition,
- child: ScaleTransition(
- scale: scaleTransition,
- child: child,
+ child: AnimatedBuilder(
+ animation: widget.secondaryAnimation,
+ builder: (BuildContext context, Widget child) {
+ if (widget.secondaryAnimation.status == AnimationStatus.forward || _transitionWasInterrupted) {
+ return ScaleTransition(
+ scale: _forwardStartScreenScaleTransition,
+ child: child,
+ );
+ } else if (widget.secondaryAnimation.status == AnimationStatus.reverse) {
+ return ScaleTransition(
+ scale: _reverseEndScreenScaleTransition,
+ child: child,
+ );
+ }
+ return child;
+ },
+ child: widget.child,
),
);
}
diff --git a/packages/flutter/lib/src/widgets/dual_transition_builder.dart b/packages/flutter/lib/src/widgets/dual_transition_builder.dart
deleted file mode 100644
index 239aa11..0000000
--- a/packages/flutter/lib/src/widgets/dual_transition_builder.dart
+++ /dev/null
@@ -1,203 +0,0 @@
-// Copyright 2014 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.
-
-// @dart = 2.8
-
-import 'basic.dart';
-import 'framework.dart';
-
-/// Builder callback used by [DualTransitionBuilder].
-///
-/// The builder is expected to return a transition powered by the provided
-/// `animation` and wrapping the provided `child`.
-///
-/// The `animation` provided to the builder always runs forward from 0.0 to 1.0.
-typedef AnimatedTransitionBuilder = Widget Function(
- BuildContext context,
- Animation<double> animation,
- Widget child,
-);
-
-/// A transition builder that animates its [child] based on the
-/// [AnimationStatus] of the provided [animation].
-///
-/// This widget can be used to specify different enter and exit transitions for
-/// a [child].
-///
-/// While the [animation] runs forward, the [child] is animated according to
-/// [forwardBuilder] and while the [animation] is running in reverse, it is
-/// animated according to [reverseBuilder].
-///
-/// Using this builder allows the widget tree to maintain its shape by nesting
-/// the enter and exit transitions. This ensures that no state information of
-/// any descendant widget is lost when the transition starts or completes.
-class DualTransitionBuilder extends StatefulWidget {
- /// Creates a [DualTransitionBuilder].
- ///
- /// The [animation], [forwardBuilder], and [reverseBuilder] arguments are
- /// required and must not be null.
- const DualTransitionBuilder({
- Key key,
- @required this.animation,
- @required this.forwardBuilder,
- @required this.reverseBuilder,
- this.child,
- }) : assert(animation != null),
- assert(forwardBuilder != null),
- assert(reverseBuilder != null),
- super(key: key);
-
- /// The animation that drives the [child]'s transition.
- ///
- /// When this animation runs forward, the [child] transitions as specified by
- /// [forwardBuilder]. When it runs in reverse, the child transitions according
- /// to [reverseBuilder].
- final Animation<double> animation;
-
- /// A builder for the transition that makes [child] appear on screen.
- ///
- /// The [child] should be fully visible when the provided `animation` reaches
- /// 1.0.
- ///
- /// The `animation` provided to this builder is running forward from 0.0 to
- /// 1.0 when [animation] runs _forward_. When [animation] runs in reverse,
- /// the given `animation` is set to [kAlwaysCompleteAnimation].
- ///
- /// See also:
- ///
- /// * [reverseBuilder], which builds the transition for making the [child]
- /// disappear from the screen.
- final AnimatedTransitionBuilder forwardBuilder;
-
- /// A builder for a transition that makes [child] disappear from the screen.
- ///
- /// The [child] should be fully invisible when the provided `animation`
- /// reaches 1.0.
- ///
- /// The `animation` provided to this builder is running forward from 0.0 to
- /// 1.0 when [animation] runs in _reverse_. When [animation] runs forward,
- /// the given `animation` is set to [kAlwaysDismissedAnimation].
- ///
- /// See also:
- ///
- /// * [forwardBuilder], which builds the transition for making the [child]
- /// appear on screen.
- final AnimatedTransitionBuilder reverseBuilder;
-
- /// The widget below this [DualTransitionBuilder] in the tree.
- ///
- /// This child widget will be wrapped by the transitions built by
- /// [forwardBuilder] and [reverseBuilder].
- final Widget child;
-
- @override
- State<DualTransitionBuilder> createState() => _DualTransitionBuilderState();
-}
-
-class _DualTransitionBuilderState extends State<DualTransitionBuilder> {
- AnimationStatus _effectiveAnimationStatus;
- final ProxyAnimation _forwardAnimation = ProxyAnimation();
- final ProxyAnimation _reverseAnimation = ProxyAnimation();
-
- @override
- void initState() {
- super.initState();
- _effectiveAnimationStatus = widget.animation.status;
- widget.animation.addStatusListener(_animationListener);
- _updateAnimations();
- }
-
- void _animationListener(AnimationStatus animationStatus) {
- final AnimationStatus oldEffective = _effectiveAnimationStatus;
- _effectiveAnimationStatus = _calculateEffectiveAnimationStatus(
- lastEffective: _effectiveAnimationStatus,
- current: animationStatus,
- );
- if (oldEffective != _effectiveAnimationStatus) {
- _updateAnimations();
- }
- }
-
- @override
- void didUpdateWidget(DualTransitionBuilder oldWidget) {
- super.didUpdateWidget(oldWidget);
- if (oldWidget.animation != widget.animation) {
- oldWidget.animation.removeStatusListener(_animationListener);
- widget.animation.addStatusListener(_animationListener);
- _animationListener(widget.animation.status);
- }
- }
-
- // 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 _updateAnimations() {
- switch (_effectiveAnimationStatus) {
- case AnimationStatus.dismissed:
- case AnimationStatus.forward:
- _forwardAnimation.parent = widget.animation;
- _reverseAnimation.parent = kAlwaysDismissedAnimation;
- break;
- case AnimationStatus.reverse:
- case AnimationStatus.completed:
- _forwardAnimation.parent = kAlwaysCompleteAnimation;
- _reverseAnimation.parent = ReverseAnimation(widget.animation);
- break;
- }
- }
-
- @override
- void dispose() {
- widget.animation.removeStatusListener(_animationListener);
- super.dispose();
- }
-
- @override
- Widget build(BuildContext context) {
- return widget.forwardBuilder(
- context,
- _forwardAnimation,
- widget.reverseBuilder(
- context,
- _reverseAnimation,
- widget.child,
- ),
- );
- }
-}
diff --git a/packages/flutter/lib/widgets.dart b/packages/flutter/lib/widgets.dart
index 1805f6e..d8cc265 100644
--- a/packages/flutter/lib/widgets.dart
+++ b/packages/flutter/lib/widgets.dart
@@ -37,7 +37,6 @@
export 'src/widgets/disposable_build_context.dart';
export 'src/widgets/drag_target.dart';
export 'src/widgets/draggable_scrollable_sheet.dart';
-export 'src/widgets/dual_transition_builder.dart';
export 'src/widgets/editable_text.dart';
export 'src/widgets/fade_in_image.dart';
export 'src/widgets/focus_manager.dart';
diff --git a/packages/flutter/test/material/page_transitions_theme_test.dart b/packages/flutter/test/material/page_transitions_theme_test.dart
index ba1efc3..e823432 100644
--- a/packages/flutter/test/material/page_transitions_theme_test.dart
+++ b/packages/flutter/test/material/page_transitions_theme_test.dart
@@ -161,50 +161,4 @@
expect(find.text('page b'), findsOneWidget);
expect(findZoomPageTransition(), findsOneWidget);
}, variant: TargetPlatformVariant.only(TargetPlatform.android));
-
- testWidgets('_ZoomPageTransition only cause child widget built once', (WidgetTester tester) async {
- // Regression test for https://github.com/flutter/flutter/issues/58345
-
- int builtCount = 0;
-
- final Map<String, WidgetBuilder> routes = <String, WidgetBuilder>{
- '/': (BuildContext context) => Material(
- child: FlatButton(
- child: const Text('push'),
- onPressed: () { Navigator.of(context).pushNamed('/b'); },
- ),
- ),
- '/b': (BuildContext context) => StatefulBuilder(
- builder: (BuildContext context, StateSetter setState) {
- builtCount++; // Increase [builtCount] each time the widget build
- return FlatButton(
- child: const Text('pop'),
- onPressed: () { Navigator.pop(context); },
- );
- },
- ),
- };
-
- await tester.pumpWidget(
- MaterialApp(
- theme: ThemeData(
- pageTransitionsTheme: const PageTransitionsTheme(
- builders: <TargetPlatform, PageTransitionsBuilder>{
- TargetPlatform.android: ZoomPageTransitionsBuilder(), // creates a _ZoomPageTransition
- },
- ),
- ),
- routes: routes,
- ),
- );
-
- // No matter push or pop was called, the child widget should built only once.
- await tester.tap(find.text('push'));
- await tester.pumpAndSettle();
- expect(builtCount, 1);
-
- await tester.tap(find.text('pop'));
- await tester.pumpAndSettle();
- expect(builtCount, 1);
- }, variant: TargetPlatformVariant.only(TargetPlatform.android));
}
diff --git a/packages/flutter/test/widgets/dual_transition_builder_test.dart b/packages/flutter/test/widgets/dual_transition_builder_test.dart
deleted file mode 100644
index 398173c..0000000
--- a/packages/flutter/test/widgets/dual_transition_builder_test.dart
+++ /dev/null
@@ -1,302 +0,0 @@
-// Copyright 2014 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.
-
-// @dart = 2.8
-
-import 'package:flutter_test/flutter_test.dart';
-import 'package:flutter/material.dart';
-import 'package:flutter/widgets.dart';
-
-import 'package:flutter/src/widgets/dual_transition_builder.dart';
-
-void main() {
- testWidgets('runs animations', (WidgetTester tester) async {
- final AnimationController controller = AnimationController(
- vsync: const TestVSync(),
- duration: const Duration(milliseconds: 300),
- );
-
- await tester.pumpWidget(Center(
- child: DualTransitionBuilder(
- animation: controller,
- forwardBuilder: (
- BuildContext context,
- Animation<double> animation,
- Widget child,
- ) {
- return ScaleTransition(
- scale: animation,
- child: child,
- );
- },
- reverseBuilder: (
- BuildContext context,
- Animation<double> animation,
- Widget child,
- ) {
- return FadeTransition(
- opacity: Tween<double>(begin: 1.0, end: 0.0).animate(animation),
- child: child,
- );
- },
- child: Container(
- color: Colors.green,
- height: 100,
- width: 100,
- ),
- ),
- ));
- expect(_getScale(tester), 0.0);
- expect(_getOpacity(tester), 1.0);
-
- controller.forward();
- await tester.pump();
- await tester.pump(const Duration(milliseconds: 150));
- expect(_getScale(tester), 0.5);
- expect(_getOpacity(tester), 1.0);
-
- await tester.pump(const Duration(milliseconds: 150));
- expect(_getScale(tester), 1.0);
- expect(_getOpacity(tester), 1.0);
-
- await tester.pumpAndSettle();
- expect(_getScale(tester), 1.0);
- expect(_getOpacity(tester), 1.0);
-
- controller.reverse();
- await tester.pump();
- await tester.pump(const Duration(milliseconds: 150));
- expect(_getScale(tester), 1.0);
- expect(_getOpacity(tester), 0.5);
-
- await tester.pump(const Duration(milliseconds: 150));
- expect(_getScale(tester), 1.0);
- expect(_getOpacity(tester), 0.0);
-
- await tester.pumpAndSettle();
- expect(_getScale(tester), 0.0);
- expect(_getOpacity(tester), 1.0);
- });
-
- testWidgets('keeps state', (WidgetTester tester) async {
- final AnimationController controller = AnimationController(
- vsync: const TestVSync(),
- duration: const Duration(milliseconds: 300),
- );
-
- await tester.pumpWidget(Directionality(
- textDirection: TextDirection.ltr,
- child: Center(
- child: DualTransitionBuilder(
- animation: controller,
- forwardBuilder: (
- BuildContext context,
- Animation<double> animation,
- Widget child,
- ) {
- return ScaleTransition(
- scale: animation,
- child: child,
- );
- },
- reverseBuilder: (
- BuildContext context,
- Animation<double> animation,
- Widget child,
- ) {
- return FadeTransition(
- opacity: Tween<double>(begin: 1.0, end: 0.0).animate(animation),
- child: child,
- );
- },
- child: const _StatefulTestWidget(name: 'Foo'),
- ),
- ),
- ));
- final State<StatefulWidget> state =
- tester.state(find.byType(_StatefulTestWidget));
- expect(state, isNotNull);
-
- controller.forward();
- await tester.pump();
- expect(state, same(tester.state(find.byType(_StatefulTestWidget))));
- await tester.pump(const Duration(milliseconds: 150));
- expect(state, same(tester.state(find.byType(_StatefulTestWidget))));
-
- await tester.pump(const Duration(milliseconds: 150));
- expect(state, same(tester.state(find.byType(_StatefulTestWidget))));
-
- await tester.pumpAndSettle();
- expect(state, same(tester.state(find.byType(_StatefulTestWidget))));
-
- controller.reverse();
- await tester.pump();
- expect(state, same(tester.state(find.byType(_StatefulTestWidget))));
- await tester.pump(const Duration(milliseconds: 150));
- expect(state, same(tester.state(find.byType(_StatefulTestWidget))));
-
- await tester.pump(const Duration(milliseconds: 150));
- expect(state, same(tester.state(find.byType(_StatefulTestWidget))));
-
- await tester.pumpAndSettle();
- expect(state, same(tester.state(find.byType(_StatefulTestWidget))));
- });
-
- testWidgets('does not jump when interrupted - forward',
- (WidgetTester tester) async {
- final AnimationController controller = AnimationController(
- vsync: const TestVSync(),
- duration: const Duration(milliseconds: 300),
- );
- await tester.pumpWidget(Center(
- child: DualTransitionBuilder(
- animation: controller,
- forwardBuilder: (
- BuildContext context,
- Animation<double> animation,
- Widget child,
- ) {
- return ScaleTransition(
- scale: animation,
- child: child,
- );
- },
- reverseBuilder: (
- BuildContext context,
- Animation<double> animation,
- Widget child,
- ) {
- return FadeTransition(
- opacity: Tween<double>(begin: 1.0, end: 0.0).animate(animation),
- child: child,
- );
- },
- child: Container(
- color: Colors.green,
- height: 100,
- width: 100,
- ),
- ),
- ));
- expect(_getScale(tester), 0.0);
- expect(_getOpacity(tester), 1.0);
-
- controller.forward();
- await tester.pump();
- await tester.pump(const Duration(milliseconds: 150));
- expect(_getScale(tester), 0.5);
- expect(_getOpacity(tester), 1.0);
-
- controller.reverse();
- expect(_getScale(tester), 0.5);
- expect(_getOpacity(tester), 1.0);
- await tester.pump();
- expect(_getScale(tester), 0.5);
- expect(_getOpacity(tester), 1.0);
-
- await tester.pump(const Duration(milliseconds: 75));
- expect(_getScale(tester), 0.25);
- expect(_getOpacity(tester), 1.0);
-
- await tester.pump(const Duration(milliseconds: 75));
- expect(_getScale(tester), 0.0);
- expect(_getOpacity(tester), 1.0);
-
- await tester.pumpAndSettle();
- expect(_getScale(tester), 0.0);
- expect(_getOpacity(tester), 1.0);
- });
-
- testWidgets('does not jump when interrupted - reverse',
- (WidgetTester tester) async {
- final AnimationController controller = AnimationController(
- value: 1.0,
- vsync: const TestVSync(),
- duration: const Duration(milliseconds: 300),
- );
- await tester.pumpWidget(Center(
- child: DualTransitionBuilder(
- animation: controller,
- forwardBuilder: (
- BuildContext context,
- Animation<double> animation,
- Widget child,
- ) {
- return ScaleTransition(
- scale: animation,
- child: child,
- );
- },
- reverseBuilder: (
- BuildContext context,
- Animation<double> animation,
- Widget child,
- ) {
- return FadeTransition(
- opacity: Tween<double>(begin: 1.0, end: 0.0).animate(animation),
- child: child,
- );
- },
- child: Container(
- color: Colors.green,
- height: 100,
- width: 100,
- ),
- ),
- ));
- expect(_getScale(tester), 1.0);
- expect(_getOpacity(tester), 1.0);
-
- controller.reverse();
- await tester.pump();
- await tester.pump(const Duration(milliseconds: 150));
- expect(_getScale(tester), 1.0);
- expect(_getOpacity(tester), 0.5);
-
- controller.forward();
- expect(_getScale(tester), 1.0);
- expect(_getOpacity(tester), 0.5);
- await tester.pump();
- expect(_getScale(tester), 1.0);
- expect(_getOpacity(tester), 0.5);
-
- await tester.pump(const Duration(milliseconds: 75));
- expect(_getScale(tester), 1.0);
- expect(_getOpacity(tester), 0.75);
-
- await tester.pump(const Duration(milliseconds: 75));
- expect(_getScale(tester), 1.0);
- expect(_getOpacity(tester), 1.0);
-
- await tester.pumpAndSettle();
- expect(_getScale(tester), 1.0);
- expect(_getOpacity(tester), 1.0);
- });
-}
-
-double _getScale(WidgetTester tester) {
- final ScaleTransition scale = tester.widget(find.byType(ScaleTransition));
- return scale.scale.value;
-}
-
-double _getOpacity(WidgetTester tester) {
- final FadeTransition scale = tester.widget(find.byType(FadeTransition));
- return scale.opacity.value;
-}
-
-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);
- }
-}